@ng-prism/plugin-jsdoc 22.6.0 → 22.7.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +18 -15
  2. package/dist/api-page/api-page.helpers.d.ts +79 -0
  3. package/dist/api-page/api-page.helpers.d.ts.map +1 -0
  4. package/dist/api-page/api-page.helpers.js +133 -0
  5. package/dist/api-page/api-page.model.d.ts +62 -0
  6. package/dist/api-page/api-page.model.d.ts.map +1 -0
  7. package/dist/api-page/api-page.model.js +122 -0
  8. package/dist/api-page/component-usage.d.ts +14 -0
  9. package/dist/api-page/component-usage.d.ts.map +1 -0
  10. package/dist/api-page/component-usage.js +120 -0
  11. package/dist/api-page/doc-links.d.ts +15 -0
  12. package/dist/api-page/doc-links.d.ts.map +1 -0
  13. package/dist/api-page/doc-links.js +45 -0
  14. package/dist/api-page/jsdoc-api-page.component.d.ts +98 -0
  15. package/dist/api-page/jsdoc-api-page.component.d.ts.map +1 -0
  16. package/dist/api-page/jsdoc-api-page.component.js +1458 -0
  17. package/dist/api-page/pipe-usage.d.ts +13 -0
  18. package/dist/api-page/pipe-usage.d.ts.map +1 -0
  19. package/dist/api-page/pipe-usage.js +35 -0
  20. package/dist/api-page/service-usage.d.ts +31 -0
  21. package/dist/api-page/service-usage.d.ts.map +1 -0
  22. package/dist/api-page/service-usage.js +66 -0
  23. package/dist/jsdoc-extractor.d.ts.map +1 -1
  24. package/dist/jsdoc-extractor.js +17 -10
  25. package/dist/jsdoc-panel.component.d.ts +8 -25
  26. package/dist/jsdoc-panel.component.d.ts.map +1 -1
  27. package/dist/jsdoc-panel.component.js +34 -474
  28. package/dist/jsdoc-plugin.browser.d.ts.map +1 -1
  29. package/dist/jsdoc-plugin.browser.js +2 -1
  30. package/dist/jsdoc-plugin.d.ts.map +1 -1
  31. package/dist/jsdoc-plugin.js +5 -1
  32. package/dist/service-page/api-page.model.d.ts +35 -0
  33. package/dist/service-page/api-page.model.d.ts.map +1 -0
  34. package/dist/service-page/api-page.model.js +54 -0
  35. package/dist/service-page/jsdoc-service-page.component.d.ts +82 -0
  36. package/dist/service-page/jsdoc-service-page.component.d.ts.map +1 -0
  37. package/dist/service-page/jsdoc-service-page.component.js +1178 -0
  38. package/dist/service-page/pipe-usage.d.ts +13 -0
  39. package/dist/service-page/pipe-usage.d.ts.map +1 -0
  40. package/dist/service-page/pipe-usage.js +35 -0
  41. package/dist/service-page/service-page.model.d.ts +78 -0
  42. package/dist/service-page/service-page.model.d.ts.map +1 -0
  43. package/dist/service-page/service-page.model.js +130 -0
  44. package/dist/service-page/service-usage.d.ts +28 -0
  45. package/dist/service-page/service-usage.d.ts.map +1 -0
  46. package/dist/service-page/service-usage.js +61 -0
  47. package/package.json +3 -2
