diff --git a/src/compiler/types/generate-app-types.ts b/src/compiler/types/generate-app-types.ts index c1c43bb87f04..6f48d3492217 100644 --- a/src/compiler/types/generate-app-types.ts +++ b/src/compiler/types/generate-app-types.ts @@ -4,7 +4,7 @@ import { generateComponentTypes } from './generate-component-types'; import { generateEventDetailTypes } from './generate-event-detail-types'; import { GENERATED_DTS, getComponentsDtsSrcFilePath } from '../output-targets/output-utils'; import { isAbsolute, relative, resolve } from 'path'; -import { normalizePath } from '@utils'; +import { addDockBlock, normalizePath } from '@utils'; import { updateReferenceTypeImports } from './update-import-refs'; import { updateStencilTypesImports } from './stencil-types'; @@ -146,10 +146,15 @@ const generateComponentTypesFile = (config: d.Config, buildCtx: d.BuildCtx, areT c.push(` export namespace JSX {`); c.push(` interface IntrinsicElements {`); c.push( - ...modules.map( - (m) => - ` "${m.tagName}": LocalJSX.${m.tagNameAsPascal} & JSXBase.HTMLAttributes<${m.htmlElementName}>;` - ) + ...modules.map((m) => { + const docs = components.find((c) => c.tagName === m.tagName).docs; + + return addDockBlock( + ` "${m.tagName}": LocalJSX.${m.tagNameAsPascal} & JSXBase.HTMLAttributes<${m.htmlElementName}>;`, + docs, + 12 + ); + }) ); c.push(` }`); c.push(` }`); diff --git a/src/compiler/types/generate-component-types.ts b/src/compiler/types/generate-component-types.ts index 205f06b67197..c07f3b715802 100644 --- a/src/compiler/types/generate-component-types.ts +++ b/src/compiler/types/generate-component-types.ts @@ -1,5 +1,5 @@ import type * as d from '../../declarations'; -import { dashToPascalCase, sortBy } from '@utils'; +import { addDockBlock, dashToPascalCase, sortBy } from '@utils'; import { generateEventTypes } from './generate-event-types'; import { generateMethodTypes } from './generate-method-types'; import { generatePropTypes } from './generate-prop-types'; @@ -33,7 +33,11 @@ export const generateComponentTypes = ( const jsxAttributes = attributesToMultiLineString([...propAttributes, ...eventAttributes], true, areTypesInternal); const element = [ - ` interface ${htmlElementName} extends Components.${tagNameAsPascal}, HTMLStencilElement {`, + addDockBlock( + ` interface ${htmlElementName} extends Components.${tagNameAsPascal}, HTMLStencilElement {`, + cmp.docs, + 4 + ), ` }`, ` var ${htmlElementName}: {`, ` prototype: ${htmlElementName};`, @@ -45,8 +49,8 @@ export const generateComponentTypes = ( tagName, tagNameAsPascal, htmlElementName, - component: ` interface ${tagNameAsPascal} {\n${componentAttributes} }`, - jsx: ` interface ${tagNameAsPascal} {\n${jsxAttributes} }`, + component: addDockBlock(` interface ${tagNameAsPascal} {\n${componentAttributes} }`, cmp.docs, 4), + jsx: addDockBlock(` interface ${tagNameAsPascal} {\n${jsxAttributes} }`, cmp.docs, 2), element: element.join(`\n`), }; }; diff --git a/src/utils/test/util.spec.ts b/src/utils/test/util.spec.ts index 8fc12ddc6e24..2b74a3d4435a 100644 --- a/src/utils/test/util.spec.ts +++ b/src/utils/test/util.spec.ts @@ -199,4 +199,46 @@ describe('util', () => { }); }); }); + + describe('addDockBlock', () => { + let str: string; + let docs: d.CompilerJsDoc; + + beforeEach(() => { + str = 'interface Foo extends Components.Foo, HTMLStencilElement {'; + docs = { + tags: [{ name: 'deprecated', text: 'only for testing' }], + text: 'Lorem ipsum', + }; + }); + + it('adds a doc block to the string', () => { + expect(util.addDockBlock(str, docs)).toEqual(`/** + * Lorem ipsum + * @deprecated only for testing + */ +interface Foo extends Components.Foo, HTMLStencilElement {`); + }); + + it('indents the doc block correctly', () => { + str = ' ' + str; + expect(util.addDockBlock(str, docs, 4)).toEqual(` /** + * Lorem ipsum + * @deprecated only for testing + */ + interface Foo extends Components.Foo, HTMLStencilElement {`); + }); + + it('excludes the @internal tag', () => { + docs.tags.push({ name: 'internal' }); + expect(util.addDockBlock(str, docs).includes('@internal')).toBeFalsy(); + }); + + it.each([[null], [undefined], [{ tags: [], text: '' }]])( + 'does not add a dock block when docs are empty (%j)', + (docs) => { + expect(util.addDockBlock(str, docs)).toEqual(str); + } + ); + }); }); diff --git a/src/utils/util.ts b/src/utils/util.ts index fe11fd325224..0c6c403ca0d9 100644 --- a/src/utils/util.ts +++ b/src/utils/util.ts @@ -72,6 +72,41 @@ ${docs.tags .join('\n')}`.trim(); } +/** + * Adds a doc block to a string + * @param str the string to add a doc block to + * @param docs the compiled JS docs + * @param indentation number of spaces to indent the block with + * @returns the doc block + */ +export function addDockBlock(str: string, docs?: d.CompilerJsDoc, indentation: number = 0): string { + if (!docs) { + return str; + } + + return [getDocBlock(docs, indentation), str].filter(Boolean).join(`\n`); +} + +function getDocBlock(docs: d.CompilerJsDoc, indentation: number = 0): string { + const textDocs = getDocBlockLines(docs); + if (!textDocs.filter(Boolean).length) { + return ''; + } + + const spaces = new Array(indentation + 1).join(' '); + + return [spaces + '/**', ...textDocs.map((line) => spaces + ` * ${line}`), spaces + ' */'].join(`\n`); +} + +function getDocBlockLines(docs: d.CompilerJsDoc | undefined | null): string[] { + return [ + ...docs.text.split(lineBreakRegex), + ...docs.tags + .filter((tag) => tag.name !== 'internal') + .map((tag) => `@${tag.name} ${tag.text || ''}`.split(lineBreakRegex)), + ].flat(); +} + /** * Retrieve a project's dependencies from the current build context * @param buildCtx the current build context to query for a specific package