diff --git a/packages/jsdoc-doclet/lib/schema.js b/packages/jsdoc-doclet/lib/schema.js index 9222460e..4433ed08 100644 --- a/packages/jsdoc-doclet/lib/schema.js +++ b/packages/jsdoc-doclet/lib/schema.js @@ -106,6 +106,10 @@ export const TYPE_PROPERTY_SCHEMA = { type: OBJECT, additionalProperties: false, properties: { + // original type expression + expression: { + type: STRING, + }, names: { type: ARRAY, minItems: 1, @@ -113,11 +117,6 @@ export const TYPE_PROPERTY_SCHEMA = { type: STRING, }, }, - // type parser output - parsedType: { - type: OBJECT, - additionalProperties: true, - }, }, }; diff --git a/packages/jsdoc-tag/lib/tag.js b/packages/jsdoc-tag/lib/tag.js index 2390f9eb..21e160fc 100644 --- a/packages/jsdoc-tag/lib/tag.js +++ b/packages/jsdoc-tag/lib/tag.js @@ -56,17 +56,6 @@ function trim(text, opts, meta) { return text; } -function addHiddenProperty(obj, propName, propValue, env) { - const { options } = env; - - Object.defineProperty(obj, propName, { - value: propValue, - writable: true, - enumerable: Boolean(options.debug), - configurable: true, - }); -} - function parseType({ env, text, originalTitle }, { canHaveName, canHaveType }, meta) { let log; @@ -90,7 +79,7 @@ function parseType({ env, text, originalTitle }, { canHaveName, canHaveType }, m } } -function processTagText(tagInstance, tagDef, meta, env) { +function processTagText(tagInstance, tagDef, meta) { let tagType; if (tagDef.onTagText) { @@ -105,9 +94,9 @@ function processTagText(tagInstance, tagDef, meta, env) { if (tagType.type) { if (tagType.type.length) { tagInstance.value.type = { + expression: tagType.typeExpression, names: tagType.type, }; - addHiddenProperty(tagInstance.value.type, 'parsedType', tagType.parsedType, env); } ['optional', 'nullable', 'variable', 'defaultvalue'].forEach((prop) => { @@ -187,7 +176,7 @@ export class Tag { this.text = trim(tagBody, trimOpts, meta); if (this.text) { - processTagText(this, tagDef, meta, env); + processTagText(this, tagDef, meta); } tagValidator.validate(this, tagDef, meta); diff --git a/packages/jsdoc-tag/lib/type.js b/packages/jsdoc-tag/lib/type.js index 8deda169..60a09ae8 100644 --- a/packages/jsdoc-tag/lib/type.js +++ b/packages/jsdoc-tag/lib/type.js @@ -283,7 +283,6 @@ function parseTypeExpression(tagInfo) { } tagInfo.type = tagInfo.type.concat(getTypeStrings(parsedType, true)); - tagInfo.parsedType = parsedType; // Catharsis and JSDoc use the same names for 'optional' and 'nullable'... ['optional', 'nullable'].forEach((key) => { diff --git a/packages/jsdoc-tag/test/specs/lib/tag.js b/packages/jsdoc-tag/test/specs/lib/tag.js index dce58e5e..dbe75089 100644 --- a/packages/jsdoc-tag/test/specs/lib/tag.js +++ b/packages/jsdoc-tag/test/specs/lib/tag.js @@ -52,31 +52,18 @@ describe('@jsdoc/tag/lib/tag', () => { ]; const textExampleIndented = exampleIndentedRaw.join(''); - let tagArg; - let tagExample; - let tagExampleIndented; - let tagParam; - let tagParamWithType; - let tagType; - - function createTags() { - // Synonym for @param; space in the title - tagArg = new Tag('arg ', text, meta, jsdoc.env); - // @param with no type, but with optional and defaultvalue - tagParam = new Tag('param', '[foo=1]', meta, jsdoc.env); - // @param with type and no type modifiers (such as optional) - tagParamWithType = new Tag('param', '{string} foo', meta, jsdoc.env); - // @example that does not need indentation to be removed - tagExample = new Tag('example', textExample, meta, jsdoc.env); - // @example that needs indentation to be removed - tagExampleIndented = new Tag('example', textExampleIndented, meta, jsdoc.env); - // For testing that `onTagText` is called when necessary - tagType = new Tag('type', 'MyType ', meta, jsdoc.env); - } - - beforeEach(() => { - createTags(); - }); + // Synonym for @param; space in the title + const tagArg = new Tag('arg ', text, meta, jsdoc.env); + // @param with no type, but with optional and defaultvalue + const tagParam = new Tag('param', '[foo=1]', meta, jsdoc.env); + // @param with type and no type modifiers (such as optional) + const tagParamWithType = new Tag('param', '{string} foo', meta, jsdoc.env); + // @example that does not need indentation to be removed + const tagExample = new Tag('example', textExample, meta, jsdoc.env); + // @example that needs indentation to be removed + const tagExampleIndented = new Tag('example', textExampleIndented, meta, jsdoc.env); + // For testing that `onTagText` is called when necessary + const tagType = new Tag('type', 'MyType ', meta, jsdoc.env); describe('`originalTitle` property', () => { it('has an `originalTitle` property', () => { @@ -170,7 +157,6 @@ describe('@jsdoc/tag/lib/tag', () => { function verifyTagType(tag) { let def; - let descriptor; let info; def = jsdocDictionary.lookUp(tag.title); @@ -185,26 +171,18 @@ describe('@jsdoc/tag/lib/tag', () => { } }); - if (info.type && info.type.length) { + if (info.type?.length) { expect(tag.value.type).toBeObject(); expect(tag.value.type.names).toEqual(info.type); - expect(tag.value.type.parsedType).toBeObject(); - - descriptor = Object.getOwnPropertyDescriptor(tag.value.type, 'parsedType'); - expect(descriptor.enumerable).toBe(Boolean(options.debug)); + expect(tag.value.type.expression).toBeString(); } } it('contains the type information for tags with types', () => { - [true, false].forEach((bool) => { - options.debug = bool; - createTags(); - - verifyTagType(tagType); - verifyTagType(tagArg); - verifyTagType(tagParam); - }); + verifyTagType(tagType); + verifyTagType(tagArg); + verifyTagType(tagParam); }); it('contains any additional descriptive text', () => { @@ -222,6 +200,12 @@ describe('@jsdoc/tag/lib/tag', () => { expect(Object.hasOwn(tagParamWithType.value, modifier)).toBeFalse(); }); }); + + it('contains the original type expression', () => { + const paramWithTypeModifiers = new Tag('param', '{?Object} foo', meta, jsdoc.env); + + expect(paramWithTypeModifiers.value.type.expression).toBe('?Object'); + }); }); // Additional tests in validator.js. diff --git a/packages/jsdoc-template-legacy/lib/templateHelper.js b/packages/jsdoc-template-legacy/lib/templateHelper.js index 48c98436..ef09b29e 100644 --- a/packages/jsdoc-template-legacy/lib/templateHelper.js +++ b/packages/jsdoc-template-legacy/lib/templateHelper.js @@ -244,7 +244,7 @@ function parseType(longname) { let err; try { - return catharsis.parse(longname, { jsdoc: true }); + return catharsis.parse(longname, { jsdoc: true, useCache: false }); } catch (e) { err = new Error(`unable to parse ${longname}: ${e.message}`); console.error(err); @@ -253,7 +253,9 @@ function parseType(longname) { } } -function stringifyType(parsedType, cssClass, stringifyLinkMap) { +function stringifyType(typeExpression, cssClass, stringifyLinkMap) { + const parsedType = parseType(typeExpression); + return catharsis.stringify(parsedType, { cssClass: cssClass, htmlSafe: true, @@ -315,8 +317,6 @@ function buildLink(longname, linkText, options) { let stripped; let text; - let parsedType; - // handle cases like: // @see // @see http://example.org @@ -333,9 +333,7 @@ function buildLink(longname, linkText, options) { /\{@.+\}/.test(longname) === false && /^<[\s\S]+>/.test(longname) === false ) { - parsedType = parseType(longname); - - return stringifyType(parsedType, options.cssClass, options.linkMap); + return stringifyType(longname, options.cssClass, options.linkMap); } else { fileUrl = Object.hasOwn(options.linkMap, longname) ? options.linkMap[longname] : ''; text = linkText || (options.shortenName ? getShortName(longname) : longname); diff --git a/packages/jsdoc-template-legacy/test/specs/lib/templateHelper.js b/packages/jsdoc-template-legacy/test/specs/lib/templateHelper.js index 729cdf68..21f70866 100644 --- a/packages/jsdoc-template-legacy/test/specs/lib/templateHelper.js +++ b/packages/jsdoc-template-legacy/test/specs/lib/templateHelper.js @@ -364,27 +364,29 @@ describe('@jsdoc/template-legacy/lib/templateHelper', () => { it('works correctly with type applications if only the longname is specified', () => { const link = helper.linkto('Array.'); - expect(link).toBe('Array.<LinktoFakeClass>'); + expect(link).toBe('Array<LinktoFakeClass>'); }); it('works correctly with type applications if a class is not specified', () => { const link = helper.linkto('Array.', 'link text'); - expect(link).toBe('Array.<LinktoFakeClass>'); + expect(link).toBe('Array<LinktoFakeClass>'); }); it('works correctly with type applications if a class is specified', () => { const link = helper.linkto('Array.', 'link text', 'myclass'); - expect(link).toBe('Array.<LinktoFakeClass>'); + expect(link).toBe( + 'Array<LinktoFakeClass>' + ); }); it('works correctly with type applications that include a type union', () => { const link = helper.linkto('Array.<(linktoTest|LinktoFakeClass)>', 'link text'); expect(link).toBe( - 'Array.<(linktoTest|' + - 'LinktoFakeClass)>' + 'Array<(linktoTest|' + + 'LinktoFakeClass)>' ); }); @@ -857,7 +859,7 @@ describe('@jsdoc/template-legacy/lib/templateHelper', () => { expect(types).toBeArrayOfSize(2); expect(types).toContain('number'); - expect(types).toContain(helper.htmlsafe('Array.')); + expect(types).toContain('Array<boolean>'); }); it('creates links for types if relevant', () => { @@ -965,8 +967,8 @@ describe('@jsdoc/template-legacy/lib/templateHelper', () => { }; const html = helper.getSignatureReturns(mockDoclet); - expect(html).not.toContain('Array.'); - expect(html).toContain('Array.<string>'); + expect(html).not.toContain('Array'); + expect(html).toContain('Array<string>'); }); it('returns an empty array if the doclet has no returns', () => { diff --git a/packages/jsdoc/test/specs/documentation/paramtagsametype.js b/packages/jsdoc/test/specs/documentation/paramtagsametype.js deleted file mode 100644 index 9732aa66..00000000 --- a/packages/jsdoc/test/specs/documentation/paramtagsametype.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright 2020 the JSDoc Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -const { options } = jsdoc.env; - -describe('multiple @param tags with the same type expression', () => { - const debug = Boolean(options.debug); - - afterEach(() => { - options.debug = debug; - }); - - it('does not have circular references when type.parsedType is enumerable', () => { - let docSet; - let params; - let stringified; - - // Force type.parsedType to be enumerable. - options.debug = true; - docSet = jsdoc.getDocSetFromFile('test/fixtures/paramtagsametype.js'); - params = docSet.getByLongname('foo.bar.Baz').filter((d) => !d.undocumented)[0].params; - stringified = JSON.stringify(params); - - expect(stringified).toContain('"parsedType":'); - expect(stringified).not.toContain(''); - - // Prevent the schema validator from complaining about `parsedType`. (The schema _should_ - // allow that property, but for some reason, that doesn't work correctly.) - params.forEach((p) => delete p.type.parsedType); - }); -}); diff --git a/packages/jsdoc/test/specs/documentation/typetagwithnewline.js b/packages/jsdoc/test/specs/documentation/typetagwithnewline.js index 78236ce7..3f61c902 100644 --- a/packages/jsdoc/test/specs/documentation/typetagwithnewline.js +++ b/packages/jsdoc/test/specs/documentation/typetagwithnewline.js @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ + describe('@type tag containing a newline character', () => { const docSet = jsdoc.getDocSetFromFile('test/fixtures/typetagwithnewline.js'); const mini = docSet.getByLongname('Matryoshka.mini')[0]; @@ -24,7 +25,7 @@ describe('@type tag containing a newline character', () => { () => { expect(mini).toBeObject(); expect(mini.type).toBeObject(); - expect(mini.type.names).toEqual(['!Array.', '!Array.>']); + expect(mini.type.names).toEqual(['!Array', '!Array>']); } ); @@ -35,9 +36,9 @@ describe('@type tag containing a newline character', () => { expect(mega).toBeObject(); expect(mega.type).toBeObject(); expect(mega.type.names).toEqual([ - '!Array.', - '!Array.>', - '!Array.>>', + '!Array', + '!Array>', + '!Array>>', ]); } ); diff --git a/packages/jsdoc/test/specs/tags/paramtag.js b/packages/jsdoc/test/specs/tags/paramtag.js index dc25428f..7240ef6c 100644 --- a/packages/jsdoc/test/specs/tags/paramtag.js +++ b/packages/jsdoc/test/specs/tags/paramtag.js @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ + describe('@param tag', () => { const docSet = jsdoc.getDocSetFromFile('test/fixtures/paramtag.js'); const docSet2 = jsdoc.getDocSetFromFile('test/fixtures/paramtag2.js'); @@ -21,7 +22,7 @@ describe('@param tag', () => { const find = docSet.getByLongname('find')[0]; expect(find.params).toBeArrayOfSize(1); - expect(find.params[0].type.names.join(', ')).toBe('String, Array.'); + expect(find.params[0].type.names.join(', ')).toBe('String, Array'); expect(find.params[0].name).toBe('targetName'); expect(find.params[0].description).toBe('The name (or names) of what to find.'); }); diff --git a/packages/jsdoc/test/specs/tags/returnstag.js b/packages/jsdoc/test/specs/tags/returnstag.js index 85d87098..d42f5be4 100644 --- a/packages/jsdoc/test/specs/tags/returnstag.js +++ b/packages/jsdoc/test/specs/tags/returnstag.js @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ + describe('@returns tag', () => { const docSet = jsdoc.getDocSetFromFile('test/fixtures/returnstag.js'); @@ -20,7 +21,7 @@ describe('@returns tag', () => { const find = docSet.getByLongname('find')[0]; expect(find.returns).toBeArrayOfSize(1); - expect(find.returns[0].type.names.join(', ')).toBe('string, Array.'); + expect(find.returns[0].type.names.join(', ')).toBe('string, Array'); expect(find.returns[0].description).toBe('The names of the found item(s).'); }); diff --git a/packages/jsdoc/test/specs/tags/typetag.js b/packages/jsdoc/test/specs/tags/typetag.js index cb7637a3..e209fa69 100644 --- a/packages/jsdoc/test/specs/tags/typetag.js +++ b/packages/jsdoc/test/specs/tags/typetag.js @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ + describe('@type tag', () => { const docSet = jsdoc.getDocSetFromFile('test/fixtures/typetag.js'); @@ -21,7 +22,7 @@ describe('@type tag', () => { expect(foo.type).toBeObject(); expect(foo.type.names).toBeArrayOfStrings(); - expect(foo.type.names.join(', ')).toBe('string, Array.'); + expect(foo.type.names.join(', ')).toBe('string, Array'); }); it("When a symbol has a @type tag set to a plain string, the doclet has a type property set to that value's type.", () => {