@@ -0,0 +1,13 @@
1
+ import type { ScannedPipe } from '@ng-prism/core/plugin';
2
+ /**
3
+ * The snippet that shows how to use a pipe: the imports, the `providers`
4
+ * entries its setup asks for, the component's `imports` entry and a template
5
+ * line. Imports and setup are written as for a service.
6
+ *
7
+ * Like the service snippet it is generated and says nothing about values: the
8
+ * template line names the piped value `value` and each argument by its
9
+ * parameter name. It shows the first signature that is not `@deprecated`, the
10
+ * way a new template should call the pipe, or the first one when all are.
11
+ */
12
+ export declare function generatePipeUsage(pipe: ScannedPipe): string;
13
+ //# sourceMappingURL=pipe-usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipe-usage.d.ts","sourceRoot":"","sources":["../../src/service-page/pipe-usage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGpE;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAU3D"}
@@ -0,0 +1,35 @@
1
+ import { importLines, joinUsageBlocks, setupBlock } from './service-usage.js';
2
+ /**
3
+ * The snippet that shows how to use a pipe: the imports, the `providers`
4
+ * entries its setup asks for, the component's `imports` entry and a template
5
+ * line. Imports and setup are written as for a service.
6
+ *
7
+ * Like the service snippet it is generated and says nothing about values: the
8
+ * template line names the piped value `value` and each argument by its
9
+ * parameter name. It shows the first signature that is not `@deprecated`, the
10
+ * way a new template should call the pipe, or the first one when all are.
11
+ */
12
+ export function generatePipeUsage(pipe) {
13
+ const { className, pipeName, api } = pipe;
14
+ const signature = api.transform.find((method) => !method.tags.deprecated) ?? api.transform[0];
15
+ return joinUsageBlocks([
16
+ importLines(pipe),
17
+ setupBlock(api.setup),
18
+ ['// in the component that uses it', `@Component({ imports: [${className}], … })`],
19
+ ['// in its template', `{{ value | ${pipeName}${pipeArguments(signature)} }}`]
20
+ ]);
21
+ }
22
+ /**
23
+ * The arguments of the template line, from a signature's parameters after the
24
+ * piped value: `: pattern` for each one the call has to pass. Pipe arguments
25
+ * are positional, so what can be left out is the optional and rest parameters
26
+ * at the end; one with a default that a required parameter follows stays in.
27
+ */
28
+ function pipeArguments(signature) {
29
+ const params = signature?.params.slice(1) ?? [];
30
+ const lastRequired = params.map((param) => !param.optional && !param.rest).lastIndexOf(true);
31
+ return params
32
+ .slice(0, lastRequired + 1)
33
+ .map((param) => `: ${param.name}`)
34
+ .join('');
35
+ }
@@ -0,0 +1,78 @@
1
+ import type { ApiParam, ApiSetupEntry, ApiTypeRef } from '@ng-prism/core/plugin';
2
+ import type { ApiPageModel } from './api-page.model.js';
3
+ /** One entry of the API page's "On this page" list. `id` names the section, `level` 2 is a type below "Types". */
4
+ export interface TocEntry {
5
+ id: string;
6
+ label: string;
7
+ level: 1 | 2;
8
+ }
9
+ /** A piece of a type as written; `link` is the TOC id of the documented type the piece names. */
10
+ export interface TypeSegment {
11
+ text: string;
12
+ link?: string;
13
+ }
14
+ /** The TOC id of a documented type's card. */
15
+ export declare function typeAnchor(typeId: string): string;
16
+ /**
17
+ * The sections of an API page in the order the page shows them. Usage is
18
+ * always there, being generated; any other section only when it has content.
19
+ * A service has properties and methods, a pipe its transform signatures.
20
+ */
21
+ export declare function buildApiPageToc(page: ApiPageModel): TocEntry[];
22
+ /** Where the page is scrolled to, and where each anchor of the "On this page" list sits on it. */
23
+ export interface ScrollSpyState {
24
+ /** The TOC id of each anchor in page order, with its offset from the top of the scrolled content. */
25
+ anchors: readonly {
26
+ id: string;
27
+ top: number;
28
+ }[];
29
+ scrollTop: number;
30
+ clientHeight: number;
31
+ scrollHeight: number;
32
+ /** The entry a jump is heading for, for as long as the jump lasts. */
33
+ jumpTarget: string | null;
34
+ }
35
+ /**
36
+ * The entry the "On this page" list marks. During a jump, the one the reader
37
+ * jumped to, whatever the jump scrolls past. Otherwise the last section whose
38
+ * heading has come up to a line a quarter down the view, except at the bottom
39
+ * of the page: the sections there can never come up to the line, so the last
40
+ * one in view is active.
41
+ */
42
+ export declare function activeTocId({ anchors, scrollTop, clientHeight, scrollHeight, jumpTarget }: ScrollSpyState): string;
43
+ /**
44
+ * Cut a type as written into plain text and the names of documented types, so
45
+ * the page can turn those names into links to their cards.
46
+ *
47
+ * `ref.links` holds type ids, and an id is the type's name, or
48
+ * `${name}@${importPath}` when two reachable types share a name. An identifier
49
+ * in the text is matched against the name part of each id, the id that is the
50
+ * name itself first. Known limitation: a type imported under another name
51
+ * (`import { Item as OtherItem }`) appears in the text by its local name, which
52
+ * no id carries, so it stays unlinked.
53
+ */
54
+ export declare function typeSegments(ref: ApiTypeRef): TypeSegment[];
55
+ /** A parameter as a signature writes it, `config?: ToastConfig` or `...features: Feature[]`, with its type linked. */
56
+ export declare function paramSegments(param: ApiParam): TypeSegment[];
57
+ /**
58
+ * A parameter as its declaration writes it, default included:
59
+ * `config: ToastConfig = {}`. A default makes the parameter optional, so it
60
+ * goes without the `?`; one without a default reads as in a signature.
61
+ */
62
+ export declare function declaredParamSegments(param: ApiParam): TypeSegment[];
63
+ /** Whether a parameter list says more than its signature: a `@param` text or a default. */
64
+ export declare function documentsParams(params: readonly ApiParam[] | undefined): boolean;
65
+ /**
66
+ * What follows a setup entry's name on its signature line: the parameters and
67
+ * the return type of a function, the type of a token.
68
+ */
69
+ export declare function setupSignature(entry: ApiSetupEntry): TypeSegment[];
70
+ /**
71
+ * Split a type's `reachedFrom` (`parameter of show()`, `via options.action`)
72
+ * into its words and the code they end in, so the page can set the code apart.
73
+ */
74
+ export declare function splitReachedFrom(reachedFrom: string): {
75
+ text: string;
76
+ code?: string;
77
+ };
78
+ //# sourceMappingURL=service-page.model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-page.model.d.ts","sourceRoot":"","sources":["../../src/service-page/service-page.model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,kHAAkH;AAClH,MAAM,WAAW,QAAQ;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;CAChB;AAED,iGAAiG;AACjG,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,8CAA8C;AAC9C,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,EAAE,CAc9D;AAKD,kGAAkG;AAClG,MAAM,WAAW,cAAc;IAC3B,qGAAqG;IACrG,OAAO,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,EAAE,cAAc,GAAG,MAAM,CAYlH;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,WAAW,EAAE,CAsB3D;AAED,sHAAsH;AACtH,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,GAAG,WAAW,EAAE,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,QAAQ,GAAG,WAAW,EAAE,CAGpE;AAED,2FAA2F;AAC3F,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,GAAG,SAAS,GAAG,OAAO,CAEhF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,WAAW,EAAE,CAMlE;AAiBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAKrF"}
@@ -0,0 +1,130 @@
1
+ /** The TOC id of a documented type's card. */
2
+ export function typeAnchor(typeId) {
3
+ return `type-${typeId}`;
4
+ }
5
+ /**
6
+ * The sections of an API page in the order the page shows them. Usage is
7
+ * always there, being generated; any other section only when it has content.
8
+ * A service has properties and methods, a pipe its transform signatures.
9
+ */
10
+ export function buildApiPageToc(page) {
11
+ const sections = [
12
+ ['usage', 'Usage', true],
13
+ ['setup', 'Setup', page.setup.length > 0],
14
+ ['examples', 'Examples', page.examples.length > 0],
15
+ ['properties', 'Properties', !!page.properties?.length],
16
+ ['methods', 'Methods', !!page.methods?.length],
17
+ ['transform', 'Transform', !!page.transform?.length],
18
+ ['types', 'Types', page.types.length > 0]
19
+ ];
20
+ const toc = sections.filter(([, , present]) => present).map(([id, label]) => ({ id, label, level: 1 }));
21
+ for (const type of page.types)
22
+ toc.push({ id: typeAnchor(type.id), label: type.name, level: 2 });
23
+ return toc;
24
+ }
25
+ /** How far down the view, as a share of its height, a section's heading has to come to make the section active. */
26
+ const ACTIVATION_LINE = 0.25;
27
+ /**
28
+ * The entry the "On this page" list marks. During a jump, the one the reader
29
+ * jumped to, whatever the jump scrolls past. Otherwise the last section whose
30
+ * heading has come up to a line a quarter down the view, except at the bottom
31
+ * of the page: the sections there can never come up to the line, so the last
32
+ * one in view is active.
33
+ */
34
+ export function activeTocId({ anchors, scrollTop, clientHeight, scrollHeight, jumpTarget }) {
35
+ if (jumpTarget)
36
+ return jumpTarget;
37
+ if (scrollTop + clientHeight >= scrollHeight - 1) {
38
+ const inView = anchors.filter((anchor) => anchor.top >= scrollTop && anchor.top < scrollTop + clientHeight).at(-1);
39
+ if (inView)
40
+ return inView.id;
41
+ }
42
+ const line = scrollTop + clientHeight * ACTIVATION_LINE;
43
+ return (anchors.filter((anchor) => anchor.top <= line).at(-1) ?? anchors[0])?.id ?? 'usage';
44
+ }
45
+ const IDENTIFIER = /[A-Za-z_$][\w$]*/g;
46
+ /**
47
+ * Cut a type as written into plain text and the names of documented types, so
48
+ * the page can turn those names into links to their cards.
49
+ *
50
+ * `ref.links` holds type ids, and an id is the type's name, or
51
+ * `${name}@${importPath}` when two reachable types share a name. An identifier
52
+ * in the text is matched against the name part of each id, the id that is the
53
+ * name itself first. Known limitation: a type imported under another name
54
+ * (`import { Item as OtherItem }`) appears in the text by its local name, which
55
+ * no id carries, so it stays unlinked.
56
+ */
57
+ export function typeSegments(ref) {
58
+ const links = ref.links ?? [];
59
+ const segments = [];
60
+ let plain = '';
61
+ let last = 0;
62
+ for (const match of ref.text.matchAll(IDENTIFIER)) {
63
+ const name = match[0];
64
+ const id = links.find((candidate) => candidate === name) ?? links.find((candidate) => candidate.split('@')[0] === name);
65
+ if (!id)
66
+ continue;
67
+ plain += ref.text.slice(last, match.index);
68
+ if (plain)
69
+ segments.push({ text: plain });
70
+ segments.push({ text: name, link: typeAnchor(id) });
71
+ plain = '';
72
+ last = match.index + name.length;
73
+ }
74
+ plain += ref.text.slice(last);
75
+ if (plain || !segments.length)
76
+ segments.push({ text: plain });
77
+ return segments;
78
+ }
79
+ /** A parameter as a signature writes it, `config?: ToastConfig` or `...features: Feature[]`, with its type linked. */
80
+ export function paramSegments(param) {
81
+ return joinSegments(`${param.rest ? '...' : ''}${param.name}${param.optional ? '?' : ''}: `, typeSegments(param.type));
82
+ }
83
+ /**
84
+ * A parameter as its declaration writes it, default included:
85
+ * `config: ToastConfig = {}`. A default makes the parameter optional, so it
86
+ * goes without the `?`; one without a default reads as in a signature.
87
+ */
88
+ export function declaredParamSegments(param) {
89
+ if (param.defaultValue === undefined)
90
+ return paramSegments(param);
91
+ return joinSegments(`${param.rest ? '...' : ''}${param.name}: `, typeSegments(param.type), ` = ${param.defaultValue}`);
92
+ }
93
+ /** Whether a parameter list says more than its signature: a `@param` text or a default. */
94
+ export function documentsParams(params) {
95
+ return !!params?.some((param) => param.description || param.defaultValue !== undefined);
96
+ }
97
+ /**
98
+ * What follows a setup entry's name on its signature line: the parameters and
99
+ * the return type of a function, the type of a token.
100
+ */
101
+ export function setupSignature(entry) {
102
+ if (entry.kind === 'token')
103
+ return entry.tokenType ? joinSegments(': ', typeSegments(entry.tokenType)) : [];
104
+ const params = (entry.params ?? []).flatMap((param, index) => (index ? [', ', paramSegments(param)] : [paramSegments(param)]));
105
+ return joinSegments('(', ...params, ')', ...(entry.returnType ? [': ', typeSegments(entry.returnType)] : []));
106
+ }
107
+ /** Concatenate text and segments, merging neighbouring plain text into one segment. */
108
+ function joinSegments(...parts) {
109
+ const joined = [];
110
+ for (const segment of parts.flatMap((part) => (typeof part === 'string' ? [{ text: part }] : part))) {
111
+ const previous = joined.at(-1);
112
+ if (!segment.text)
113
+ continue;
114
+ if (previous && !previous.link && !segment.link)
115
+ previous.text += segment.text;
116
+ else
117
+ joined.push({ ...segment });
118
+ }
119
+ return joined;
120
+ }
121
+ /**
122
+ * Split a type's `reachedFrom` (`parameter of show()`, `via options.action`)
123
+ * into its words and the code they end in, so the page can set the code apart.
124
+ */
125
+ export function splitReachedFrom(reachedFrom) {
126
+ const space = reachedFrom.lastIndexOf(' ');
127
+ if (space < 0)
128
+ return { text: reachedFrom };
129
+ return { text: reachedFrom.slice(0, space + 1), code: reachedFrom.slice(space + 1) };
130
+ }
@@ -0,0 +1,28 @@
1
+ import type { ApiSetupEntry, ScannedService } from '@ng-prism/core/plugin';
2
+ /** What the import lines read: a scanned service or pipe. */
3
+ export interface UsageSubject {
4
+ className: string;
5
+ importPath?: string;
6
+ api: {
7
+ setup: ApiSetupEntry[];
8
+ };
9
+ }
10
+ /**
11
+ * The snippet that shows how to bring a service into an application: the
12
+ * imports, the `providers` entries its setup asks for, and the `inject()` call.
13
+ *
14
+ * It is generated from the scanned API, not taken from the author, so it says
15
+ * nothing about values: a setup function's arguments and a token's value are
16
+ * left as `…` for the reader to fill in.
17
+ */
18
+ export declare function generateServiceUsage(service: ScannedService): string;
19
+ /**
20
+ * One line per entry point, the subject's first, each naming the subject
21
+ * before its setup entries. Shared with the pipe snippet.
22
+ */
23
+ export declare function importLines({ className, importPath, api }: UsageSubject): string[];
24
+ /** The `app.config.ts` block with one `providers` entry per setup entry, or nothing without setup. Shared with the pipe snippet. */
25
+ export declare function setupBlock(setup: ApiSetupEntry[]): string[];
26
+ /** The blocks of a snippet, each a list of lines, with a blank line between them; an empty block is left out. */
27
+ export declare function joinUsageBlocks(blocks: string[][]): string;
28
+ //# sourceMappingURL=service-usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-usage.d.ts","sourceRoot":"","sources":["../../src/service-page/service-usage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE3E,6DAA6D;AAC7D,MAAM,WAAW,YAAY;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE;QAAE,KAAK,EAAE,aAAa,EAAE,CAAA;KAAE,CAAC;CACnC;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAmBpE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,YAAY,GAAG,MAAM,EAAE,CASlF;AAED,oIAAoI;AACpI,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,MAAM,EAAE,CAG3D;AAED,iHAAiH;AACjH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,CAK1D"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The snippet that shows how to bring a service into an application: the
3
+ * imports, the `providers` entries its setup asks for, and the `inject()` call.
4
+ *
5
+ * It is generated from the scanned API, not taken from the author, so it says
6
+ * nothing about values: a setup function's arguments and a token's value are
7
+ * left as `…` for the reader to fill in.
8
+ */
9
+ export function generateServiceUsage(service) {
10
+ const { className, provision, api } = service;
11
+ const variable = className.charAt(0).toLowerCase() + className.slice(1);
12
+ const blocks = [importLines(service), setupBlock(api.setup)];
13
+ // A setup function of a service provided by hand normally provides it; a token never does.
14
+ if (provision === 'manual' && !api.setup.some((entry) => entry.kind === 'function')) {
15
+ blocks.push([
16
+ '// the component that uses it',
17
+ `@Component({ …, providers: [${className}] })`,
18
+ 'export class MyComponent {',
19
+ ` private readonly ${variable} = inject(${className});`,
20
+ '}'
21
+ ]);
22
+ }
23
+ else {
24
+ blocks.push(['// in a component or service', `private readonly ${variable} = inject(${className});`]);
25
+ }
26
+ return joinUsageBlocks(blocks);
27
+ }
28
+ /**
29
+ * One line per entry point, the subject's first, each naming the subject
30
+ * before its setup entries. Shared with the pipe snippet.
31
+ */
32
+ export function importLines({ className, importPath, api }) {
33
+ const groups = new Map();
34
+ const add = (path, name) => groups.set(path, [...(groups.get(path) ?? []), name]);
35
+ // A class the scanner could not place has no import path; there is nothing true to write for it.
36
+ if (importPath)
37
+ add(importPath, className);
38
+ for (const entry of api.setup)
39
+ add(entry.importPath, entry.name);
40
+ return [...groups].map(([path, names]) => `import { ${names.join(', ')} } from '${path}';`);
41
+ }
42
+ /** The `app.config.ts` block with one `providers` entry per setup entry, or nothing without setup. Shared with the pipe snippet. */
43
+ export function setupBlock(setup) {
44
+ if (!setup.length)
45
+ return [];
46
+ return ['// app.config.ts', `providers: [${setup.map(providerEntry).join(', ')}]`];
47
+ }
48
+ /** The blocks of a snippet, each a list of lines, with a blank line between them; an empty block is left out. */
49
+ export function joinUsageBlocks(blocks) {
50
+ return blocks
51
+ .filter((lines) => lines.length)
52
+ .map((lines) => lines.join('\n'))
53
+ .join('\n\n');
54
+ }
55
+ function providerEntry(entry) {
56
+ if (entry.kind === 'token')
57
+ return `{ provide: ${entry.name}, useValue: … }`;
58
+ // A rest parameter can be left out as well as an optional one.
59
+ const callableBare = (entry.params ?? []).every((param) => param.optional || param.rest);
60
+ return `${entry.name}(${callableBare ? '' : '…'})`;
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-prism/plugin-jsdoc",
3
- "version": "22.6.0",
3
+ "version": "22.7.0-beta.0",
4
4
  "description": "JSDoc API documentation panel for ng-prism.",
5
5
  "ng-add": {
6
6
  "save": "devDependencies"
@@ -35,8 +35,9 @@
35
35
  "!**/*.tsbuildinfo"
36
36
  ],
37
37
  "peerDependencies": {
38
+ "@angular/common": ">=20.0.0",
38
39
  "@angular/core": ">=20.0.0",
39
- "@ng-prism/core": "^22.6.0-0",
40
+ "@ng-prism/core": "^22.7.0-0",
40
41
  "highlight.js": ">=11.0.0",
41
42
  "ngx-highlightjs": ">=14.0.0",
42
43
  "typescript": ">=5.5.0",