diff --git a/.changeset/cyan-bears-carry.md b/.changeset/cyan-bears-carry.md new file mode 100644 index 0000000000..da63d34878 --- /dev/null +++ b/.changeset/cyan-bears-carry.md @@ -0,0 +1,5 @@ +--- +'@lit-labs/analyzer': minor +--- + +Added support for analyzing const variables initialized to class or function expressions as ClassDeclaration and FunctionDeclaration, respectively. diff --git a/packages/labs/analyzer/src/lib/javascript/classes.ts b/packages/labs/analyzer/src/lib/javascript/classes.ts index 51b840c77b..deb4dac9ee 100644 --- a/packages/labs/analyzer/src/lib/javascript/classes.ts +++ b/packages/labs/analyzer/src/lib/javascript/classes.ts @@ -42,11 +42,17 @@ import { /** * Returns an analyzer `ClassDeclaration` model for the given - * ts.ClassDeclaration. + * ts.ClassLikeDeclaration. + * + * Note, the `docNode` may differ from the `declaration` in the case of a const + * assignment to a class expression, as the JSDoc will be attached to the + * VariableStatement rather than the class-like expression. */ -const getClassDeclaration = ( - declaration: ts.ClassDeclaration, - analyzer: AnalyzerInterface +export const getClassDeclaration = ( + declaration: ts.ClassLikeDeclaration, + name: string, + analyzer: AnalyzerInterface, + docNode?: ts.Node ) => { if (isLitElementSubclass(declaration, analyzer)) { return getLitElementDeclaration(declaration, analyzer); @@ -55,11 +61,10 @@ const getClassDeclaration = ( return getCustomElementDeclaration(declaration, analyzer); } return new ClassDeclaration({ - // TODO(kschaaf): support anonymous class expressions when assigned to a const - name: declaration.name?.text ?? '', + name, node: declaration, getHeritage: () => getHeritage(declaration, analyzer), - ...parseNodeJSDocInfo(declaration), + ...parseNodeJSDocInfo(docNode ?? declaration), ...getClassMembers(declaration, analyzer), }); }; @@ -68,7 +73,7 @@ const getClassDeclaration = ( * Returns the `fields` and `methods` of a class. */ export const getClassMembers = ( - declaration: ts.ClassDeclaration, + declaration: ts.ClassLikeDeclaration, analyzer: AnalyzerInterface ) => { const fieldMap = new Map(); @@ -140,9 +145,10 @@ export const getClassDeclarationInfo = ( declaration: ts.ClassDeclaration, analyzer: AnalyzerInterface ): DeclarationInfo => { + const name = getClassDeclarationName(declaration); return { - name: getClassDeclarationName(declaration), - factory: () => getClassDeclaration(declaration, analyzer), + name, + factory: () => getClassDeclaration(declaration, name, analyzer), isExport: hasExportModifier(declaration), }; }; diff --git a/packages/labs/analyzer/src/lib/javascript/functions.ts b/packages/labs/analyzer/src/lib/javascript/functions.ts index fc14a7ac99..c70d19ae1c 100644 --- a/packages/labs/analyzer/src/lib/javascript/functions.ts +++ b/packages/labs/analyzer/src/lib/javascript/functions.ts @@ -53,14 +53,23 @@ export const getFunctionDeclarationInfo = ( }; }; -const getFunctionDeclaration = ( +/** + * Returns an analyzer `FunctionDeclaration` model for the given + * ts.FunctionLikeDeclaration. + * + * Note, the `docNode` may differ from the `declaration` in the case of a const + * assignment to a class expression, as the JSDoc will be attached to the + * VariableStatement rather than the class-like expression. + */ +export const getFunctionDeclaration = ( declaration: ts.FunctionLikeDeclaration, name: string, - analyzer: AnalyzerInterface + analyzer: AnalyzerInterface, + docNode?: ts.Node ): FunctionDeclaration => { return new FunctionDeclaration({ name, - ...parseNodeJSDocInfo(declaration), + ...parseNodeJSDocInfo(docNode ?? declaration), ...getFunctionLikeInfo(declaration, analyzer), }); }; diff --git a/packages/labs/analyzer/src/lib/javascript/jsdoc.ts b/packages/labs/analyzer/src/lib/javascript/jsdoc.ts index 01730dfdf9..976174e014 100644 --- a/packages/labs/analyzer/src/lib/javascript/jsdoc.ts +++ b/packages/labs/analyzer/src/lib/javascript/jsdoc.ts @@ -239,20 +239,22 @@ const getModuleJSDocs = (sourceFile: ts.SourceFile) => { */ export const parseNodeJSDocInfo = (node: ts.Node): DeprecatableDescribed => { const info: DeprecatableDescribed = {}; - const jsDocTags = ts.getJSDocTags(node); + const moduleJSDocs = getModuleJSDocs(node.getSourceFile()); + // Module-level docs (that are explicitly tagged as such) may be + // attached to the first declaration if the declaration is undocumented, + // so we filter those out since they shouldn't apply to a + // declaration node + const jsDocTags = ts + .getJSDocTags(node) + .filter(({parent}) => !moduleJSDocs.includes(parent as ts.JSDoc)); if (jsDocTags !== undefined) { addJSDocTagInfo(info, jsDocTags); } if (info.description === undefined) { - const moduleJSDocs = getModuleJSDocs(node.getSourceFile()); const comment = normalizeLineEndings( node .getChildren() .filter(ts.isJSDoc) - // Module-level docs (that are explicitly tagged as such) may be - // attached to the first declaration if the declaration is undocumented, - // so we filter those out since they shouldn't apply to a - // declaration node .filter((c) => !moduleJSDocs.includes(c)) .map((n) => n.comment) .filter((c) => c !== undefined) diff --git a/packages/labs/analyzer/src/lib/javascript/variables.ts b/packages/labs/analyzer/src/lib/javascript/variables.ts index e43b0d903e..81e8c2c023 100644 --- a/packages/labs/analyzer/src/lib/javascript/variables.ts +++ b/packages/labs/analyzer/src/lib/javascript/variables.ts @@ -15,12 +15,14 @@ import { VariableDeclaration, AnalyzerInterface, DeclarationInfo, - DeprecatableDescribed, + Declaration, } from '../model.js'; import {hasExportModifier} from '../utils.js'; import {DiagnosticsError} from '../errors.js'; import {getTypeForNode} from '../types.js'; import {parseNodeJSDocInfo} from './jsdoc.js'; +import {getFunctionDeclaration} from './functions.js'; +import {getClassDeclaration} from './classes.js'; type VariableName = | ts.Identifier @@ -32,16 +34,45 @@ type VariableName = * ts.Identifier within a potentially nested ts.VariableDeclaration. */ const getVariableDeclaration = ( - dec: ts.VariableDeclaration | ts.EnumDeclaration, + statement: ts.VariableStatement, + dec: ts.VariableDeclaration, name: ts.Identifier, - jsDocInfo: DeprecatableDescribed, analyzer: AnalyzerInterface -): VariableDeclaration => { +): Declaration => { + // For const variable declarations initialized to functions or classes, we + // treat these as FunctionDeclaration and ClassDeclaration, respectively since + // they are (mostly) unobservably different to the module consumer and we can + // give better docs this way + if ( + ts.isVariableDeclaration(dec) && + Boolean(statement.declarationList.flags & ts.NodeFlags.Const) && + dec.initializer !== undefined + ) { + const {initializer} = dec; + if ( + ts.isArrowFunction(initializer) || + ts.isFunctionExpression(initializer) + ) { + return getFunctionDeclaration( + initializer, + name.getText(), + analyzer, + statement + ); + } else if (ts.isClassExpression(initializer)) { + return getClassDeclaration( + initializer, + name.getText(), + analyzer, + statement + ); + } + } return new VariableDeclaration({ name: name.text, node: dec, type: getTypeForNode(name, analyzer), - ...jsDocInfo, + ...parseNodeJSDocInfo(statement), }); }; @@ -54,10 +85,10 @@ export const getVariableDeclarationInfo = ( analyzer: AnalyzerInterface ): DeclarationInfo[] => { const isExport = hasExportModifier(statement); - const jsDocInfo = parseNodeJSDocInfo(statement); - return statement.declarationList.declarations + const {declarationList} = statement; + return declarationList.declarations .map((d) => - getVariableDeclarationInfoList(d, d.name, isExport, jsDocInfo, analyzer) + getVariableDeclarationInfoList(statement, d, d.name, isExport, analyzer) ) .flat(); }; @@ -68,17 +99,17 @@ export const getVariableDeclarationInfo = ( * tuples of name and factory for each declaration. */ const getVariableDeclarationInfoList = ( + statement: ts.VariableStatement, dec: ts.VariableDeclaration, name: VariableName, isExport: boolean, - jsDocInfo: DeprecatableDescribed, analyzer: AnalyzerInterface ): DeclarationInfo[] => { if (ts.isIdentifier(name)) { return [ { name: name.text, - factory: () => getVariableDeclaration(dec, name, jsDocInfo, analyzer), + factory: () => getVariableDeclaration(statement, dec, name, analyzer), isExport, }, ]; @@ -94,10 +125,10 @@ const getVariableDeclarationInfoList = ( return els .map((el) => getVariableDeclarationInfoList( + statement, dec, el.name, isExport, - jsDocInfo, analyzer ) ) @@ -146,14 +177,24 @@ const getExportAssignmentVariableDeclaration = ( }; export const getEnumDeclarationInfo = ( - statement: ts.EnumDeclaration, + dec: ts.EnumDeclaration, analyzer: AnalyzerInterface ) => { - const jsDocInfo = parseNodeJSDocInfo(statement); return { - name: statement.name.text, - factory: () => - getVariableDeclaration(statement, statement.name, jsDocInfo, analyzer), - isExport: hasExportModifier(statement), + name: dec.name.text, + factory: () => getEnumDeclaration(dec, analyzer), + isExport: hasExportModifier(dec), }; }; + +const getEnumDeclaration = ( + dec: ts.EnumDeclaration, + analyzer: AnalyzerInterface +) => { + return new VariableDeclaration({ + name: dec.name.text, + node: dec, + type: getTypeForNode(dec.name, analyzer), + ...parseNodeJSDocInfo(dec), + }); +}; diff --git a/packages/labs/analyzer/src/lib/model.ts b/packages/labs/analyzer/src/lib/model.ts index 642a9dc2ce..eaa76ea218 100644 --- a/packages/labs/analyzer/src/lib/model.ts +++ b/packages/labs/analyzer/src/lib/model.ts @@ -400,7 +400,7 @@ export type ClassHeritage = { }; export interface ClassDeclarationInit extends DeclarationInit { - node: ts.ClassDeclaration; + node: ts.ClassLikeDeclaration; getHeritage: () => ClassHeritage; fieldMap?: Map | undefined; staticFieldMap?: Map | undefined; @@ -409,7 +409,7 @@ export interface ClassDeclarationInit extends DeclarationInit { } export class ClassDeclaration extends Declaration { - readonly node: ts.ClassDeclaration; + readonly node: ts.ClassLikeDeclaration; private _getHeritage: () => ClassHeritage; private _heritage: ClassHeritage | undefined = undefined; readonly _fieldMap: Map; diff --git a/packages/labs/analyzer/src/test/javascript/classes_test.ts b/packages/labs/analyzer/src/test/javascript/classes_test.ts new file mode 100644 index 0000000000..4927fe9bfa --- /dev/null +++ b/packages/labs/analyzer/src/test/javascript/classes_test.ts @@ -0,0 +1,264 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + +import {suite} from 'uvu'; +// eslint-disable-next-line import/extensions +import * as assert from 'uvu/assert'; +import { + AnalyzerTestContext, + languages, + setupAnalyzerForTest, +} from '../utils.js'; + +for (const lang of languages) { + const test = suite(`Class tests (${lang})`); + + test.before((ctx) => { + setupAnalyzerForTest(ctx, lang, 'classes'); + }); + + // Class description, summary, deprecated + + test('tagged description and summary', ({getModule}) => { + const dec = getModule('classes').getDeclaration('TaggedDescription'); + assert.ok(dec.isClassDeclaration()); + assert.equal( + dec.description, + `TaggedDescription description. Lorem ipsum dolor sit amet, consectetur +adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna +aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris +nisi ut aliquip ex ea commodo consequat.` + ); + assert.equal(dec.summary, `TaggedDescription summary.`); + assert.equal(dec.deprecated, `TaggedDescription deprecated message.`); + }); + + test('untagged description', ({getModule}) => { + const dec = getModule('classes').getDeclaration('UntaggedDescription'); + assert.ok(dec.isClassDeclaration()); + assert.equal( + dec.description, + `UntaggedDescription description. Lorem ipsum dolor sit amet, consectetur +adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna +aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris +nisi ut aliquip ex ea commodo consequat.` + ); + assert.equal(dec.summary, `UntaggedDescription summary.`); + assert.equal(dec.deprecated, `UntaggedDescription deprecated message.`); + }); + + // Fields + + test('field1', ({getModule}) => { + const dec = getModule('classes').getDeclaration('Class1'); + assert.ok(dec.isClassDeclaration()); + const member = dec.getField('field1'); + assert.ok(member?.isClassField()); + assert.equal( + member.description, + `Class field 1 description\nwith wraparound` + ); + assert.equal(member.default, `'default1'`); + assert.equal(member.privacy, 'private'); + assert.equal(member.type?.text, 'string'); + }); + + test('field2', ({getModule}) => { + const dec = getModule('classes').getDeclaration('Class1'); + assert.ok(dec.isClassDeclaration()); + const member = dec.getField('field2'); + assert.ok(member?.isClassField()); + assert.equal(member.summary, `Class field 2 summary\nwith wraparound`); + assert.equal( + member.description, + `Class field 2 description\nwith wraparound` + ); + assert.equal(member.default, undefined); + assert.equal(member.privacy, 'protected'); + assert.equal(member.type?.text, 'string | number'); + }); + + test('field3', ({getModule}) => { + const dec = getModule('classes').getDeclaration('Class1'); + assert.ok(dec.isClassDeclaration()); + const member = dec.getField('field3'); + assert.ok(member?.isClassField()); + assert.equal( + member.description, + `Class field 3 description\nwith wraparound` + ); + assert.equal(member.default, undefined); + assert.equal(member.privacy, 'public'); + assert.equal(member.type?.text, 'string'); + assert.equal(member.deprecated, true); + }); + + test('field4', ({getModule}) => { + const dec = getModule('classes').getDeclaration('Class1'); + assert.ok(dec.isClassDeclaration()); + const member = dec.getField('field4'); + assert.ok(member?.isClassField()); + assert.equal(member.summary, `Class field 4 summary\nwith wraparound`); + assert.equal( + member.description, + `Class field 4 description\nwith wraparound` + ); + assert.equal( + member.default, + `new Promise${lang === 'ts' ? '' : ''}((r) => r())` + ); + assert.equal(member.type?.text, 'Promise'); + assert.equal(member.deprecated, 'Class field 4 deprecated'); + }); + + test('static field1', ({getModule}) => { + const dec = getModule('classes').getDeclaration('Class1'); + assert.ok(dec.isClassDeclaration()); + const member = dec.getStaticField('field1'); + assert.ok(member?.isClassField()); + assert.equal(member.summary, `Static class field 1 summary`); + assert.equal(member.description, `Static class field 1 description`); + assert.equal(member.default, undefined); + assert.equal(member.privacy, 'protected'); + assert.equal(member.type?.text, 'string | number'); + }); + + // Methods + + test('method1', ({getModule}) => { + const dec = getModule('classes').getDeclaration('Class1'); + assert.ok(dec.isClassDeclaration()); + const member = dec.getMethod('method1'); + assert.ok(member?.isClassMethod()); + assert.equal(member.description, `Method 1 description\nwith wraparound`); + assert.equal(member.parameters?.length, 0); + assert.equal(member.return?.type?.text, 'void'); + }); + + test('method2', ({getModule}) => { + const dec = getModule('classes').getDeclaration('Class1'); + assert.ok(dec.isClassDeclaration()); + const member = dec.getMethod('method2'); + assert.ok(member?.isClassMethod()); + assert.equal(member.summary, `Method 2 summary\nwith wraparound`); + assert.equal(member.description, `Method 2 description\nwith wraparound`); + assert.equal(member.parameters?.length, 3); + assert.equal(member.parameters?.[0].name, 'a'); + assert.equal(member.parameters?.[0].description, 'Param a description'); + assert.equal(member.parameters?.[0].summary, undefined); + assert.equal(member.parameters?.[0].type?.text, 'string'); + assert.equal(member.parameters?.[0].default, undefined); + assert.equal(member.parameters?.[0].rest, false); + assert.equal(member.parameters?.[1].name, 'b'); + assert.equal( + member.parameters?.[1].description, + 'Param b description\nwith wraparound' + ); + assert.equal(member.parameters?.[1].type?.text, 'boolean'); + assert.equal(member.parameters?.[1].optional, true); + assert.equal(member.parameters?.[1].default, 'false'); + assert.equal(member.parameters?.[1].rest, false); + assert.equal(member.parameters?.[2].name, 'c'); + assert.equal(member.parameters?.[2].description, 'Param c description'); + assert.equal(member.parameters?.[2].summary, undefined); + assert.equal(member.parameters?.[2].type?.text, 'number[]'); + assert.equal(member.parameters?.[2].optional, false); + assert.equal(member.parameters?.[2].default, undefined); + assert.equal(member.parameters?.[2].rest, true); + assert.equal(member.return?.type?.text, 'string'); + assert.equal(member.return?.description, 'Method 2 return description'); + assert.equal(member.deprecated, 'Method 2 deprecated'); + }); + + test('static method1', ({getModule}) => { + const dec = getModule('classes').getDeclaration('Class1'); + assert.ok(dec.isClassDeclaration()); + const member = dec.getStaticMethod('method1'); + assert.ok(member?.isClassMethod()); + assert.equal(member.summary, `Static method 1 summary`); + assert.equal(member.description, `Static method 1 description`); + assert.equal(member.parameters?.length, 3); + assert.equal(member.parameters?.[0].name, 'a'); + assert.equal(member.parameters?.[0].description, 'Param a description'); + assert.equal(member.parameters?.[0].summary, undefined); + assert.equal(member.parameters?.[0].type?.text, 'string'); + assert.equal(member.parameters?.[0].default, undefined); + assert.equal(member.parameters?.[0].rest, false); + assert.equal(member.parameters?.[1].name, 'b'); + assert.equal(member.parameters?.[1].description, 'Param b description'); + assert.equal(member.parameters?.[1].type?.text, 'boolean'); + assert.equal(member.parameters?.[1].optional, true); + assert.equal(member.parameters?.[1].default, 'false'); + assert.equal(member.parameters?.[1].rest, false); + assert.equal(member.parameters?.[2].name, 'c'); + assert.equal(member.parameters?.[2].description, 'Param c description'); + assert.equal(member.parameters?.[2].summary, undefined); + assert.equal(member.parameters?.[2].type?.text, 'number[]'); + assert.equal(member.parameters?.[2].optional, false); + assert.equal(member.parameters?.[2].default, undefined); + assert.equal(member.parameters?.[2].rest, true); + assert.equal(member.return?.type?.text, 'string'); + assert.equal( + member.return?.description, + 'Static method 1 return description' + ); + assert.equal(member.deprecated, 'Static method 1 deprecated'); + }); + + test('superClass', ({getModule}) => { + const dec = getModule('classes').getDeclaration('Class1'); + assert.ok(dec.isClassDeclaration()); + const superClassRef = dec.heritage.superClass; + assert.ok(superClassRef); + assert.equal(superClassRef.package, '@lit-internal/test-classes'); + assert.equal(superClassRef.module, 'classes.js'); + assert.equal(superClassRef.name, 'BaseClass'); + const superClass = superClassRef.dereference(); + assert.ok(superClass.isClassDeclaration()); + assert.equal(superClass.name, 'BaseClass'); + }); + + test('ConstClass', ({getModule}) => { + const dec = getModule('classes').getDeclaration('ConstClass'); + assert.equal(dec.name, 'ConstClass'); + assert.equal(dec.description, 'ConstClass description'); + assert.ok(dec.isClassDeclaration()); + const field = dec.getField('field1'); + assert.ok(field?.isClassField()); + assert.equal(field.description, 'ConstClass field 1 description'); + const method = dec.getMethod('method1'); + assert.ok(method?.isClassMethod()); + assert.equal(method.description, 'ConstClass method 1 description'); + }); + + test('ConstClassNoName', ({getModule}) => { + const dec = getModule('classes').getDeclaration('ConstClassNoName'); + assert.equal(dec.name, 'ConstClassNoName'); + assert.equal(dec.description, 'ConstClassNoName description'); + assert.ok(dec.isClassDeclaration()); + const field = dec.getField('field1'); + assert.ok(field?.isClassField()); + assert.equal(field.description, 'ConstClassNoName field 1 description'); + const method = dec.getMethod('method1'); + assert.ok(method?.isClassMethod()); + assert.equal(method.description, 'ConstClassNoName method 1 description'); + }); + + test('default class', ({getModule}) => { + const dec = getModule('classes').getDeclaration('default'); + assert.equal(dec.name, 'default'); + assert.equal(dec.description, 'default class description'); + assert.ok(dec.isClassDeclaration()); + const field = dec.getField('field1'); + assert.ok(field?.isClassField()); + assert.equal(field.description, 'default class field 1 description'); + const method = dec.getMethod('method1'); + assert.ok(method?.isClassMethod()); + assert.equal(method.description, 'default class method 1 description'); + }); + + test.run(); +} diff --git a/packages/labs/analyzer/src/test/javascript/functions_test.ts b/packages/labs/analyzer/src/test/javascript/functions_test.ts index d1134c550f..da4aacdd22 100644 --- a/packages/labs/analyzer/src/test/javascript/functions_test.ts +++ b/packages/labs/analyzer/src/test/javascript/functions_test.ts @@ -82,5 +82,102 @@ for (const lang of languages) { assert.equal(fn.deprecated, undefined); }); + test('Const function', ({module}) => { + const exportedFn = module.getResolvedExport('constFunction'); + const fn = module.getDeclaration('constFunction'); + assert.equal(fn, exportedFn); + assert.ok(fn?.isFunctionDeclaration()); + assert.equal(fn.name, `constFunction`); + assert.equal(fn.summary, `Const function summary\nwith wraparound`); + assert.equal(fn.description, `Const function description\nwith wraparound`); + assert.equal(fn.parameters?.length, 3); + assert.equal(fn.parameters?.[0].name, 'a'); + assert.equal(fn.parameters?.[0].description, 'Param a description'); + assert.equal(fn.parameters?.[0].summary, undefined); + assert.equal(fn.parameters?.[0].type?.text, 'string'); + assert.equal(fn.parameters?.[0].default, undefined); + assert.equal(fn.parameters?.[0].rest, false); + assert.equal(fn.parameters?.[1].name, 'b'); + assert.equal( + fn.parameters?.[1].description, + 'Param b description\nwith wraparound' + ); + assert.equal(fn.parameters?.[1].type?.text, 'boolean'); + assert.equal(fn.parameters?.[1].optional, true); + assert.equal(fn.parameters?.[1].default, 'false'); + assert.equal(fn.parameters?.[1].rest, false); + assert.equal(fn.parameters?.[2].name, 'c'); + assert.equal(fn.parameters?.[2].description, 'Param c description'); + assert.equal(fn.parameters?.[2].summary, undefined); + assert.equal(fn.parameters?.[2].type?.text, 'number[]'); + assert.equal(fn.parameters?.[2].optional, false); + assert.equal(fn.parameters?.[2].default, undefined); + assert.equal(fn.parameters?.[2].rest, true); + assert.equal(fn.return?.type?.text, 'string'); + assert.equal(fn.return?.description, 'Const function return description'); + assert.equal(fn.deprecated, 'Const function deprecated'); + }); + + test('Const arrow function', ({module}) => { + const exportedFn = module.getResolvedExport('constArrowFunction'); + const fn = module.getDeclaration('constArrowFunction'); + assert.equal(fn, exportedFn); + assert.ok(fn?.isFunctionDeclaration()); + assert.equal(fn.name, `constArrowFunction`); + assert.equal(fn.summary, `Const arrow function summary\nwith wraparound`); + assert.equal( + fn.description, + `Const arrow function description\nwith wraparound` + ); + assert.equal(fn.parameters?.length, 3); + assert.equal(fn.parameters?.[0].name, 'a'); + assert.equal(fn.parameters?.[0].description, 'Param a description'); + assert.equal(fn.parameters?.[0].summary, undefined); + assert.equal(fn.parameters?.[0].type?.text, 'string'); + assert.equal(fn.parameters?.[0].default, undefined); + assert.equal(fn.parameters?.[0].rest, false); + assert.equal(fn.parameters?.[1].name, 'b'); + assert.equal( + fn.parameters?.[1].description, + 'Param b description\nwith wraparound' + ); + assert.equal(fn.parameters?.[1].type?.text, 'boolean'); + assert.equal(fn.parameters?.[1].optional, true); + assert.equal(fn.parameters?.[1].default, 'false'); + assert.equal(fn.parameters?.[1].rest, false); + assert.equal(fn.parameters?.[2].name, 'c'); + assert.equal(fn.parameters?.[2].description, 'Param c description'); + assert.equal(fn.parameters?.[2].summary, undefined); + assert.equal(fn.parameters?.[2].type?.text, 'number[]'); + assert.equal(fn.parameters?.[2].optional, false); + assert.equal(fn.parameters?.[2].default, undefined); + assert.equal(fn.parameters?.[2].rest, true); + assert.equal(fn.return?.type?.text, 'string'); + assert.equal( + fn.return?.description, + 'Const arrow function return description' + ); + assert.equal(fn.deprecated, 'Const arrow function deprecated'); + }); + + test('Async function', ({module}) => { + const exportedFn = module.getResolvedExport('asyncFunction'); + const fn = module.getDeclaration('asyncFunction'); + assert.equal(fn, exportedFn); + assert.ok(fn?.isFunctionDeclaration()); + assert.equal(fn.name, `asyncFunction`); + assert.equal(fn.description, `Async function description`); + assert.equal(fn.summary, undefined); + assert.equal(fn.parameters?.[0].name, 'a'); + assert.equal(fn.parameters?.[0].description, 'Param a description'); + assert.equal(fn.parameters?.[0].summary, undefined); + assert.equal(fn.parameters?.[0].type?.text, 'string'); + assert.equal(fn.parameters?.[0].default, undefined); + assert.equal(fn.parameters?.[0].rest, false); + assert.equal(fn.return?.type?.text, 'Promise'); + assert.equal(fn.return?.description, 'Async function return description'); + assert.equal(fn.deprecated, 'Async function deprecated'); + }); + test.run(); } diff --git a/packages/labs/analyzer/src/test/javascript/modules_test.ts b/packages/labs/analyzer/src/test/javascript/modules_test.ts index e4a6d60321..5b68903899 100644 --- a/packages/labs/analyzer/src/test/javascript/modules_test.ts +++ b/packages/labs/analyzer/src/test/javascript/modules_test.ts @@ -210,4 +210,293 @@ for (const lang of languages) { ); cachingTest.run(); + + // Doing module JSDoc tests in-memory, to test a number of variations + // without needing to maintain a file for each. + + for (const hasFirstStatementDoc of [false, true]) { + const moduleTest = suite<{ + analyzer: InMemoryAnalyzer; + }>( + `Module jsDoc tests, ${ + hasFirstStatementDoc ? 'has' : 'no' + } first statement docs (${lang})` + ); + + moduleTest.before.each((ctx) => { + ctx.analyzer = new InMemoryAnalyzer(lang, { + '/package.json': JSON.stringify({name: '@lit-internal/in-memory-test'}), + }); + }); + + const firstStatementDoc = hasFirstStatementDoc + ? ` + /** + * First statement description + * @summary First statement summary + */ + ` + : ''; + + moduleTest('untagged module description with @module tag', ({analyzer}) => { + analyzer.setFile( + '/module', + ` + /** + * Module description + * more description + * @module + */ + ${firstStatementDoc} + export const foo = 42; + ` + ); + const module = analyzer.getModule( + getSourceFilename('/module', lang) as AbsolutePath + ); + assert.equal(module.description, 'Module description\nmore description'); + }); + + moduleTest( + 'untagged module description with @fileoverview tag', + ({analyzer}) => { + analyzer.setFile( + '/module', + ` + /** + * Module description + * more description + * @fileoverview + */ + ${firstStatementDoc} + export const foo = 42; + ` + ); + const module = analyzer.getModule( + getSourceFilename('/module', lang) as AbsolutePath + ); + assert.equal( + module.description, + 'Module description\nmore description' + ); + assert.equal( + module.getDeclaration('foo').description, + hasFirstStatementDoc ? 'First statement description' : undefined + ); + } + ); + + moduleTest('module description in @fileoverview tag', ({analyzer}) => { + analyzer.setFile( + '/module', + ` + /** + * @fileoverview Module description + * more description + */ + ${firstStatementDoc} + export const foo = 42; + ` + ); + const module = analyzer.getModule( + getSourceFilename('/module', lang) as AbsolutePath + ); + assert.equal(module.description, 'Module description\nmore description'); + assert.equal( + module.getDeclaration('foo').description, + hasFirstStatementDoc ? 'First statement description' : undefined + ); + }); + + moduleTest( + 'untagged module description with @packageDocumentation tag', + ({analyzer}) => { + analyzer.setFile( + '/module', + ` + /** + * Module description + * more description + * @packageDocumentation + */ + ${firstStatementDoc} + export const foo = 42; + ` + ); + const module = analyzer.getModule( + getSourceFilename('/module', lang) as AbsolutePath + ); + assert.equal( + module.description, + 'Module description\nmore description' + ); + assert.equal( + module.getDeclaration('foo').description, + hasFirstStatementDoc ? 'First statement description' : undefined + ); + } + ); + + moduleTest( + 'module description in @packageDocumentation tag', + ({analyzer}) => { + analyzer.setFile( + '/module', + ` + /** + * @packageDocumentation Module description + * more description + */ + ${firstStatementDoc} + export const foo = 42; + ` + ); + const module = analyzer.getModule( + getSourceFilename('/module', lang) as AbsolutePath + ); + assert.equal( + module.description, + 'Module description\nmore description' + ); + assert.equal( + module.getDeclaration('foo').description, + hasFirstStatementDoc ? 'First statement description' : undefined + ); + } + ); + + moduleTest( + 'module description in @packageDocumentation tag with other tags', + ({analyzer}) => { + analyzer.setFile( + '/module', + ` + /** + * @packageDocumentation Module description + * more description + * @module foo + * @deprecated Module is deprecated + */ + ${firstStatementDoc} + export const foo = 42; + ` + ); + const module = analyzer.getModule( + getSourceFilename('/module', lang) as AbsolutePath + ); + assert.equal( + module.description, + 'Module description\nmore description' + ); + assert.equal(module.deprecated, 'Module is deprecated'); + assert.equal( + module.getDeclaration('foo').description, + hasFirstStatementDoc ? 'First statement description' : undefined + ); + } + ); + + moduleTest('untagged module description', ({analyzer}) => { + analyzer.setFile( + '/module', + ` + /** + * Module description + * more module description + * @summary Module summary + * @deprecated + */ + /** + * First statement description + * @summary First statement summary + */ + export const foo = 42; + ` + ); + const module = analyzer.getModule( + getSourceFilename('/module', lang) as AbsolutePath + ); + assert.equal( + module.description, + 'Module description\nmore module description' + ); + assert.equal(module.summary, 'Module summary'); + assert.equal(module.deprecated, true); + assert.equal( + module.getDeclaration('foo').description, + 'First statement description' + ); + }); + + moduleTest('multiple untagged module descriptions', ({analyzer}) => { + analyzer.setFile( + '/module', + ` + /** + * Module description + * more module description + */ + /** + * Even more module description + */ + /** + * First statement description + * @summary First statement summary + */ + export const foo = 42; + ` + ); + const module = analyzer.getModule( + getSourceFilename('/module', lang) as AbsolutePath + ); + assert.equal( + module.description, + 'Module description\nmore module description\nEven more module description' + ); + assert.equal( + module.getDeclaration('foo').description, + 'First statement description' + ); + }); + + moduleTest( + 'multiple untagged module descriptions with other tags', + ({analyzer}) => { + analyzer.setFile( + '/module', + ` + /** + * Module description + * more module description + * @deprecated + */ + /** + * Even more module description + * @summary Module summary + */ + /** + * First statement description + * @summary First statement summary + */ + export const foo = 42; + ` + ); + const module = analyzer.getModule( + getSourceFilename('/module', lang) as AbsolutePath + ); + assert.equal( + module.description, + 'Module description\nmore module description\nEven more module description' + ); + assert.equal(module.summary, 'Module summary'); + assert.equal(module.deprecated, true); + assert.equal( + module.getDeclaration('foo').description, + 'First statement description' + ); + } + ); + + moduleTest.run(); + } } diff --git a/packages/labs/analyzer/src/test/lit-element/jsdoc_test.ts b/packages/labs/analyzer/src/test/lit-element/jsdoc_test.ts index 1c2838c32c..9c8c8d2adb 100644 --- a/packages/labs/analyzer/src/test/lit-element/jsdoc_test.ts +++ b/packages/labs/analyzer/src/test/lit-element/jsdoc_test.ts @@ -9,14 +9,10 @@ import {suite} from 'uvu'; import * as assert from 'uvu/assert'; import { AnalyzerTestContext, - getSourceFilename, - InMemoryAnalyzer, languages, setupAnalyzerForTest, } from '../utils.js'; -import {AbsolutePath} from '../../index.js'; - for (const lang of languages) { const test = suite(`JSDoc tests (${lang})`); @@ -174,452 +170,20 @@ for (const lang of languages) { ); }); - // Class description, summary, deprecated - - test('tagged description and summary', ({getModule}) => { - const element = getModule('element-a').getDeclaration('TaggedDescription'); - assert.ok(element.isLitElementDeclaration()); - assert.equal( - element.description, - `TaggedDescription description. Lorem ipsum dolor sit amet, consectetur -adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna -aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris -nisi ut aliquip ex ea commodo consequat.` - ); - assert.equal(element.summary, `TaggedDescription summary.`); - assert.equal(element.deprecated, `TaggedDescription deprecated message.`); - }); - - test('untagged description', ({getModule}) => { - const element = getModule('element-a').getDeclaration( - 'UntaggedDescription' - ); - assert.ok(element.isLitElementDeclaration()); - assert.equal( - element.description, - `UntaggedDescription description. Lorem ipsum dolor sit amet, consectetur -adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna -aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris -nisi ut aliquip ex ea commodo consequat.` - ); - assert.equal(element.summary, `UntaggedDescription summary.`); - assert.equal(element.deprecated, `UntaggedDescription deprecated message.`); - }); - - // Fields - - test('field1', ({getModule}) => { + test('basic class analysis', ({getModule}) => { const element = getModule('element-a').getDeclaration('ElementA'); assert.ok(element.isClassDeclaration()); - const member = element.getField('field1'); - assert.ok(member?.isClassField()); - assert.equal( - member.description, - `Class field 1 description\nwith wraparound` - ); - assert.equal(member.default, `'default1'`); - assert.equal(member.privacy, 'private'); - assert.equal(member.type?.text, 'string'); - }); - - test('field2', ({getModule}) => { - const element = getModule('element-a').getDeclaration('ElementA'); - assert.ok(element.isClassDeclaration()); - const member = element.getField('field2'); - assert.ok(member?.isClassField()); - assert.equal(member.summary, `Class field 2 summary\nwith wraparound`); - assert.equal( - member.description, - `Class field 2 description\nwith wraparound` - ); - assert.equal(member.default, undefined); - assert.equal(member.privacy, 'protected'); - assert.equal(member.type?.text, 'string | number'); - }); - - test('field3', ({getModule}) => { - const element = getModule('element-a').getDeclaration('ElementA'); - assert.ok(element.isClassDeclaration()); - const member = element.getField('field3'); - assert.ok(member?.isClassField()); - assert.equal( - member.description, - `Class field 3 description\nwith wraparound` - ); - assert.equal(member.default, undefined); - assert.equal(member.privacy, 'public'); - assert.equal(member.type?.text, 'string'); - assert.equal(member.deprecated, true); - }); - - test('field4', ({getModule}) => { - const element = getModule('element-a').getDeclaration('ElementA'); - assert.ok(element.isClassDeclaration()); - const member = element.getField('field4'); - assert.ok(member?.isClassField()); - assert.equal(member.summary, `Class field 4 summary\nwith wraparound`); - assert.equal( - member.description, - `Class field 4 description\nwith wraparound` - ); - assert.equal( - member.default, - `new Promise${lang === 'ts' ? '' : ''}((r) => r())` - ); - assert.equal(member.type?.text, 'Promise'); - assert.equal(member.deprecated, 'Class field 4 deprecated'); - }); - - test('static field1', ({getModule}) => { - const element = getModule('element-a').getDeclaration('ElementA'); - assert.ok(element.isClassDeclaration()); - const member = element.getStaticField('field1'); - assert.ok(member?.isClassField()); - assert.equal(member.summary, `Static class field 1 summary`); - assert.equal(member.description, `Static class field 1 description`); - assert.equal(member.default, undefined); - assert.equal(member.privacy, 'protected'); - assert.equal(member.type?.text, 'string | number'); - }); - - // Methods - - test('method1', ({getModule}) => { - const element = getModule('element-a').getDeclaration('ElementA'); - assert.ok(element.isClassDeclaration()); - const member = element.getMethod('method1'); - assert.ok(member?.isClassMethod()); - assert.equal(member.description, `Method 1 description\nwith wraparound`); - assert.equal(member.parameters?.length, 0); - assert.equal(member.return?.type?.text, 'void'); - }); - - test('method2', ({getModule}) => { - const element = getModule('element-a').getDeclaration('ElementA'); - assert.ok(element.isClassDeclaration()); - const member = element.getMethod('method2'); - assert.ok(member?.isClassMethod()); - assert.equal(member.summary, `Method 2 summary\nwith wraparound`); - assert.equal(member.description, `Method 2 description\nwith wraparound`); - assert.equal(member.parameters?.length, 3); - assert.equal(member.parameters?.[0].name, 'a'); - assert.equal(member.parameters?.[0].description, 'Param a description'); - assert.equal(member.parameters?.[0].summary, undefined); - assert.equal(member.parameters?.[0].type?.text, 'string'); - assert.equal(member.parameters?.[0].default, undefined); - assert.equal(member.parameters?.[0].rest, false); - assert.equal(member.parameters?.[1].name, 'b'); - assert.equal( - member.parameters?.[1].description, - 'Param b description\nwith wraparound' - ); - assert.equal(member.parameters?.[1].type?.text, 'boolean'); - assert.equal(member.parameters?.[1].optional, true); - assert.equal(member.parameters?.[1].default, 'false'); - assert.equal(member.parameters?.[1].rest, false); - assert.equal(member.parameters?.[2].name, 'c'); - assert.equal(member.parameters?.[2].description, 'Param c description'); - assert.equal(member.parameters?.[2].summary, undefined); - assert.equal(member.parameters?.[2].type?.text, 'number[]'); - assert.equal(member.parameters?.[2].optional, false); - assert.equal(member.parameters?.[2].default, undefined); - assert.equal(member.parameters?.[2].rest, true); - assert.equal(member.return?.type?.text, 'string'); - assert.equal(member.return?.description, 'Method 2 return description'); - assert.equal(member.deprecated, 'Method 2 deprecated'); - }); - - test('static method1', ({getModule}) => { - const element = getModule('element-a').getDeclaration('ElementA'); - assert.ok(element.isClassDeclaration()); - const member = element.getStaticMethod('method1'); - assert.ok(member?.isClassMethod()); - assert.equal(member.summary, `Static method 1 summary`); - assert.equal(member.description, `Static method 1 description`); - assert.equal(member.parameters?.length, 3); - assert.equal(member.parameters?.[0].name, 'a'); - assert.equal(member.parameters?.[0].description, 'Param a description'); - assert.equal(member.parameters?.[0].summary, undefined); - assert.equal(member.parameters?.[0].type?.text, 'string'); - assert.equal(member.parameters?.[0].default, undefined); - assert.equal(member.parameters?.[0].rest, false); - assert.equal(member.parameters?.[1].name, 'b'); - assert.equal(member.parameters?.[1].description, 'Param b description'); - assert.equal(member.parameters?.[1].type?.text, 'boolean'); - assert.equal(member.parameters?.[1].optional, true); - assert.equal(member.parameters?.[1].default, 'false'); - assert.equal(member.parameters?.[1].rest, false); - assert.equal(member.parameters?.[2].name, 'c'); - assert.equal(member.parameters?.[2].description, 'Param c description'); - assert.equal(member.parameters?.[2].summary, undefined); - assert.equal(member.parameters?.[2].type?.text, 'number[]'); - assert.equal(member.parameters?.[2].optional, false); - assert.equal(member.parameters?.[2].default, undefined); - assert.equal(member.parameters?.[2].rest, true); - assert.equal(member.return?.type?.text, 'string'); - assert.equal( - member.return?.description, - 'Static method 1 return description' - ); - assert.equal(member.deprecated, 'Static method 1 deprecated'); + assert.equal(element.description, 'A cool custom element.'); + const field = element.getField('field1'); + assert.ok(field?.isClassField()); + assert.equal(field.description, `Class field 1 description`); + assert.equal(field.type?.text, 'string'); + const method = element.getMethod('method1'); + assert.ok(method?.isClassMethod()); + assert.equal(method.description, `Method 1 description`); + assert.equal(method.parameters?.length, 0); + assert.equal(method.return?.type?.text, 'void'); }); test.run(); - - // Doing module JSDoc tests in-memory, to test a number of variations - // without needing to maintain a file for each. - - for (const hasFirstStatementDoc of [false, true]) { - const moduleTest = suite<{ - analyzer: InMemoryAnalyzer; - }>( - `Module jsDoc tests, ${ - hasFirstStatementDoc ? 'has' : 'no' - } first statement docs (${lang})` - ); - - moduleTest.before.each((ctx) => { - ctx.analyzer = new InMemoryAnalyzer(lang, { - '/package.json': JSON.stringify({name: '@lit-internal/in-memory-test'}), - }); - }); - - const firstStatementDoc = hasFirstStatementDoc - ? ` - /** - * First statement description - * @summary First statement summary - */ - ` - : ''; - - moduleTest('untagged module description with @module tag', ({analyzer}) => { - analyzer.setFile( - '/module', - ` - /** - * Module description - * more description - * @module - */ - ${firstStatementDoc} - export const foo = 42; - ` - ); - const module = analyzer.getModule( - getSourceFilename('/module', lang) as AbsolutePath - ); - assert.equal(module.description, 'Module description\nmore description'); - }); - - moduleTest( - 'untagged module description with @fileoverview tag', - ({analyzer}) => { - analyzer.setFile( - '/module', - ` - /** - * Module description - * more description - * @fileoverview - */ - ${firstStatementDoc} - export const foo = 42; - ` - ); - const module = analyzer.getModule( - getSourceFilename('/module', lang) as AbsolutePath - ); - assert.equal( - module.description, - 'Module description\nmore description' - ); - } - ); - - moduleTest('module description in @fileoverview tag', ({analyzer}) => { - analyzer.setFile( - '/module', - ` - /** - * @fileoverview Module description - * more description - */ - ${firstStatementDoc} - export const foo = 42; - ` - ); - const module = analyzer.getModule( - getSourceFilename('/module', lang) as AbsolutePath - ); - assert.equal(module.description, 'Module description\nmore description'); - }); - - moduleTest( - 'untagged module description with @packageDocumentation tag', - ({analyzer}) => { - analyzer.setFile( - '/module', - ` - /** - * Module description - * more description - * @packageDocumentation - */ - ${firstStatementDoc} - export const foo = 42; - ` - ); - const module = analyzer.getModule( - getSourceFilename('/module', lang) as AbsolutePath - ); - assert.equal( - module.description, - 'Module description\nmore description' - ); - } - ); - - moduleTest( - 'module description in @packageDocumentation tag', - ({analyzer}) => { - analyzer.setFile( - '/module', - ` - /** - * @packageDocumentation Module description - * more description - */ - ${firstStatementDoc} - export const foo = 42; - ` - ); - const module = analyzer.getModule( - getSourceFilename('/module', lang) as AbsolutePath - ); - assert.equal( - module.description, - 'Module description\nmore description' - ); - } - ); - - moduleTest( - 'module description in @packageDocumentation tag with other tags', - ({analyzer}) => { - analyzer.setFile( - '/module', - ` - /** - * @packageDocumentation Module description - * more description - * @module foo - * @deprecated Module is deprecated - */ - ${firstStatementDoc} - export const foo = 42; - ` - ); - const module = analyzer.getModule( - getSourceFilename('/module', lang) as AbsolutePath - ); - assert.equal( - module.description, - 'Module description\nmore description' - ); - assert.equal(module.deprecated, 'Module is deprecated'); - } - ); - - moduleTest('untagged module description', ({analyzer}) => { - analyzer.setFile( - '/module', - ` - /** - * Module description - * more module description - * @summary Module summary - * @deprecated - */ - /** - * First statement description - * @summary First statement summary - */ - export const foo = 42; - ` - ); - const module = analyzer.getModule( - getSourceFilename('/module', lang) as AbsolutePath - ); - assert.equal( - module.description, - 'Module description\nmore module description' - ); - assert.equal(module.summary, 'Module summary'); - assert.equal(module.deprecated, true); - }); - - moduleTest('multiple untagged module descriptions', ({analyzer}) => { - analyzer.setFile( - '/module', - ` - /** - * Module description - * more module description - */ - /** - * Even more module description - */ - /** - * First statement description - * @summary First statement summary - */ - export const foo = 42; - ` - ); - const module = analyzer.getModule( - getSourceFilename('/module', lang) as AbsolutePath - ); - assert.equal( - module.description, - 'Module description\nmore module description\nEven more module description' - ); - }); - - moduleTest( - 'multiple untagged module descriptions with other tags', - ({analyzer}) => { - analyzer.setFile( - '/module', - ` - /** - * Module description - * more module description - * @deprecated - */ - /** - * Even more module description - * @summary Module summary - */ - /** - * First statement description - * @summary First statement summary - */ - export const foo = 42; - ` - ); - const module = analyzer.getModule( - getSourceFilename('/module', lang) as AbsolutePath - ); - assert.equal( - module.description, - 'Module description\nmore module description\nEven more module description' - ); - assert.equal(module.summary, 'Module summary'); - assert.equal(module.deprecated, true); - } - ); - - moduleTest.run(); - } } diff --git a/packages/labs/analyzer/src/test/vanilla-element/jsdoc_test.ts b/packages/labs/analyzer/src/test/vanilla-element/jsdoc_test.ts index 9b2d72276e..7b81c2ac2d 100644 --- a/packages/labs/analyzer/src/test/vanilla-element/jsdoc_test.ts +++ b/packages/labs/analyzer/src/test/vanilla-element/jsdoc_test.ts @@ -172,147 +172,19 @@ for (const lang of languages) { ); }); - // Class description, summary, deprecated - - test('tagged description and summary', ({getModule}) => { - const element = getModule('element-a').getDeclaration('TaggedDescription'); - assert.ok(element.isCustomElementDeclaration()); - assert.equal( - element.description, - `TaggedDescription description. Lorem ipsum dolor sit amet, consectetur -adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna -aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris -nisi ut aliquip ex ea commodo consequat.` - ); - assert.equal(element.summary, `TaggedDescription summary.`); - assert.equal(element.deprecated, `TaggedDescription deprecated message.`); - }); - - test('untagged description', ({getModule}) => { - const element = getModule('element-a').getDeclaration( - 'UntaggedDescription' - ); - assert.ok(element.isCustomElementDeclaration()); - assert.equal( - element.description, - `UntaggedDescription description. Lorem ipsum dolor sit amet, consectetur -adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna -aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris -nisi ut aliquip ex ea commodo consequat.` - ); - assert.equal(element.summary, `UntaggedDescription summary.`); - assert.equal(element.deprecated, `UntaggedDescription deprecated message.`); - }); - - // Fields - - test('field1', ({getModule}) => { - const element = getModule('element-a').getDeclaration('ElementA'); - assert.ok(element.isClassDeclaration()); - const member = element.getField('field1'); - assert.ok(member?.isClassField()); - assert.equal( - member.description, - `Class field 1 description\nwith wraparound` - ); - assert.equal(member.default, `'default1'`); - assert.equal(member.privacy, 'private'); - assert.equal(member.type?.text, 'string'); - }); - - test('field2', ({getModule}) => { - const element = getModule('element-a').getDeclaration('ElementA'); - assert.ok(element.isClassDeclaration()); - const member = element.getField('field2'); - assert.ok(member?.isClassField()); - assert.equal(member.summary, `Class field 2 summary\nwith wraparound`); - assert.equal( - member.description, - `Class field 2 description\nwith wraparound` - ); - assert.equal(member.default, undefined); - assert.equal(member.privacy, 'protected'); - assert.equal(member.type?.text, 'string | number'); - }); - - test('field3', ({getModule}) => { - const element = getModule('element-a').getDeclaration('ElementA'); - assert.ok(element.isClassDeclaration()); - const member = element.getField('field3'); - assert.ok(member?.isClassField()); - assert.equal( - member.description, - `Class field 3 description\nwith wraparound` - ); - assert.equal(member.default, undefined); - assert.equal(member.privacy, 'public'); - assert.equal(member.type?.text, 'string'); - assert.equal(member.deprecated, true); - }); - - test('field4', ({getModule}) => { + test('basic class analysis', ({getModule}) => { const element = getModule('element-a').getDeclaration('ElementA'); assert.ok(element.isClassDeclaration()); - const member = element.getField('field4'); - assert.ok(member?.isClassField()); - assert.equal(member.summary, `Class field 4 summary\nwith wraparound`); - assert.equal( - member.description, - `Class field 4 description\nwith wraparound` - ); - assert.equal( - member.default, - `new Promise${lang === 'ts' ? '' : ''}((r) => r())` - ); - assert.equal(member.type?.text, 'Promise'); - assert.equal(member.deprecated, 'Class field 4 deprecated'); - }); - - // Methods - - test('method1', ({getModule}) => { - const element = getModule('element-a').getDeclaration('ElementA'); - assert.ok(element.isClassDeclaration()); - const member = element.getMethod('method1'); - assert.ok(member?.isClassMethod()); - assert.equal(member.description, `Method 1 description\nwith wraparound`); - assert.equal(member.parameters?.length, 0); - assert.equal(member.return?.type?.text, 'void'); - }); - - test('method2', ({getModule}) => { - const element = getModule('element-a').getDeclaration('ElementA'); - assert.ok(element.isClassDeclaration()); - const member = element.getMethod('method2'); - assert.ok(member?.isClassMethod()); - assert.equal(member.summary, `Method 2 summary\nwith wraparound`); - assert.equal(member.description, `Method 2 description\nwith wraparound`); - assert.equal(member.parameters?.length, 3); - assert.equal(member.parameters?.[0].name, 'a'); - assert.equal(member.parameters?.[0].description, 'Param a description'); - assert.equal(member.parameters?.[0].summary, undefined); - assert.equal(member.parameters?.[0].type?.text, 'string'); - assert.equal(member.parameters?.[0].default, undefined); - assert.equal(member.parameters?.[0].rest, false); - assert.equal(member.parameters?.[1].name, 'b'); - assert.equal( - member.parameters?.[1].description, - 'Param b description\nwith wraparound' - ); - assert.equal(member.parameters?.[1].type?.text, 'boolean'); - assert.equal(member.parameters?.[1].optional, true); - assert.equal(member.parameters?.[1].default, 'false'); - assert.equal(member.parameters?.[1].rest, false); - assert.equal(member.parameters?.[2].name, 'c'); - assert.equal(member.parameters?.[2].description, 'Param c description'); - assert.equal(member.parameters?.[2].summary, undefined); - assert.equal(member.parameters?.[2].type?.text, 'number[]'); - assert.equal(member.parameters?.[2].optional, false); - assert.equal(member.parameters?.[2].default, undefined); - assert.equal(member.parameters?.[2].rest, true); - assert.equal(member.return?.type?.text, 'string'); - assert.equal(member.return?.description, 'Method 2 return description'); - assert.equal(member.deprecated, 'Method 2 deprecated'); + assert.equal(element.description, 'A cool custom element.'); + const field = element.getField('field1'); + assert.ok(field?.isClassField()); + assert.equal(field.description, `Class field 1 description`); + assert.equal(field.type?.text, 'string'); + const method = element.getMethod('method1'); + assert.ok(method?.isClassMethod()); + assert.equal(method.description, `Method 1 description`); + assert.equal(method.parameters?.length, 0); + assert.equal(method.return?.type?.text, 'void'); }); test.run(); diff --git a/packages/labs/analyzer/test-files/js/classes/classes.js b/packages/labs/analyzer/test-files/js/classes/classes.js new file mode 100644 index 0000000000..52351aa483 --- /dev/null +++ b/packages/labs/analyzer/test-files/js/classes/classes.js @@ -0,0 +1,159 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + +export class BaseClass {} + +export class Class1 extends BaseClass { + /** + * Class field 1 description + * with wraparound + * @private + */ + field1 = 'default1'; + + /** + * @summary Class field 2 summary + * with wraparound + * + * @description Class field 2 description + * with wraparound + * @protected + * @type {number | string} + */ + field2; + + /** + * @description Class field 3 description + * with wraparound + * @optional + * @type {string} + * @deprecated + */ + field3; + + /** + * Class field 4 description + * with wraparound + * @summary Class field 4 summary + * with wraparound + * @type {Promise} + * @deprecated Class field 4 deprecated + */ + field4 = new Promise((r) => r()); + + /** + * Method 1 description + * with wraparound + */ + method1() {} + + /** + * @summary Method 2 summary + * with wraparound + * + * @description Method 2 description + * with wraparound + * + * @param {string} a Param a description + * @param {boolean} b Param b description + * with wraparound + * + * @param {number[]} c Param c description + * @returns {string} Method 2 return description + * + * @deprecated Method 2 deprecated + */ + method2(a, b = false, ...c) { + return b ? a : c[0].toFixed(); + } + + /** + * @summary Static class field 1 summary + * @description Static class field 1 description + * @protected + * @type {number | string} + */ + static field1; + + /** + * @summary Static method 1 summary + * @description Static method 1 description + * @param {string} a Param a description + * @param {boolean} b Param b description + * @param {number[]} c Param c description + * @returns {string} Static method 1 return description + * @deprecated Static method 1 deprecated + */ + static method1(a, b = false, ...c) { + return b ? a : c[0].toFixed(); + } +} + +/** + * @description TaggedDescription description. Lorem ipsum dolor sit amet, consectetur + * adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna + * aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris + * nisi ut aliquip ex ea commodo consequat. + * @summary TaggedDescription summary. + * @deprecated TaggedDescription deprecated message. + */ +export class TaggedDescription extends BaseClass {} + +/** + * UntaggedDescription description. Lorem ipsum dolor sit amet, consectetur + * adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna + * aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris + * nisi ut aliquip ex ea commodo consequat. + * + * @deprecated UntaggedDescription deprecated message. + * @summary UntaggedDescription summary. + */ +export class UntaggedDescription extends BaseClass {} + +/** + * ConstClass description + */ +export const ConstClass = class IgnoreThisName extends BaseClass { + /** + * ConstClass field 1 description + */ + field1 = 'default1'; + + /** + * ConstClass method 1 description + */ + method1() {} +}; + +/** + * @description ConstClassNoName description + */ +export const ConstClassNoName = class extends BaseClass { + /** + * ConstClassNoName field 1 description + */ + field1 = 'default1'; + + /** + * ConstClassNoName method 1 description + */ + method1() {} +}; + +/** + * default class description + */ +export default class extends BaseClass { + /** + * default class field 1 description + */ + field1 = 'default1'; + + /** + * default class method 1 description + */ + method1() {} +} diff --git a/packages/labs/analyzer/test-files/js/classes/package.json b/packages/labs/analyzer/test-files/js/classes/package.json new file mode 100644 index 0000000000..1fd4d4af5c --- /dev/null +++ b/packages/labs/analyzer/test-files/js/classes/package.json @@ -0,0 +1,6 @@ +{ + "name": "@lit-internal/test-classes", + "dependencies": { + "lit": "^2.0.0" + } +} diff --git a/packages/labs/analyzer/test-files/js/functions/functions.js b/packages/labs/analyzer/test-files/js/functions/functions.js index 35af9c1249..8ce866376a 100644 --- a/packages/labs/analyzer/test-files/js/functions/functions.js +++ b/packages/labs/analyzer/test-files/js/functions/functions.js @@ -36,3 +36,54 @@ export function function2(a, b = false, ...c) { export default function () { return 'default'; } + +/** + * Const function description + * with wraparound + * + * @summary Const function summary + * with wraparound + * + * @param {string} a Param a description + * @param {boolean} b Param b description + * with wraparound + * + * @param {number[]} c Param c description + * @returns {string} Const function return description + * + * @deprecated Const function deprecated + */ +export const constFunction = function ignoreThisName(a, b = false, ...c) { + return b ? a : c[0].toFixed(); +}; + +/** + * @summary Const arrow function summary + * with wraparound + * + * @description Const arrow function description + * with wraparound + * + * @param {string} a Param a description + * @param {boolean} b Param b description + * with wraparound + * + * @param {number[]} c Param c description + * @returns {string} Const arrow function return description + * + * @deprecated Const arrow function deprecated + */ +export const constArrowFunction = (a, b = false, ...c) => { + return b ? a : c[0].toFixed(); +}; + +/** + * @description Async function description + * @param {string} a Param a description + * @returns {Promise} Async function return description + * @deprecated Async function deprecated + */ +export const asyncFunction = async (a) => { + await 0; + return a; +}; diff --git a/packages/labs/analyzer/test-files/js/jsdoc/element-a.js b/packages/labs/analyzer/test-files/js/jsdoc/element-a.js index 1eedbb3a8e..0dabde66b5 100644 --- a/packages/labs/analyzer/test-files/js/jsdoc/element-a.js +++ b/packages/labs/analyzer/test-files/js/jsdoc/element-a.js @@ -30,107 +30,12 @@ import {LitElement} from 'lit'; export class ElementA extends LitElement { /** * Class field 1 description - * with wraparound - * @private */ field1 = 'default1'; - /** - * @summary Class field 2 summary - * with wraparound - * - * @description Class field 2 description - * with wraparound - * @protected - * @type {number | string} - */ - field2; - - /** - * @description Class field 3 description - * with wraparound - * @optional - * @type {string} - * @deprecated - */ - field3; - - /** - * Class field 4 description - * with wraparound - * @summary Class field 4 summary - * with wraparound - * @type {Promise} - * @deprecated Class field 4 deprecated - */ - field4 = new Promise((r) => r()); - /** * Method 1 description - * with wraparound */ method1() {} - - /** - * @summary Method 2 summary - * with wraparound - * - * @description Method 2 description - * with wraparound - * - * @param {string} a Param a description - * @param {boolean} b Param b description - * with wraparound - * - * @param {number[]} c Param c description - * @returns {string} Method 2 return description - * - * @deprecated Method 2 deprecated - */ - method2(a, b = false, ...c) { - return b ? a : c[0].toFixed(); - } - - /** - * @summary Static class field 1 summary - * @description Static class field 1 description - * @protected - * @type {number | string} - */ - static field1; - - /** - * @summary Static method 1 summary - * @description Static method 1 description - * @param {string} a Param a description - * @param {boolean} b Param b description - * @param {number[]} c Param c description - * @returns {string} Static method 1 return description - * @deprecated Static method 1 deprecated - */ - static method1(a, b = false, ...c) { - return b ? a : c[0].toFixed(); - } } customElements.define('element-a', ElementA); - -/** - * @description TaggedDescription description. Lorem ipsum dolor sit amet, consectetur - * adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna - * aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris - * nisi ut aliquip ex ea commodo consequat. - * @summary TaggedDescription summary. - * @deprecated TaggedDescription deprecated message. - */ -export class TaggedDescription extends LitElement {} - -/** - * UntaggedDescription description. Lorem ipsum dolor sit amet, consectetur - * adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna - * aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris - * nisi ut aliquip ex ea commodo consequat. - * - * @deprecated UntaggedDescription deprecated message. - * @summary UntaggedDescription summary. - */ -export class UntaggedDescription extends LitElement {} diff --git a/packages/labs/analyzer/test-files/js/vanilla-jsdoc/element-a.js b/packages/labs/analyzer/test-files/js/vanilla-jsdoc/element-a.js index 2a3327c08b..65f2435616 100644 --- a/packages/labs/analyzer/test-files/js/vanilla-jsdoc/element-a.js +++ b/packages/labs/analyzer/test-files/js/vanilla-jsdoc/element-a.js @@ -28,86 +28,12 @@ export class ElementA extends HTMLElement { /** * Class field 1 description - * with wraparound - * @private */ field1 = 'default1'; - /** - * @summary Class field 2 summary - * with wraparound - * - * @description Class field 2 description - * with wraparound - * @protected - * @type {number | string} - */ - field2; - - /** - * @description Class field 3 description - * with wraparound - * @optional - * @type {string} - * @deprecated - */ - field3; - - /** - * Class field 4 description - * with wraparound - * @summary Class field 4 summary - * with wraparound - * @type {Promise} - * @deprecated Class field 4 deprecated - */ - field4 = new Promise((r) => r()); - /** * Method 1 description - * with wraparound */ method1() {} - - /** - * @summary Method 2 summary - * with wraparound - * - * @description Method 2 description - * with wraparound - * - * @param {string} a Param a description - * @param {boolean} b Param b description - * with wraparound - * - * @param {number[]} c Param c description - * @returns {string} Method 2 return description - * - * @deprecated Method 2 deprecated - */ - method2(a, b = false, ...c) { - return b ? a : c[0].toFixed(); - } } customElements.define('element-a', HTMLElement); - -/** - * @description TaggedDescription description. Lorem ipsum dolor sit amet, consectetur - * adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna - * aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris - * nisi ut aliquip ex ea commodo consequat. - * @summary TaggedDescription summary. - * @deprecated TaggedDescription deprecated message. - */ -export class TaggedDescription extends HTMLElement {} - -/** - * UntaggedDescription description. Lorem ipsum dolor sit amet, consectetur - * adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna - * aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris - * nisi ut aliquip ex ea commodo consequat. - * - * @deprecated UntaggedDescription deprecated message. - * @summary UntaggedDescription summary. - */ -export class UntaggedDescription extends HTMLElement {} diff --git a/packages/labs/analyzer/test-files/ts/classes/package.json b/packages/labs/analyzer/test-files/ts/classes/package.json new file mode 100644 index 0000000000..1fd4d4af5c --- /dev/null +++ b/packages/labs/analyzer/test-files/ts/classes/package.json @@ -0,0 +1,6 @@ +{ + "name": "@lit-internal/test-classes", + "dependencies": { + "lit": "^2.0.0" + } +} diff --git a/packages/labs/analyzer/test-files/ts/classes/src/classes.ts b/packages/labs/analyzer/test-files/ts/classes/src/classes.ts new file mode 100644 index 0000000000..be7ef7fbee --- /dev/null +++ b/packages/labs/analyzer/test-files/ts/classes/src/classes.ts @@ -0,0 +1,152 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + +export class BaseClass {} + +export class Class1 extends BaseClass { + /** + * Class field 1 description + * with wraparound + */ + private field1 = 'default1'; + + /** + * @summary Class field 2 summary + * with wraparound + * + * @description Class field 2 description + * with wraparound + */ + protected field2: number | string; + + /** + * @description Class field 3 description + * with wraparound + * @deprecated + */ + field3?: string; + + /** + * Class field 4 description + * with wraparound + * @summary Class field 4 summary + * with wraparound + * @deprecated Class field 4 deprecated + */ + field4 = new Promise((r) => r()); + + /** + * Method 1 description + * with wraparound + */ + method1() {} + + /** + * @summary Method 2 summary + * with wraparound + * + * @description Method 2 description + * with wraparound + * + * @param a Param a description + * @param b Param b description + * with wraparound + * + * @param c Param c description + * @returns Method 2 return description + * + * @deprecated Method 2 deprecated + */ + method2(a: string, b = false, ...c: number[]) { + return b ? a : c[0].toFixed(); + } + + /** + * @summary Static class field 1 summary + * @description Static class field 1 description + * @protected + */ + static field1: number | string; + + /** + * @summary Static method 1 summary + * @description Static method 1 description + * @param a Param a description + * @param b Param b description + * @param c Param c description + * @returns Static method 1 return description + * @deprecated Static method 1 deprecated + */ + static method1(a: string, b = false, ...c: number[]) { + return b ? a : c[0].toFixed(); + } +} + +/** + * @description TaggedDescription description. Lorem ipsum dolor sit amet, consectetur + * adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna + * aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris + * nisi ut aliquip ex ea commodo consequat. + * @summary TaggedDescription summary. + * @deprecated TaggedDescription deprecated message. + */ +export class TaggedDescription extends BaseClass {} + +/** + * UntaggedDescription description. Lorem ipsum dolor sit amet, consectetur + * adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna + * aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris + * nisi ut aliquip ex ea commodo consequat. + * + * @deprecated UntaggedDescription deprecated message. + * @summary UntaggedDescription summary. + */ +export class UntaggedDescription extends BaseClass {} + +/** + * ConstClass description + */ +export const ConstClass = class IgnoreThisName extends BaseClass { + /** + * ConstClass field 1 description + */ + field1 = 'default1'; + + /** + * ConstClass method 1 description + */ + method1() {} +}; + +/** + * @description ConstClassNoName description + */ +export const ConstClassNoName = class extends BaseClass { + /** + * ConstClassNoName field 1 description + */ + field1 = 'default1'; + + /** + * ConstClassNoName method 1 description + */ + method1() {} +}; + +/** + * default class description + */ +export default class extends BaseClass { + /** + * default class field 1 description + */ + field1 = 'default1'; + + /** + * default class method 1 description + */ + method1() {} +} diff --git a/packages/labs/analyzer/test-files/ts/classes/tsconfig.json b/packages/labs/analyzer/test-files/ts/classes/tsconfig.json new file mode 100644 index 0000000000..204701fc83 --- /dev/null +++ b/packages/labs/analyzer/test-files/ts/classes/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "es2020", + "lib": ["es2020", "DOM"], + "module": "ES2020", + "rootDir": "./src", + "outDir": "./", + "moduleResolution": "node", + "experimentalDecorators": true, + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["src/**/*.ts"], + "exclude": [] +} diff --git a/packages/labs/analyzer/test-files/ts/functions/src/functions.ts b/packages/labs/analyzer/test-files/ts/functions/src/functions.ts index 0c1dbeaa27..02fda1b323 100644 --- a/packages/labs/analyzer/test-files/ts/functions/src/functions.ts +++ b/packages/labs/analyzer/test-files/ts/functions/src/functions.ts @@ -36,3 +36,58 @@ export function function2(a: string, b = false, ...c: number[]) { export default function () { return 'default'; } + +/** + * Const function description + * with wraparound + * + * @summary Const function summary + * with wraparound + * + * @param a Param a description + * @param b Param b description + * with wraparound + * + * @param c Param c description + * @returns Const function return description + * + * @deprecated Const function deprecated + */ +export const constFunction = function ignoreThisName( + a: string, + b = false, + ...c: number[] +) { + return b ? a : c[0].toFixed(); +}; + +/** + * @summary Const arrow function summary + * with wraparound + * + * @description Const arrow function description + * with wraparound + * + * @param a Param a description + * @param b Param b description + * with wraparound + * + * @param c Param c description + * @returns Const arrow function return description + * + * @deprecated Const arrow function deprecated + */ +export const constArrowFunction = (a: string, b = false, ...c: number[]) => { + return b ? a : c[0].toFixed(); +}; + +/** + * @description Async function description + * @param a Param a description + * @returns Async function return description + * @deprecated Async function deprecated + */ +export const asyncFunction = async (a: string) => { + await 0; + return a; +}; diff --git a/packages/labs/analyzer/test-files/ts/jsdoc/src/element-a.ts b/packages/labs/analyzer/test-files/ts/jsdoc/src/element-a.ts index 3c0463fb23..bcc6e6539f 100644 --- a/packages/labs/analyzer/test-files/ts/jsdoc/src/element-a.ts +++ b/packages/labs/analyzer/test-files/ts/jsdoc/src/element-a.ts @@ -32,99 +32,11 @@ import {customElement} from 'lit/decorators.js'; export class ElementA extends LitElement { /** * Class field 1 description - * with wraparound */ - private field1 = 'default1'; - - /** - * @summary Class field 2 summary - * with wraparound - * - * @description Class field 2 description - * with wraparound - */ - protected field2: number | string; - - /** - * @description Class field 3 description - * with wraparound - * @deprecated - */ - field3?: string; - - /** - * Class field 4 description - * with wraparound - * @summary Class field 4 summary - * with wraparound - * @deprecated Class field 4 deprecated - */ - field4 = new Promise((r) => r()); + field1 = 'default1'; /** * Method 1 description - * with wraparound */ method1() {} - - /** - * @summary Method 2 summary - * with wraparound - * - * @description Method 2 description - * with wraparound - * - * @param a Param a description - * @param b Param b description - * with wraparound - * - * @param c Param c description - * @returns Method 2 return description - * - * @deprecated Method 2 deprecated - */ - method2(a: string, b = false, ...c: number[]) { - return b ? a : c[0].toFixed(); - } - - /** - * @summary Static class field 1 summary - * @description Static class field 1 description - * @protected - */ - static field1: number | string; - - /** - * @summary Static method 1 summary - * @description Static method 1 description - * @param a Param a description - * @param b Param b description - * @param c Param c description - * @returns Static method 1 return description - * @deprecated Static method 1 deprecated - */ - static method1(a: string, b = false, ...c: number[]) { - return b ? a : c[0].toFixed(); - } } - -/** - * @description TaggedDescription description. Lorem ipsum dolor sit amet, consectetur - * adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna - * aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris - * nisi ut aliquip ex ea commodo consequat. - * @summary TaggedDescription summary. - * @deprecated TaggedDescription deprecated message. - */ -export class TaggedDescription extends LitElement {} - -/** - * UntaggedDescription description. Lorem ipsum dolor sit amet, consectetur - * adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna - * aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris - * nisi ut aliquip ex ea commodo consequat. - * - * @deprecated UntaggedDescription deprecated message. - * @summary UntaggedDescription summary. - */ -export class UntaggedDescription extends LitElement {} diff --git a/packages/labs/analyzer/test-files/ts/vanilla-jsdoc/src/element-a.ts b/packages/labs/analyzer/test-files/ts/vanilla-jsdoc/src/element-a.ts index 286f7dc437..0064157f4c 100644 --- a/packages/labs/analyzer/test-files/ts/vanilla-jsdoc/src/element-a.ts +++ b/packages/labs/analyzer/test-files/ts/vanilla-jsdoc/src/element-a.ts @@ -28,60 +28,13 @@ export class ElementA extends HTMLElement { /** * Class field 1 description - * with wraparound */ private field1 = 'default1'; - /** - * @summary Class field 2 summary - * with wraparound - * - * @description Class field 2 description - * with wraparound - */ - protected field2: number | string; - - /** - * @description Class field 3 description - * with wraparound - * @deprecated - */ - field3?: string; - - /** - * Class field 4 description - * with wraparound - * @summary Class field 4 summary - * with wraparound - * @deprecated Class field 4 deprecated - */ - field4 = new Promise((r) => r()); - /** * Method 1 description - * with wraparound */ method1() {} - - /** - * @summary Method 2 summary - * with wraparound - * - * @description Method 2 description - * with wraparound - * - * @param a Param a description - * @param b Param b description - * with wraparound - * - * @param c Param c description - * @returns Method 2 return description - * - * @deprecated Method 2 deprecated - */ - method2(a: string, b = false, ...c: number[]) { - return b ? a : c[0].toFixed(); - } } customElements.define('element-a', ElementA);