Skip to content

Commit 2e4b1fc

Browse files
authored
feat(compiler): copy doc block from component to generated types (#3525)
The doc block from the component itself is now copied to the generated components.d.ts file. This makes it possible for other tools to get the documentation and use it, e.g. an IDE can display the documentation for a component when it's being used
1 parent c275c64 commit 2e4b1fc

File tree

6 files changed

+168
-9
lines changed

6 files changed

+168
-9
lines changed

src/compiler/types/generate-app-types.ts

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { normalizePath } from '@utils';
1+
import { addDocBlock, normalizePath } from '@utils';
22
import { isAbsolute, relative, resolve } from 'path';
33

44
import type * as d from '../../declarations';
@@ -133,7 +133,12 @@ const generateComponentTypesFile = (config: d.Config, buildCtx: d.BuildCtx, areT
133133
c.push(`}`);
134134

135135
c.push(`declare namespace LocalJSX {`);
136-
c.push(...modules.map((m) => ` ${m.jsx}`));
136+
c.push(
137+
...modules.map((m) => {
138+
const docs = components.find((c) => c.tagName === m.tagName).docs;
139+
return addDocBlock(` ${m.jsx}`, docs, 4);
140+
})
141+
);
137142

138143
c.push(` interface IntrinsicElements {`);
139144
c.push(...modules.map((m) => ` "${m.tagName}": ${m.tagNameAsPascal};`));
@@ -147,10 +152,15 @@ const generateComponentTypesFile = (config: d.Config, buildCtx: d.BuildCtx, areT
147152
c.push(` export namespace JSX {`);
148153
c.push(` interface IntrinsicElements {`);
149154
c.push(
150-
...modules.map(
151-
(m) =>
152-
` "${m.tagName}": LocalJSX.${m.tagNameAsPascal} & JSXBase.HTMLAttributes<${m.htmlElementName}>;`
153-
)
155+
...modules.map((m) => {
156+
const docs = components.find((c) => c.tagName === m.tagName).docs;
157+
158+
return addDocBlock(
159+
` "${m.tagName}": LocalJSX.${m.tagNameAsPascal} & JSXBase.HTMLAttributes<${m.htmlElementName}>;`,
160+
docs,
161+
12
162+
);
163+
})
154164
);
155165
c.push(` }`);
156166
c.push(` }`);

src/compiler/types/generate-component-types.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { dashToPascalCase, sortBy } from '@utils';
1+
import { addDocBlock, dashToPascalCase, sortBy } from '@utils';
22

33
import type * as d from '../../declarations';
44
import { generateEventTypes } from './generate-event-types';
@@ -34,7 +34,11 @@ export const generateComponentTypes = (
3434
const jsxAttributes = attributesToMultiLineString([...propAttributes, ...eventAttributes], true, areTypesInternal);
3535

3636
const element = [
37-
` interface ${htmlElementName} extends Components.${tagNameAsPascal}, HTMLStencilElement {`,
37+
addDocBlock(
38+
` interface ${htmlElementName} extends Components.${tagNameAsPascal}, HTMLStencilElement {`,
39+
cmp.docs,
40+
4
41+
),
3842
` }`,
3943
` var ${htmlElementName}: {`,
4044
` prototype: ${htmlElementName};`,
@@ -46,7 +50,7 @@ export const generateComponentTypes = (
4650
tagName,
4751
tagNameAsPascal,
4852
htmlElementName,
49-
component: ` interface ${tagNameAsPascal} {\n${componentAttributes} }`,
53+
component: addDocBlock(` interface ${tagNameAsPascal} {\n${componentAttributes} }`, cmp.docs, 4),
5054
jsx: ` interface ${tagNameAsPascal} {\n${jsxAttributes} }`,
5155
element: element.join(`\n`),
5256
};

src/utils/test/util.spec.ts

+51
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,55 @@ describe('util', () => {
200200
});
201201
});
202202
});
203+
204+
describe('addDocBlock', () => {
205+
let str: string;
206+
let docs: d.CompilerJsDoc;
207+
208+
beforeEach(() => {
209+
str = 'interface Foo extends Components.Foo, HTMLStencilElement {';
210+
docs = {
211+
tags: [{ name: 'deprecated', text: 'only for testing' }],
212+
text: 'Lorem ipsum',
213+
};
214+
});
215+
216+
it('adds a doc block to the string', () => {
217+
expect(util.addDocBlock(str, docs)).toEqual(`/**
218+
* Lorem ipsum
219+
* @deprecated only for testing
220+
*/
221+
interface Foo extends Components.Foo, HTMLStencilElement {`);
222+
});
223+
224+
it('indents the doc block correctly', () => {
225+
str = ' ' + str;
226+
expect(util.addDocBlock(str, docs, 4)).toEqual(` /**
227+
* Lorem ipsum
228+
* @deprecated only for testing
229+
*/
230+
interface Foo extends Components.Foo, HTMLStencilElement {`);
231+
});
232+
233+
it('excludes the @internal tag', () => {
234+
docs.tags.push({ name: 'internal' });
235+
expect(util.addDocBlock(str, docs).includes('@internal')).toBeFalsy();
236+
});
237+
238+
it('excludes empty lines', () => {
239+
docs.text = '';
240+
str = ' ' + str;
241+
expect(util.addDocBlock(str, docs, 4)).toEqual(` /**
242+
* @deprecated only for testing
243+
*/
244+
interface Foo extends Components.Foo, HTMLStencilElement {`);
245+
});
246+
247+
it.each([[null], [undefined], [{ tags: [], text: '' }]])(
248+
'does not add a doc block when docs are empty (%j)',
249+
(docs) => {
250+
expect(util.addDocBlock(str, docs)).toEqual(str);
251+
}
252+
);
253+
});
203254
});

