@ng-prism/plugin-jsdoc 22.3.0 → 22.4.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.
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
API documentation plugin for [@ng-prism/core](https://github.com/dyingangel666/ng-prism). Extracts JSDoc comments from component source code at build time and renders an interactive API panel.
|
|
4
4
|
|
|
5
|
-
> **Full documentation:** [ng-prism Docs
|
|
5
|
+
> **Full documentation:** [ng-prism Docs: JSDoc Plugin](https://dyingangel666.github.io/ng-prism/#/plugins/jsdoc)
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -49,11 +49,11 @@ JSDoc descriptions are rendered as Markdown:
|
|
|
49
49
|
- Headings (`#`, `##`, `###`)
|
|
50
50
|
- Lists (ordered/unordered) and inline code (`` `code` ``)
|
|
51
51
|
- Bold (`**text**`), italic (`*text*`), and links (`[text](url)`)
|
|
52
|
-
- Fenced code blocks with a language hint (` ```html `, ` ```scss
|
|
52
|
+
- Fenced code blocks with a language hint (` ```html `, ` ```scss `, ...)
|
|
53
53
|
|
|
54
54
|
Class descriptions are rendered as block Markdown; input/output/method descriptions are
|
|
55
55
|
rendered inline (no wrapping `<p>`). Existing plain-text descriptions keep working
|
|
56
|
-
unchanged
|
|
56
|
+
unchanged, since plain text is valid Markdown.
|
|
57
57
|
|
|
58
58
|
````typescript
|
|
59
59
|
/**
|
|
@@ -61,8 +61,8 @@ unchanged — plain text is valid Markdown.
|
|
|
61
61
|
*
|
|
62
62
|
* ## Size
|
|
63
63
|
*
|
|
64
|
-
* - `small` (4px)
|
|
65
|
-
* - `large` (9px, default)
|
|
64
|
+
* - `small` (4px): inline indicators
|
|
65
|
+
* - `large` (9px, default): section-level loading
|
|
66
66
|
*
|
|
67
67
|
* @example
|
|
68
68
|
* ```html
|
|
@@ -72,9 +72,9 @@ unchanged — plain text is valid Markdown.
|
|
|
72
72
|
````
|
|
73
73
|
|
|
74
74
|
`@example` blocks accept a fenced code block with a language hint. The language is
|
|
75
|
-
passed through to `ngx-highlightjs` (`html`, `scss`, `typescript`,
|
|
75
|
+
passed through to `ngx-highlightjs` (`html`, `scss`, `typescript`, ...). Examples without
|
|
76
76
|
fences fall back to `typescript` for backwards compatibility. Make sure the languages
|
|
77
|
-
you reference are registered with `highlight.js` in your app
|
|
77
|
+
you reference are registered with `highlight.js` in your app. `highlight.js`'s core
|
|
78
78
|
language set is loaded by default, but additional languages must be registered
|
|
79
79
|
explicitly by the consumer.
|
|
80
80
|
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import type { JsDocData } from './jsdoc.types.js';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* `baseClasses` come from the scanner, nearest first. Members are read from
|
|
4
|
+
* the class and then from each of them; the first class that declares a
|
|
5
|
+
* member decides its tags, as a redeclaration replaces the inherited one.
|
|
6
|
+
*/
|
|
7
|
+
export declare function extractJsDocData(filePath: string, className: string, baseClasses?: {
|
|
8
|
+
className: string;
|
|
9
|
+
filePath: string;
|
|
10
|
+
}[]): JsDocData | null;
|
|
3
11
|
//# sourceMappingURL=jsdoc-extractor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsdoc-extractor.d.ts","sourceRoot":"","sources":["../src/jsdoc-extractor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAkC,MAAM,kBAAkB,CAAC;AAElF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"jsdoc-extractor.d.ts","sourceRoot":"","sources":["../src/jsdoc-extractor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAkC,MAAM,kBAAkB,CAAC;AAElF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAAO,GAAG,SAAS,GAAG,IAAI,CAyBnJ"}
|
package/dist/jsdoc-extractor.js
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import ts from 'typescript';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
/**
|
|
4
|
+
* `baseClasses` come from the scanner, nearest first. Members are read from
|
|
5
|
+
* the class and then from each of them; the first class that declares a
|
|
6
|
+
* member decides its tags, as a redeclaration replaces the inherited one.
|
|
7
|
+
*/
|
|
8
|
+
export function extractJsDocData(filePath, className, baseClasses = []) {
|
|
9
|
+
const sourceFiles = new Map();
|
|
10
|
+
const findClass = (path, name) => {
|
|
11
|
+
let sourceFile = sourceFiles.get(path);
|
|
12
|
+
if (!sourceFile) {
|
|
13
|
+
sourceFile = ts.createSourceFile(path, readFileSync(path, 'utf-8'), ts.ScriptTarget.ES2022, true);
|
|
14
|
+
sourceFiles.set(path, sourceFile);
|
|
15
|
+
}
|
|
16
|
+
return findClassByName(sourceFile, name);
|
|
17
|
+
};
|
|
18
|
+
const classDecl = findClass(filePath, className);
|
|
7
19
|
if (!classDecl)
|
|
8
20
|
return null;
|
|
21
|
+
const hierarchy = [classDecl, ...baseClasses.flatMap((base) => findClass(base.filePath, base.className) ?? [])];
|
|
9
22
|
return {
|
|
10
23
|
classDescription: extractDescription(classDecl),
|
|
11
24
|
classTags: extractTags(classDecl),
|
|
12
|
-
memberTags: extractAllMemberTags(
|
|
13
|
-
methods: extractPublicMethods(
|
|
25
|
+
memberTags: extractAllMemberTags(hierarchy),
|
|
26
|
+
methods: extractPublicMethods(hierarchy)
|
|
14
27
|
};
|
|
15
28
|
}
|
|
16
29
|
function findClassByName(sourceFile, className) {
|
|
@@ -90,18 +103,37 @@ function tagCommentToString(comment) {
|
|
|
90
103
|
.join('')
|
|
91
104
|
.trim() || undefined);
|
|
92
105
|
}
|
|
93
|
-
function extractAllMemberTags(
|
|
106
|
+
function extractAllMemberTags(hierarchy) {
|
|
94
107
|
const result = {};
|
|
108
|
+
const shadowed = new Set();
|
|
109
|
+
for (const classDecl of hierarchy) {
|
|
110
|
+
for (const member of classDecl.members) {
|
|
111
|
+
const name = getMemberName(member);
|
|
112
|
+
if (!name || shadowed.has(name))
|
|
113
|
+
continue;
|
|
114
|
+
const tags = ts.getJSDocTags(member);
|
|
115
|
+
if (tags.length === 0)
|
|
116
|
+
continue;
|
|
117
|
+
result[name] = buildTagsFromList(tags);
|
|
118
|
+
}
|
|
119
|
+
addMemberNames(classDecl, shadowed);
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A member with JSDoc of its own hides the same name further up, as it does
|
|
125
|
+
* for TypeScript and for the description the scanner reads; one without
|
|
126
|
+
* keeps the documentation it inherits. Applied after the whole class, so the
|
|
127
|
+
* getter, setter or overloads next to a member are read too. A static member
|
|
128
|
+
* hides nothing, since inputs, outputs and methods are instance members.
|
|
129
|
+
*/
|
|
130
|
+
function addMemberNames(classDecl, names) {
|
|
95
131
|
for (const member of classDecl.members) {
|
|
96
132
|
const name = getMemberName(member);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (tags.length === 0)
|
|
101
|
-
continue;
|
|
102
|
-
result[name] = buildTagsFromList(tags);
|
|
133
|
+
const isStatic = ts.canHaveModifiers(member) && ts.getModifiers(member)?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
|
|
134
|
+
if (name && !isStatic && ts.getJSDocCommentsAndTags(member).length > 0)
|
|
135
|
+
names.add(name);
|
|
103
136
|
}
|
|
104
|
-
return result;
|
|
105
137
|
}
|
|
106
138
|
function getMemberName(member) {
|
|
107
139
|
if (!member.name)
|
|
@@ -122,29 +154,35 @@ const LIFECYCLE_HOOKS = new Set([
|
|
|
122
154
|
'ngAfterViewInit',
|
|
123
155
|
'ngAfterViewChecked'
|
|
124
156
|
]);
|
|
125
|
-
function extractPublicMethods(
|
|
157
|
+
function extractPublicMethods(hierarchy) {
|
|
126
158
|
const result = [];
|
|
127
|
-
const
|
|
128
|
-
for (const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
159
|
+
const shadowed = new Set();
|
|
160
|
+
for (const classDecl of hierarchy) {
|
|
161
|
+
const sourceFile = classDecl.getSourceFile();
|
|
162
|
+
for (const member of classDecl.members) {
|
|
163
|
+
if (!ts.isMethodDeclaration(member))
|
|
164
|
+
continue;
|
|
165
|
+
const name = getMemberName(member);
|
|
166
|
+
if (!name || shadowed.has(name))
|
|
167
|
+
continue;
|
|
168
|
+
if (name.startsWith('_'))
|
|
169
|
+
continue;
|
|
170
|
+
if (LIFECYCLE_HOOKS.has(name))
|
|
171
|
+
continue;
|
|
172
|
+
const isPrivate = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.PrivateKeyword || m.kind === ts.SyntaxKind.ProtectedKeyword);
|
|
173
|
+
if (isPrivate)
|
|
174
|
+
continue;
|
|
175
|
+
if (!hasDirectJsDoc(member, sourceFile))
|
|
176
|
+
continue;
|
|
177
|
+
const description = extractDescription(member);
|
|
178
|
+
const tags = extractTags(member);
|
|
179
|
+
const params = extractParamDocs(member);
|
|
180
|
+
const returnType = member.type ? member.type.getText() : undefined;
|
|
181
|
+
if (!description && Object.keys(tags).length === 0)
|
|
182
|
+
continue;
|
|
183
|
+
result.push({ name, description, tags, params, returnType });
|
|
184
|
+
}
|
|
185
|
+
addMemberNames(classDecl, shadowed);
|
|
148
186
|
}
|
|
149
187
|
return result;
|
|
150
188
|
}
|
|
@@ -427,7 +427,7 @@ export class JsDocPanelComponent {
|
|
|
427
427
|
classDescription = computed(() => this.jsdocData()?.classDescription, /* @ts-ignore */
|
|
428
428
|
...(ngDevMode ? [{ debugName: "classDescription" }] : /* istanbul ignore next */ []));
|
|
429
429
|
// Source is component-author JSDoc compiled into the manifest at build time,
|
|
430
|
-
// not user input
|
|
430
|
+
// not user input, so bypassing sanitization is safe here.
|
|
431
431
|
renderedClassDescription = computed(() => {
|
|
432
432
|
const html = renderBlockMarkdown(this.classDescription());
|
|
433
433
|
return html ? this.sanitizer.bypassSecurityTrustHtml(html) : null;
|
package/dist/jsdoc-plugin.js
CHANGED
|
@@ -3,7 +3,7 @@ export function jsDocPlugin() {
|
|
|
3
3
|
return {
|
|
4
4
|
name: '@ng-prism/plugin-jsdoc',
|
|
5
5
|
async onComponentScanned(component) {
|
|
6
|
-
const jsdoc = extractJsDocData(component.filePath, component.className);
|
|
6
|
+
const jsdoc = extractJsDocData(component.filePath, component.className, component.baseClasses);
|
|
7
7
|
if (!jsdoc)
|
|
8
8
|
return;
|
|
9
9
|
return {
|
|
@@ -12,10 +12,10 @@ function injectPlugin(options) {
|
|
|
12
12
|
call: PLUGIN_CALL
|
|
13
13
|
});
|
|
14
14
|
if (changed) {
|
|
15
|
-
context.logger.info(` ${PLUGIN_IMPORT_FROM}
|
|
15
|
+
context.logger.info(` ${PLUGIN_IMPORT_FROM} added to ${configPath}`);
|
|
16
16
|
}
|
|
17
17
|
else {
|
|
18
|
-
context.logger.info(` ${PLUGIN_IMPORT_FROM} already configured
|
|
18
|
+
context.logger.info(` ${PLUGIN_IMPORT_FROM} already configured, skipped`);
|
|
19
19
|
}
|
|
20
20
|
return tree;
|
|
21
21
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-prism/plugin-jsdoc",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.4.0",
|
|
4
4
|
"description": "JSDoc API documentation panel for ng-prism.",
|
|
5
5
|
"ng-add": {
|
|
6
6
|
"save": "devDependencies"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
],
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@angular/core": ">=20.0.0",
|
|
39
|
-
"@ng-prism/core": "^22.
|
|
39
|
+
"@ng-prism/core": "^22.4.0-0",
|
|
40
40
|
"highlight.js": ">=11.0.0",
|
|
41
41
|
"ngx-highlightjs": ">=14.0.0",
|
|
42
42
|
"typescript": ">=5.5.0",
|