src/utils/util.ts

+50
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type * as d from '../declarations';
22
import { dashToPascalCase, isString, toDashCase } from './helpers';
33
import { buildError } from './message-utils';
44

5+
const SUPPRESSED_JSDOC_TAGS: string[] = ['internal'];
6+
57
export const createJsVarName = (fileName: string) => {
68
if (isString(fileName)) {
79
fileName = fileName.split('?')[0];
@@ -72,6 +74,54 @@ ${docs.tags
7274
.join('\n')}`.trim();
7375
}
7476

77+
/**
78+
* Adds a doc block to a string
79+
* @param str the string to add a doc block to
80+
* @param docs the compiled JS docs
81+
* @param indentation number of spaces to indent the block with
82+
* @returns the doc block
83+
*/
84+
export function addDocBlock(str: string, docs?: d.CompilerJsDoc, indentation: number = 0): string {
85+
if (!docs) {
86+
return str;
87+
}
88+
89+
return [formatDocBlock(docs, indentation), str].filter(Boolean).join(`\n`);
90+
}
91+
92+
/**
93+
* Formats the given compiled docs to a JavaScript doc block
94+
* @param docs the compiled JS docs
95+
* @param indentation number of spaces to indent the block with
96+
* @returns the formatted doc block
97+
*/
98+
function formatDocBlock(docs: d.CompilerJsDoc, indentation: number = 0): string {
99+
const textDocs = getDocBlockLines(docs);
100+
if (!textDocs.filter(Boolean).length) {
101+
return '';
102+
}
103+
104+
const spaces = new Array(indentation + 1).join(' ');
105+
106+
return [spaces + '/**', ...textDocs.map((line) => spaces + ` * ${line}`), spaces + ' */'].join(`\n`);
107+
}
108+
109+
/**
110+
* Get all lines part of the doc block
111+
* @param docs the compiled JS docs
112+
* @returns list of lines part of the doc block
113+
*/
114+
function getDocBlockLines(docs: d.CompilerJsDoc): string[] {
115+
return [
116+
...docs.text.split(lineBreakRegex),
117+
...docs.tags
118+
.filter((tag) => !SUPPRESSED_JSDOC_TAGS.includes(tag.name))
119+
.map((tag) => `@${tag.name} ${tag.text || ''}`.split(lineBreakRegex)),
120+
]
121+
.flat()
122+
.filter(Boolean);
123+
}
124+
75125
/**
76126
* Retrieve a project's dependencies from the current build context
77127
* @param buildCtx the current build context to query for a specific package

test/end-to-end/src/components.d.ts

+32
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ export namespace Components {
1414
interface CarDetail {
1515
"car": CarData;
1616
}
17+
/**
18+
* Component that helps display a list of cars
19+
* @slot header - The slot for the header content.
20+
* @part car - The shadow part to target to style the car.
21+
*/
1722
interface CarList {
1823
"cars": CarData[];
1924
"selected": CarData;
@@ -47,6 +52,9 @@ export namespace Components {
4752
}
4853
interface PrerenderCmp {
4954
}
55+
/**
56+
* @virtualProp mode - Mode
57+
*/
5058
interface PropCmp {
5159
"first": string;
5260
"lastName": string;
@@ -92,6 +100,11 @@ declare global {
92100
prototype: HTMLCarDetailElement;
93101
new (): HTMLCarDetailElement;
94102
};
103+
/**
104+
* Component that helps display a list of cars
105+
* @slot header - The slot for the header content.
106+
* @part car - The shadow part to target to style the car.
107+
*/
95108
interface HTMLCarListElement extends Components.CarList, HTMLStencilElement {
96109
}
97110
var HTMLCarListElement: {
@@ -164,6 +177,9 @@ declare global {
164177
prototype: HTMLPrerenderCmpElement;
165178
new (): HTMLPrerenderCmpElement;
166179
};
180+
/**
181+
* @virtualProp mode - Mode
182+
*/
167183
interface HTMLPropCmpElement extends Components.PropCmp, HTMLStencilElement {
168184
}
169185
var HTMLPropCmpElement: {
@@ -225,6 +241,11 @@ declare namespace LocalJSX {
225241
interface CarDetail {
226242
"car"?: CarData;
227243
}
244+
/**
245+
* Component that helps display a list of cars
246+
* @slot header - The slot for the header content.
247+
* @part car - The shadow part to target to style the car.
248+
*/
228249
interface CarList {
229250
"cars"?: CarData[];
230251
"onCarSelected"?: (event: CarListCustomEvent<CarData>) => void;
@@ -257,6 +278,9 @@ declare namespace LocalJSX {
257278
}
258279
interface PrerenderCmp {
259280
}
281+
/**
282+
* @virtualProp mode - Mode
283+
*/
260284
interface PropCmp {
261285
"first"?: string;
262286
"lastName"?: string;
@@ -304,6 +328,11 @@ declare module "@stencil/core" {
304328
"app-root": LocalJSX.AppRoot & JSXBase.HTMLAttributes<HTMLAppRootElement>;
305329
"build-data": LocalJSX.BuildData & JSXBase.HTMLAttributes<HTMLBuildDataElement>;
306330
"car-detail": LocalJSX.CarDetail & JSXBase.HTMLAttributes<HTMLCarDetailElement>;
331+
/**
332+
* Component that helps display a list of cars
333+
* @slot header - The slot for the header content.
334+
* @part car - The shadow part to target to style the car.
335+
*/
307336
"car-list": LocalJSX.CarList & JSXBase.HTMLAttributes<HTMLCarListElement>;
308337
"dom-api": LocalJSX.DomApi & JSXBase.HTMLAttributes<HTMLDomApiElement>;
309338
"dom-interaction": LocalJSX.DomInteraction & JSXBase.HTMLAttributes<HTMLDomInteractionElement>;
@@ -316,6 +345,9 @@ declare module "@stencil/core" {
316345
"method-cmp": LocalJSX.MethodCmp & JSXBase.HTMLAttributes<HTMLMethodCmpElement>;
317346
"path-alias-cmp": LocalJSX.PathAliasCmp & JSXBase.HTMLAttributes<HTMLPathAliasCmpElement>;
318347
"prerender-cmp": LocalJSX.PrerenderCmp & JSXBase.HTMLAttributes<HTMLPrerenderCmpElement>;
348+
/**
349+
* @virtualProp mode - Mode
350+
*/
319351
"prop-cmp": LocalJSX.PropCmp & JSXBase.HTMLAttributes<HTMLPropCmpElement>;
320352
"slot-cmp": LocalJSX.SlotCmp & JSXBase.HTMLAttributes<HTMLSlotCmpElement>;
321353
"slot-cmp-container": LocalJSX.SlotCmpContainer & JSXBase.HTMLAttributes<HTMLSlotCmpContainerElement>;

test/karma/test-app/components.d.ts

+12
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,9 @@ export namespace Components {
226226
}
227227
interface ShadowDomBasicRoot {
228228
}
229+
/**
230+
* @virtualProp {string} colormode - The mode determines which platform styles to use.
231+
*/
229232
interface ShadowDomMode {
230233
/**
231234
* The mode determines which platform styles to use.
@@ -868,6 +871,9 @@ declare global {
868871
prototype: HTMLShadowDomBasicRootElement;
869872
new (): HTMLShadowDomBasicRootElement;
870873
};
874+
/**
875+
* @virtualProp {string} colormode - The mode determines which platform styles to use.
876+
*/
871877
interface HTMLShadowDomModeElement extends Components.ShadowDomMode, HTMLStencilElement {
872878
}
873879
var HTMLShadowDomModeElement: {
@@ -1490,6 +1496,9 @@ declare namespace LocalJSX {
14901496
}
14911497
interface ShadowDomBasicRoot {
14921498
}
1499+
/**
1500+
* @virtualProp {string} colormode - The mode determines which platform styles to use.
1501+
*/
14931502
interface ShadowDomMode {
14941503
/**
14951504
* The mode determines which platform styles to use.
@@ -1813,6 +1822,9 @@ declare module "@stencil/core" {
18131822
"shadow-dom-array-root": LocalJSX.ShadowDomArrayRoot & JSXBase.HTMLAttributes<HTMLShadowDomArrayRootElement>;
18141823
"shadow-dom-basic": LocalJSX.ShadowDomBasic & JSXBase.HTMLAttributes<HTMLShadowDomBasicElement>;
18151824
"shadow-dom-basic-root": LocalJSX.ShadowDomBasicRoot & JSXBase.HTMLAttributes<HTMLShadowDomBasicRootElement>;
1825+
/**
1826+
* @virtualProp {string} colormode - The mode determines which platform styles to use.
1827+
*/
18161828
"shadow-dom-mode": LocalJSX.ShadowDomMode & JSXBase.HTMLAttributes<HTMLShadowDomModeElement>;
18171829
"shadow-dom-mode-root": LocalJSX.ShadowDomModeRoot & JSXBase.HTMLAttributes<HTMLShadowDomModeRootElement>;
18181830
"shadow-dom-slot-basic": LocalJSX.ShadowDomSlotBasic & JSXBase.HTMLAttributes<HTMLShadowDomSlotBasicElement>;

0 commit comments

Comments
 (0)