@oicl-lit/analyzer 0.14.1
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/LICENSE +28 -0
- package/README.md +76 -0
- package/index.d.ts +10 -0
- package/index.d.ts.map +1 -0
- package/index.js +10 -0
- package/index.js.map +1 -0
- package/lib/analyzer.d.ts +47 -0
- package/lib/analyzer.d.ts.map +1 -0
- package/lib/analyzer.js +90 -0
- package/lib/analyzer.js.map +1 -0
- package/lib/custom-elements/custom-elements.d.ts +33 -0
- package/lib/custom-elements/custom-elements.d.ts.map +1 -0
- package/lib/custom-elements/custom-elements.js +124 -0
- package/lib/custom-elements/custom-elements.js.map +1 -0
- package/lib/custom-elements/events.d.ts +19 -0
- package/lib/custom-elements/events.d.ts.map +1 -0
- package/lib/custom-elements/events.js +25 -0
- package/lib/custom-elements/events.js.map +1 -0
- package/lib/diagnostic-code.d.ts +21 -0
- package/lib/diagnostic-code.d.ts.map +1 -0
- package/lib/diagnostic-code.js +20 -0
- package/lib/diagnostic-code.js.map +1 -0
- package/lib/errors.d.ts +24 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +17 -0
- package/lib/errors.js.map +1 -0
- package/lib/javascript/classes.d.ts +50 -0
- package/lib/javascript/classes.d.ts.map +1 -0
- package/lib/javascript/classes.js +307 -0
- package/lib/javascript/classes.js.map +1 -0
- package/lib/javascript/functions.d.ts +31 -0
- package/lib/javascript/functions.d.ts.map +1 -0
- package/lib/javascript/functions.js +144 -0
- package/lib/javascript/functions.js.map +1 -0
- package/lib/javascript/jsdoc.d.ts +67 -0
- package/lib/javascript/jsdoc.d.ts.map +1 -0
- package/lib/javascript/jsdoc.js +244 -0
- package/lib/javascript/jsdoc.js.map +1 -0
- package/lib/javascript/mixins.d.ts +45 -0
- package/lib/javascript/mixins.d.ts.map +1 -0
- package/lib/javascript/mixins.js +147 -0
- package/lib/javascript/mixins.js.map +1 -0
- package/lib/javascript/modules.d.ts +42 -0
- package/lib/javascript/modules.d.ts.map +1 -0
- package/lib/javascript/modules.js +277 -0
- package/lib/javascript/modules.js.map +1 -0
- package/lib/javascript/packages.d.ts +18 -0
- package/lib/javascript/packages.d.ts.map +1 -0
- package/lib/javascript/packages.js +53 -0
- package/lib/javascript/packages.js.map +1 -0
- package/lib/javascript/variables.d.ts +29 -0
- package/lib/javascript/variables.d.ts.map +1 -0
- package/lib/javascript/variables.js +143 -0
- package/lib/javascript/variables.js.map +1 -0
- package/lib/lit/decorators.d.ts +36 -0
- package/lib/lit/decorators.d.ts.map +1 -0
- package/lib/lit/decorators.js +32 -0
- package/lib/lit/decorators.js.map +1 -0
- package/lib/lit/lit-element.d.ts +39 -0
- package/lib/lit/lit-element.d.ts.map +1 -0
- package/lib/lit/lit-element.js +96 -0
- package/lib/lit/lit-element.js.map +1 -0
- package/lib/lit/modules.d.ts +28 -0
- package/lib/lit/modules.d.ts.map +1 -0
- package/lib/lit/modules.js +62 -0
- package/lib/lit/modules.js.map +1 -0
- package/lib/lit/properties.d.ts +43 -0
- package/lib/lit/properties.d.ts.map +1 -0
- package/lib/lit/properties.js +268 -0
- package/lib/lit/properties.js.map +1 -0
- package/lib/lit/template.d.ts +110 -0
- package/lib/lit/template.d.ts.map +1 -0
- package/lib/lit/template.js +412 -0
- package/lib/lit/template.js.map +1 -0
- package/lib/lit-element/decorators.d.ts +11 -0
- package/lib/lit-element/decorators.d.ts.map +1 -0
- package/lib/lit-element/decorators.js +11 -0
- package/lib/lit-element/decorators.js.map +1 -0
- package/lib/lit-element/lit-element.d.ts +11 -0
- package/lib/lit-element/lit-element.d.ts.map +1 -0
- package/lib/lit-element/lit-element.js +11 -0
- package/lib/lit-element/lit-element.js.map +1 -0
- package/lib/lit-element/properties.d.ts +11 -0
- package/lib/lit-element/properties.d.ts.map +1 -0
- package/lib/lit-element/properties.js +11 -0
- package/lib/lit-element/properties.js.map +1 -0
- package/lib/model.d.ts +506 -0
- package/lib/model.d.ts.map +1 -0
- package/lib/model.js +392 -0
- package/lib/model.js.map +1 -0
- package/lib/package-analyzer.d.ts +25 -0
- package/lib/package-analyzer.d.ts.map +1 -0
- package/lib/package-analyzer.js +81 -0
- package/lib/package-analyzer.js.map +1 -0
- package/lib/paths.d.ts +24 -0
- package/lib/paths.d.ts.map +1 -0
- package/lib/paths.js +35 -0
- package/lib/paths.js.map +1 -0
- package/lib/references.d.ts +107 -0
- package/lib/references.d.ts.map +1 -0
- package/lib/references.js +345 -0
- package/lib/references.js.map +1 -0
- package/lib/types.d.ts +25 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +257 -0
- package/lib/types.js.map +1 -0
- package/lib/utils.d.ts +22 -0
- package/lib/utils.d.ts.map +1 -0
- package/lib/utils.js +51 -0
- package/lib/utils.js.map +1 -0
- package/package-analyzer.d.ts +8 -0
- package/package-analyzer.d.ts.map +1 -0
- package/package-analyzer.js +8 -0
- package/package-analyzer.js.map +1 -0
- package/package.json +109 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* @fileoverview
|
|
8
|
+
*
|
|
9
|
+
* Utilities for analyzing JSDoc comments
|
|
10
|
+
*/
|
|
11
|
+
import type ts from 'typescript';
|
|
12
|
+
import { Described, TypedNamedDescribed, DeprecatableDescribed, AnalyzerInterface } from '../model.js';
|
|
13
|
+
export type TypeScript = typeof ts;
|
|
14
|
+
/**
|
|
15
|
+
* @fileoverview
|
|
16
|
+
*
|
|
17
|
+
* Utilities for parsing JSDoc comments.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Returns true if given node has a JSDoc tag
|
|
21
|
+
*/
|
|
22
|
+
export declare const hasJSDocTag: (ts: TypeScript, node: ts.Node, tag: string) => boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Parses name, type, and description from JSDoc tag for things like `@fires`.
|
|
25
|
+
*
|
|
26
|
+
* Supports the following patterns following the tag (TS parses the tag for us):
|
|
27
|
+
* * @fires event-name
|
|
28
|
+
* * @fires event-name description
|
|
29
|
+
* * @fires event-name - description
|
|
30
|
+
* * @fires event-name: description
|
|
31
|
+
* * @fires event-name {Type}
|
|
32
|
+
* * @fires event-name {Type} description
|
|
33
|
+
* * @fires event-name {Type} - description
|
|
34
|
+
* * @fires event-name {Type}: description
|
|
35
|
+
* * @fires {Type} event-name
|
|
36
|
+
* * @fires {Type} event-name description
|
|
37
|
+
* * @fires {Type} event-name - description
|
|
38
|
+
* * @fires {Type} event-name: description
|
|
39
|
+
* * @slot name
|
|
40
|
+
* * @slot name description
|
|
41
|
+
* * @slot name - description
|
|
42
|
+
* * @slot name: description
|
|
43
|
+
* * @cssProp [--name=default]
|
|
44
|
+
* * @cssProp [--name=default] description
|
|
45
|
+
* * @cssProp [--name=default] - description
|
|
46
|
+
* * @cssProp [--name=default]: description
|
|
47
|
+
* * @cssprop {<color>} [--name=default]
|
|
48
|
+
* * @cssprop {<color>} [--name=default] description
|
|
49
|
+
* * @cssprop {<color>} [--name=default] - description
|
|
50
|
+
* * @cssprop {<color>} [--name=default]: description
|
|
51
|
+
*/
|
|
52
|
+
export declare const parseNamedTypedJSDocInfo: (tag: ts.JSDocTag, analyzer: AnalyzerInterface) => TypedNamedDescribed | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Parses the description from JSDoc tag for things like `@return`.
|
|
55
|
+
*/
|
|
56
|
+
export declare const parseJSDocDescription: (tag: ts.JSDocTag, analyzer: AnalyzerInterface) => Described | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Parse summary, description, and deprecated information from JSDoc comments on
|
|
59
|
+
* a given node.
|
|
60
|
+
*/
|
|
61
|
+
export declare const parseNodeJSDocInfo: (node: ts.Node, analyzer: AnalyzerInterface) => DeprecatableDescribed;
|
|
62
|
+
/**
|
|
63
|
+
* Parse summary, description, and deprecated information from JSDoc comments on
|
|
64
|
+
* a given source file.
|
|
65
|
+
*/
|
|
66
|
+
export declare const parseModuleJSDocInfo: (sourceFile: ts.SourceFile, analyzer: AnalyzerInterface) => DeprecatableDescribed;
|
|
67
|
+
//# sourceMappingURL=jsdoc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsdoc.d.ts","sourceRoot":"","sources":["../../src/lib/javascript/jsdoc.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,EAElB,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,UAAU,GAAG,OAAO,EAAE,CAAC;AAEnC;;;;GAIG;AAEH;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,IAAI,UAAU,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,MAAM,YAErE,CAAC;AA0CF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,wBAAwB,GACnC,KAAK,EAAE,CAAC,QAAQ,EAChB,UAAU,iBAAiB,oCAiC5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAChC,KAAK,EAAE,CAAC,QAAQ,EAChB,UAAU,iBAAiB,KAC1B,SAAS,GAAG,SAMd,CAAC;AAoFF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC7B,MAAM,EAAE,CAAC,IAAI,EACb,UAAU,iBAAiB,KAC1B,qBA+BF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAC/B,YAAY,EAAE,CAAC,UAAU,EACzB,UAAU,iBAAiB,0BAmB5B,CAAC"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
*/
|
|
6
|
+
import { createDiagnostic } from '../errors.js';
|
|
7
|
+
/**
|
|
8
|
+
* @fileoverview
|
|
9
|
+
*
|
|
10
|
+
* Utilities for parsing JSDoc comments.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Returns true if given node has a JSDoc tag
|
|
14
|
+
*/
|
|
15
|
+
export const hasJSDocTag = (ts, node, tag) => {
|
|
16
|
+
return ts.getJSDocTags(node).some((t) => t.tagName.text === tag);
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Remove line feeds from JSDoc comments, so they are normalized to
|
|
20
|
+
* unix `\n` line endings.
|
|
21
|
+
*/
|
|
22
|
+
const normalizeLineEndings = (s) => s.replace(/\r/g, '').trim();
|
|
23
|
+
// Regex for parsing name, type, and description from JSDoc comments
|
|
24
|
+
const parseNameTypeDescRE = /^\[?(?<name>[^{}[\]\s=]+)(?:=(?<defaultValue>[^\]]+))?\]?(?:\s+{(?<type>.*)})?(?:\s+-\s+)?(?<description>[\s\S]*)$/m;
|
|
25
|
+
// Regex for parsing type, name, and description from JSDoc comments
|
|
26
|
+
const parseTypeNameDescRE = /^\{(?<type>.+)\}\s+\[?(?<name>[^{}[\]\s=]+)(?:=(?<defaultValue>[^\]]+))?\]?(?:\s+-\s+)?(?<description>[\s\S]*)$/m;
|
|
27
|
+
const getJSDocTagComment = (tag, analyzer) => {
|
|
28
|
+
let { comment } = tag;
|
|
29
|
+
if (comment === undefined) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(comment)) {
|
|
33
|
+
comment = comment.map((c) => c.text).join('');
|
|
34
|
+
}
|
|
35
|
+
if (typeof comment !== 'string') {
|
|
36
|
+
analyzer.addDiagnostic(createDiagnostic({
|
|
37
|
+
typescript: analyzer.typescript,
|
|
38
|
+
node: tag,
|
|
39
|
+
message: `JSDoc error: unsupported node type`,
|
|
40
|
+
}));
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return normalizeLineEndings(comment).trim();
|
|
44
|
+
};
|
|
45
|
+
const isModuleJSDocTag = (tag) => tag.tagName.text === 'module' ||
|
|
46
|
+
tag.tagName.text === 'fileoverview' ||
|
|
47
|
+
tag.tagName.text === 'packageDocumentation';
|
|
48
|
+
/**
|
|
49
|
+
* Parses name, type, and description from JSDoc tag for things like `@fires`.
|
|
50
|
+
*
|
|
51
|
+
* Supports the following patterns following the tag (TS parses the tag for us):
|
|
52
|
+
* * @fires event-name
|
|
53
|
+
* * @fires event-name description
|
|
54
|
+
* * @fires event-name - description
|
|
55
|
+
* * @fires event-name: description
|
|
56
|
+
* * @fires event-name {Type}
|
|
57
|
+
* * @fires event-name {Type} description
|
|
58
|
+
* * @fires event-name {Type} - description
|
|
59
|
+
* * @fires event-name {Type}: description
|
|
60
|
+
* * @fires {Type} event-name
|
|
61
|
+
* * @fires {Type} event-name description
|
|
62
|
+
* * @fires {Type} event-name - description
|
|
63
|
+
* * @fires {Type} event-name: description
|
|
64
|
+
* * @slot name
|
|
65
|
+
* * @slot name description
|
|
66
|
+
* * @slot name - description
|
|
67
|
+
* * @slot name: description
|
|
68
|
+
* * @cssProp [--name=default]
|
|
69
|
+
* * @cssProp [--name=default] description
|
|
70
|
+
* * @cssProp [--name=default] - description
|
|
71
|
+
* * @cssProp [--name=default]: description
|
|
72
|
+
* * @cssprop {<color>} [--name=default]
|
|
73
|
+
* * @cssprop {<color>} [--name=default] description
|
|
74
|
+
* * @cssprop {<color>} [--name=default] - description
|
|
75
|
+
* * @cssprop {<color>} [--name=default]: description
|
|
76
|
+
*/
|
|
77
|
+
export const parseNamedTypedJSDocInfo = (tag, analyzer) => {
|
|
78
|
+
const comment = getJSDocTagComment(tag, analyzer);
|
|
79
|
+
if (comment == undefined) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
const regex = comment.charAt(0) === '{' ? parseTypeNameDescRE : parseNameTypeDescRE;
|
|
83
|
+
const nameTypeDesc = comment.match(regex);
|
|
84
|
+
if (nameTypeDesc === null) {
|
|
85
|
+
analyzer.addDiagnostic(createDiagnostic({
|
|
86
|
+
typescript: analyzer.typescript,
|
|
87
|
+
node: tag,
|
|
88
|
+
message: `JSDoc error: unexpected JSDoc format`,
|
|
89
|
+
}));
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const { name, type, defaultValue, description } = nameTypeDesc.groups;
|
|
93
|
+
const info = { name };
|
|
94
|
+
if (description.length > 0) {
|
|
95
|
+
info.description = normalizeLineEndings(description);
|
|
96
|
+
}
|
|
97
|
+
if (defaultValue?.length > 0) {
|
|
98
|
+
info.default = defaultValue;
|
|
99
|
+
}
|
|
100
|
+
if (tag.tagName.text.toLowerCase().startsWith('cssprop')) {
|
|
101
|
+
info.syntax = type;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
info.type = type;
|
|
105
|
+
}
|
|
106
|
+
return info;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Parses the description from JSDoc tag for things like `@return`.
|
|
110
|
+
*/
|
|
111
|
+
export const parseJSDocDescription = (tag, analyzer) => {
|
|
112
|
+
const description = getJSDocTagComment(tag, analyzer);
|
|
113
|
+
if (description == undefined || description.length === 0) {
|
|
114
|
+
return {};
|
|
115
|
+
}
|
|
116
|
+
return { description };
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Add `@description`, `@summary`, and `@deprecated` JSDoc tag info to the
|
|
120
|
+
* given info object.
|
|
121
|
+
*/
|
|
122
|
+
const addJSDocTagInfo = (info, jsDocTags, analyzer) => {
|
|
123
|
+
for (const tag of jsDocTags) {
|
|
124
|
+
const comment = getJSDocTagComment(tag, analyzer);
|
|
125
|
+
switch (tag.tagName.text.toLowerCase()) {
|
|
126
|
+
case 'description':
|
|
127
|
+
case 'fileoverview':
|
|
128
|
+
case 'packagedocumentation':
|
|
129
|
+
if (comment !== undefined) {
|
|
130
|
+
info.description = comment;
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
case 'summary':
|
|
134
|
+
if (comment !== undefined) {
|
|
135
|
+
info.summary = comment;
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
case 'deprecated':
|
|
139
|
+
info.deprecated = comment !== undefined ? comment : true;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const moduleJSDocsMap = new WeakMap();
|
|
145
|
+
/**
|
|
146
|
+
* Returns the module-level JSDoc comment blocks for a given source file.
|
|
147
|
+
*
|
|
148
|
+
* Note that TS does not have a concept of module-level JSDoc; if it
|
|
149
|
+
* exists, it will always be attached to the first statement in the module.
|
|
150
|
+
*
|
|
151
|
+
* Thus, we parse module-level documentation using the following heuristic:
|
|
152
|
+
* - If the first statement only has one JSDoc block, it is only treated as
|
|
153
|
+
* module documentation if it contains a `@module`, `@fileoverview`, or
|
|
154
|
+
* `@packageDocumentation` tag. This is required to disambiguate a module
|
|
155
|
+
* description (with an undocumented first statement) from documentation
|
|
156
|
+
* for the first statement.
|
|
157
|
+
* - If the first statement has more than one JSDoc block, we collect all
|
|
158
|
+
* but the last and use those, regardless of whether they contain one
|
|
159
|
+
* of the above module-designating tags (the last one is assumed to belong
|
|
160
|
+
* to the first statement).
|
|
161
|
+
*
|
|
162
|
+
* This function caches its result against the given sourceFile, since it is
|
|
163
|
+
* needed both to find the module comment and to filter out module comments
|
|
164
|
+
* node comments.
|
|
165
|
+
*/
|
|
166
|
+
const getModuleJSDocs = (typescript, sourceFile) => {
|
|
167
|
+
let moduleJSDocs = moduleJSDocsMap.get(sourceFile);
|
|
168
|
+
if (moduleJSDocs !== undefined) {
|
|
169
|
+
return moduleJSDocs;
|
|
170
|
+
}
|
|
171
|
+
// Get the first child in the sourceFile; note that returning the first child
|
|
172
|
+
// from `ts.forEachChild` is more robust than `sourceFile.getChildAt(0)`,
|
|
173
|
+
// since `forEachChild` flattens embedded arrays that the child APIs would
|
|
174
|
+
// otherwise return.
|
|
175
|
+
const firstChild = typescript.forEachChild(sourceFile, (n) => n);
|
|
176
|
+
if (firstChild === undefined) {
|
|
177
|
+
moduleJSDocs = [];
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
// Get the JSDoc blocks attached to the first child (they oddly show up
|
|
181
|
+
// in the node's children)
|
|
182
|
+
const jsDocs = firstChild.getChildren().filter(typescript.isJSDoc);
|
|
183
|
+
// If there is more than one leading JSDoc block, grab all but the last,
|
|
184
|
+
// otherwise grab the one (see heuristic above)
|
|
185
|
+
moduleJSDocs = jsDocs.slice(0, jsDocs.length > 1 ? -1 : 1);
|
|
186
|
+
// If there is only one leading JSDoc block, it must have a module tag
|
|
187
|
+
if (jsDocs.length === 1 && !jsDocs[0].tags?.some(isModuleJSDocTag)) {
|
|
188
|
+
moduleJSDocs = [];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
moduleJSDocsMap.set(sourceFile, moduleJSDocs);
|
|
192
|
+
return moduleJSDocs;
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* Parse summary, description, and deprecated information from JSDoc comments on
|
|
196
|
+
* a given node.
|
|
197
|
+
*/
|
|
198
|
+
export const parseNodeJSDocInfo = (node, analyzer) => {
|
|
199
|
+
const info = {};
|
|
200
|
+
const moduleJSDocs = getModuleJSDocs(analyzer.typescript, node.getSourceFile());
|
|
201
|
+
// Module-level docs (that are explicitly tagged as such) may be
|
|
202
|
+
// attached to the first declaration if the declaration is undocumented,
|
|
203
|
+
// so we filter those out since they shouldn't apply to a
|
|
204
|
+
// declaration node
|
|
205
|
+
const jsDocTags = analyzer.typescript
|
|
206
|
+
.getJSDocTags(node)
|
|
207
|
+
.filter(({ parent }) => !moduleJSDocs.includes(parent));
|
|
208
|
+
if (jsDocTags !== undefined) {
|
|
209
|
+
addJSDocTagInfo(info, jsDocTags, analyzer);
|
|
210
|
+
}
|
|
211
|
+
if (info.description === undefined) {
|
|
212
|
+
const comment = normalizeLineEndings(node
|
|
213
|
+
.getChildren()
|
|
214
|
+
.filter(analyzer.typescript.isJSDoc)
|
|
215
|
+
.filter((c) => !moduleJSDocs.includes(c))
|
|
216
|
+
.map((n) => n.comment)
|
|
217
|
+
.filter((c) => c !== undefined)
|
|
218
|
+
.join('\n'));
|
|
219
|
+
if (comment.length > 0) {
|
|
220
|
+
info.description = comment;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return info;
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* Parse summary, description, and deprecated information from JSDoc comments on
|
|
227
|
+
* a given source file.
|
|
228
|
+
*/
|
|
229
|
+
export const parseModuleJSDocInfo = (sourceFile, analyzer) => {
|
|
230
|
+
const moduleJSDocs = getModuleJSDocs(analyzer.typescript, sourceFile);
|
|
231
|
+
const info = {};
|
|
232
|
+
addJSDocTagInfo(info, moduleJSDocs.flatMap((m) => m.tags ?? []), analyzer);
|
|
233
|
+
if (info.description === undefined) {
|
|
234
|
+
const comment = moduleJSDocs
|
|
235
|
+
.map((d) => d.comment)
|
|
236
|
+
.filter((c) => c !== undefined)
|
|
237
|
+
.join('\n');
|
|
238
|
+
if (comment.length > 0) {
|
|
239
|
+
info.description = comment;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return info;
|
|
243
|
+
};
|
|
244
|
+
//# sourceMappingURL=jsdoc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsdoc.js","sourceRoot":"","sources":["../../src/lib/javascript/jsdoc.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,EAAC,gBAAgB,EAAC,MAAM,cAAc,CAAC;AAW9C;;;;GAIG;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAAc,EAAE,IAAa,EAAE,GAAW,EAAE,EAAE;IACxE,OAAO,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;AACnE,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,oBAAoB,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AAExE,oEAAoE;AACpE,MAAM,mBAAmB,GACvB,qHAAqH,CAAC;AAExH,oEAAoE;AACpE,MAAM,mBAAmB,GACvB,kHAAkH,CAAC;AAErH,MAAM,kBAAkB,GAAG,CAAC,GAAgB,EAAE,QAA2B,EAAE,EAAE;IAC3E,IAAI,EAAC,OAAO,EAAC,GAAG,GAAG,CAAC;IACpB,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,QAAQ,CAAC,aAAa,CACpB,gBAAgB,CAAC;YACf,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,IAAI,EAAE,GAAG;YACT,OAAO,EAAE,oCAAoC;SAC9C,CAAC,CACH,CAAC;QACF,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,oBAAoB,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;AAC9C,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,GAAgB,EAAE,EAAE,CAC5C,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ;IAC7B,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,cAAc;IACnC,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,sBAAsB,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACtC,GAAgB,EAChB,QAA2B,EAC3B,EAAE;IACF,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAClD,IAAI,OAAO,IAAI,SAAS,EAAE,CAAC;QACzB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,KAAK,GACT,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,mBAAmB,CAAC;IACxE,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1C,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QAC1B,QAAQ,CAAC,aAAa,CACpB,gBAAgB,CAAC;YACf,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,IAAI,EAAE,GAAG;YACT,OAAO,EAAE,sCAAsC;SAChD,CAAC,CACH,CAAC;QACF,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,EAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAC,GAAG,YAAY,CAAC,MAAO,CAAC;IACrE,MAAM,IAAI,GAAwB,EAAC,IAAI,EAAC,CAAC;IACzC,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;IACvD,CAAC;IACD,IAAI,YAAY,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,YAAY,CAAC;IAC9B,CAAC;IACD,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACxD,IAAwB,CAAC,MAAM,GAAG,IAAI,CAAC;IAC1C,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,GAAgB,EAChB,QAA2B,EACJ,EAAE;IACzB,MAAM,WAAW,GAAG,kBAAkB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACtD,IAAI,WAAW,IAAI,SAAS,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzD,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,EAAC,WAAW,EAAC,CAAC;AACvB,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,eAAe,GAAG,CACtB,IAA2B,EAC3B,SAAiC,EACjC,QAA2B,EAC3B,EAAE;IACF,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAClD,QAAQ,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACvC,KAAK,aAAa,CAAC;YACnB,KAAK,cAAc,CAAC;YACpB,KAAK,sBAAsB;gBACzB,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;oBAC1B,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;gBAC7B,CAAC;gBACD,MAAM;YACR,KAAK,SAAS;gBACZ,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;oBAC1B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;gBACzB,CAAC;gBACD,MAAM;YACR,KAAK,YAAY;gBACf,IAAI,CAAC,UAAU,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;gBACzD,MAAM;QACV,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,IAAI,OAAO,EAA6B,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,eAAe,GAAG,CAAC,UAAsB,EAAE,UAAyB,EAAE,EAAE;IAC5E,IAAI,YAAY,GAAG,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACnD,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,YAAY,CAAC;IACtB,CAAC;IACD,6EAA6E;IAC7E,yEAAyE;IACzE,0EAA0E;IAC1E,oBAAoB;IACpB,MAAM,UAAU,GAAG,UAAU,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACjE,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,YAAY,GAAG,EAAE,CAAC;IACpB,CAAC;SAAM,CAAC;QACN,uEAAuE;QACvE,0BAA0B;QAC1B,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACnE,wEAAwE;QACxE,+CAA+C;QAC/C,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,sEAAsE;QACtE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACnE,YAAY,GAAG,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;IACD,eAAe,CAAC,GAAG,CAAC,UAAU,EAAE,YAAa,CAAC,CAAC;IAC/C,OAAO,YAAY,CAAC;AACtB,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,IAAa,EACb,QAA2B,EACJ,EAAE;IACzB,MAAM,IAAI,GAA0B,EAAE,CAAC;IACvC,MAAM,YAAY,GAAG,eAAe,CAClC,QAAQ,CAAC,UAAU,EACnB,IAAI,CAAC,aAAa,EAAE,CACrB,CAAC;IACF,gEAAgE;IAChE,wEAAwE;IACxE,yDAAyD;IACzD,mBAAmB;IACnB,MAAM,SAAS,GAAG,QAAQ,CAAC,UAAU;SAClC,YAAY,CAAC,IAAI,CAAC;SAClB,MAAM,CAAC,CAAC,EAAC,MAAM,EAAC,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAkB,CAAC,CAAC,CAAC;IACpE,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,oBAAoB,CAClC,IAAI;aACD,WAAW,EAAE;aACb,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC;aACnC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;aACxC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;aACrB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;aAC9B,IAAI,CAAC,IAAI,CAAC,CACd,CAAC;QACF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,UAAyB,EACzB,QAA2B,EAC3B,EAAE;IACF,MAAM,YAAY,GAAG,eAAe,CAAC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IACtE,MAAM,IAAI,GAA0B,EAAE,CAAC;IACvC,eAAe,CACb,IAAI,EACJ,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,EACzC,QAAQ,CACT,CAAC;IACF,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,YAAY;aACzB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;aACrB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;aAC9B,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC","sourcesContent":["/**\n * @license\n * Copyright 2022 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * @fileoverview\n *\n * Utilities for analyzing JSDoc comments\n */\n\nimport type ts from 'typescript';\nimport {createDiagnostic} from '../errors.js';\nimport {\n Described,\n TypedNamedDescribed,\n DeprecatableDescribed,\n AnalyzerInterface,\n CSSPropertyInfo,\n} from '../model.js';\n\nexport type TypeScript = typeof ts;\n\n/**\n * @fileoverview\n *\n * Utilities for parsing JSDoc comments.\n */\n\n/**\n * Returns true if given node has a JSDoc tag\n */\nexport const hasJSDocTag = (ts: TypeScript, node: ts.Node, tag: string) => {\n return ts.getJSDocTags(node).some((t) => t.tagName.text === tag);\n};\n\n/**\n * Remove line feeds from JSDoc comments, so they are normalized to\n * unix `\\n` line endings.\n */\nconst normalizeLineEndings = (s: string) => s.replace(/\\r/g, '').trim();\n\n// Regex for parsing name, type, and description from JSDoc comments\nconst parseNameTypeDescRE =\n /^\\[?(?<name>[^{}[\\]\\s=]+)(?:=(?<defaultValue>[^\\]]+))?\\]?(?:\\s+{(?<type>.*)})?(?:\\s+-\\s+)?(?<description>[\\s\\S]*)$/m;\n\n// Regex for parsing type, name, and description from JSDoc comments\nconst parseTypeNameDescRE =\n /^\\{(?<type>.+)\\}\\s+\\[?(?<name>[^{}[\\]\\s=]+)(?:=(?<defaultValue>[^\\]]+))?\\]?(?:\\s+-\\s+)?(?<description>[\\s\\S]*)$/m;\n\nconst getJSDocTagComment = (tag: ts.JSDocTag, analyzer: AnalyzerInterface) => {\n let {comment} = tag;\n if (comment === undefined) {\n return undefined;\n }\n if (Array.isArray(comment)) {\n comment = comment.map((c) => c.text).join('');\n }\n if (typeof comment !== 'string') {\n analyzer.addDiagnostic(\n createDiagnostic({\n typescript: analyzer.typescript,\n node: tag,\n message: `JSDoc error: unsupported node type`,\n })\n );\n return undefined;\n }\n return normalizeLineEndings(comment).trim();\n};\n\nconst isModuleJSDocTag = (tag: ts.JSDocTag) =>\n tag.tagName.text === 'module' ||\n tag.tagName.text === 'fileoverview' ||\n tag.tagName.text === 'packageDocumentation';\n\n/**\n * Parses name, type, and description from JSDoc tag for things like `@fires`.\n *\n * Supports the following patterns following the tag (TS parses the tag for us):\n * * @fires event-name\n * * @fires event-name description\n * * @fires event-name - description\n * * @fires event-name: description\n * * @fires event-name {Type}\n * * @fires event-name {Type} description\n * * @fires event-name {Type} - description\n * * @fires event-name {Type}: description\n * * @fires {Type} event-name\n * * @fires {Type} event-name description\n * * @fires {Type} event-name - description\n * * @fires {Type} event-name: description\n * * @slot name\n * * @slot name description\n * * @slot name - description\n * * @slot name: description\n * * @cssProp [--name=default]\n * * @cssProp [--name=default] description\n * * @cssProp [--name=default] - description\n * * @cssProp [--name=default]: description\n * * @cssprop {<color>} [--name=default]\n * * @cssprop {<color>} [--name=default] description\n * * @cssprop {<color>} [--name=default] - description\n * * @cssprop {<color>} [--name=default]: description\n */\nexport const parseNamedTypedJSDocInfo = (\n tag: ts.JSDocTag,\n analyzer: AnalyzerInterface\n) => {\n const comment = getJSDocTagComment(tag, analyzer);\n if (comment == undefined) {\n return undefined;\n }\n const regex =\n comment.charAt(0) === '{' ? parseTypeNameDescRE : parseNameTypeDescRE;\n const nameTypeDesc = comment.match(regex);\n if (nameTypeDesc === null) {\n analyzer.addDiagnostic(\n createDiagnostic({\n typescript: analyzer.typescript,\n node: tag,\n message: `JSDoc error: unexpected JSDoc format`,\n })\n );\n return undefined;\n }\n const {name, type, defaultValue, description} = nameTypeDesc.groups!;\n const info: TypedNamedDescribed = {name};\n if (description.length > 0) {\n info.description = normalizeLineEndings(description);\n }\n if (defaultValue?.length > 0) {\n info.default = defaultValue;\n }\n if (tag.tagName.text.toLowerCase().startsWith('cssprop')) {\n (info as CSSPropertyInfo).syntax = type;\n } else {\n info.type = type;\n }\n return info;\n};\n\n/**\n * Parses the description from JSDoc tag for things like `@return`.\n */\nexport const parseJSDocDescription = (\n tag: ts.JSDocTag,\n analyzer: AnalyzerInterface\n): Described | undefined => {\n const description = getJSDocTagComment(tag, analyzer);\n if (description == undefined || description.length === 0) {\n return {};\n }\n return {description};\n};\n\n/**\n * Add `@description`, `@summary`, and `@deprecated` JSDoc tag info to the\n * given info object.\n */\nconst addJSDocTagInfo = (\n info: DeprecatableDescribed,\n jsDocTags: readonly ts.JSDocTag[],\n analyzer: AnalyzerInterface\n) => {\n for (const tag of jsDocTags) {\n const comment = getJSDocTagComment(tag, analyzer);\n switch (tag.tagName.text.toLowerCase()) {\n case 'description':\n case 'fileoverview':\n case 'packagedocumentation':\n if (comment !== undefined) {\n info.description = comment;\n }\n break;\n case 'summary':\n if (comment !== undefined) {\n info.summary = comment;\n }\n break;\n case 'deprecated':\n info.deprecated = comment !== undefined ? comment : true;\n break;\n }\n }\n};\n\nconst moduleJSDocsMap = new WeakMap<ts.SourceFile, ts.JSDoc[]>();\n\n/**\n * Returns the module-level JSDoc comment blocks for a given source file.\n *\n * Note that TS does not have a concept of module-level JSDoc; if it\n * exists, it will always be attached to the first statement in the module.\n *\n * Thus, we parse module-level documentation using the following heuristic:\n * - If the first statement only has one JSDoc block, it is only treated as\n * module documentation if it contains a `@module`, `@fileoverview`, or\n * `@packageDocumentation` tag. This is required to disambiguate a module\n * description (with an undocumented first statement) from documentation\n * for the first statement.\n * - If the first statement has more than one JSDoc block, we collect all\n * but the last and use those, regardless of whether they contain one\n * of the above module-designating tags (the last one is assumed to belong\n * to the first statement).\n *\n * This function caches its result against the given sourceFile, since it is\n * needed both to find the module comment and to filter out module comments\n * node comments.\n */\nconst getModuleJSDocs = (typescript: TypeScript, sourceFile: ts.SourceFile) => {\n let moduleJSDocs = moduleJSDocsMap.get(sourceFile);\n if (moduleJSDocs !== undefined) {\n return moduleJSDocs;\n }\n // Get the first child in the sourceFile; note that returning the first child\n // from `ts.forEachChild` is more robust than `sourceFile.getChildAt(0)`,\n // since `forEachChild` flattens embedded arrays that the child APIs would\n // otherwise return.\n const firstChild = typescript.forEachChild(sourceFile, (n) => n);\n if (firstChild === undefined) {\n moduleJSDocs = [];\n } else {\n // Get the JSDoc blocks attached to the first child (they oddly show up\n // in the node's children)\n const jsDocs = firstChild.getChildren().filter(typescript.isJSDoc);\n // If there is more than one leading JSDoc block, grab all but the last,\n // otherwise grab the one (see heuristic above)\n moduleJSDocs = jsDocs.slice(0, jsDocs.length > 1 ? -1 : 1);\n // If there is only one leading JSDoc block, it must have a module tag\n if (jsDocs.length === 1 && !jsDocs[0].tags?.some(isModuleJSDocTag)) {\n moduleJSDocs = [];\n }\n }\n moduleJSDocsMap.set(sourceFile, moduleJSDocs!);\n return moduleJSDocs;\n};\n\n/**\n * Parse summary, description, and deprecated information from JSDoc comments on\n * a given node.\n */\nexport const parseNodeJSDocInfo = (\n node: ts.Node,\n analyzer: AnalyzerInterface\n): DeprecatableDescribed => {\n const info: DeprecatableDescribed = {};\n const moduleJSDocs = getModuleJSDocs(\n analyzer.typescript,\n node.getSourceFile()\n );\n // Module-level docs (that are explicitly tagged as such) may be\n // attached to the first declaration if the declaration is undocumented,\n // so we filter those out since they shouldn't apply to a\n // declaration node\n const jsDocTags = analyzer.typescript\n .getJSDocTags(node)\n .filter(({parent}) => !moduleJSDocs.includes(parent as ts.JSDoc));\n if (jsDocTags !== undefined) {\n addJSDocTagInfo(info, jsDocTags, analyzer);\n }\n if (info.description === undefined) {\n const comment = normalizeLineEndings(\n node\n .getChildren()\n .filter(analyzer.typescript.isJSDoc)\n .filter((c) => !moduleJSDocs.includes(c))\n .map((n) => n.comment)\n .filter((c) => c !== undefined)\n .join('\\n')\n );\n if (comment.length > 0) {\n info.description = comment;\n }\n }\n return info;\n};\n\n/**\n * Parse summary, description, and deprecated information from JSDoc comments on\n * a given source file.\n */\nexport const parseModuleJSDocInfo = (\n sourceFile: ts.SourceFile,\n analyzer: AnalyzerInterface\n) => {\n const moduleJSDocs = getModuleJSDocs(analyzer.typescript, sourceFile);\n const info: DeprecatableDescribed = {};\n addJSDocTagInfo(\n info,\n moduleJSDocs.flatMap((m) => m.tags ?? []),\n analyzer\n );\n if (info.description === undefined) {\n const comment = moduleJSDocs\n .map((d) => d.comment)\n .filter((c) => c !== undefined)\n .join('\\n');\n if (comment.length > 0) {\n info.description = comment;\n }\n }\n return info;\n};\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2024 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* @fileoverview
|
|
8
|
+
*
|
|
9
|
+
* Utilities for working with mixins
|
|
10
|
+
*/
|
|
11
|
+
import type ts from 'typescript';
|
|
12
|
+
import { AnalyzerInterface, MixinDeclarationInit } from '../model.js';
|
|
13
|
+
/**
|
|
14
|
+
* If the given variable declaration was a mixin function, returns a
|
|
15
|
+
* MixinDeclaration initialisation object, otherwise returns undefined.
|
|
16
|
+
*
|
|
17
|
+
* The mixin logic requires a few important syntactic heuristics to be met in
|
|
18
|
+
* order to be detected as a mixin:
|
|
19
|
+
*
|
|
20
|
+
* - a super class parameter (by any name) which is later used as a base class
|
|
21
|
+
* - a function body (rather than an arrow function)
|
|
22
|
+
* - an internal class which extends the previously mentioned super class parameter
|
|
23
|
+
* - a return statement returning the class
|
|
24
|
+
*
|
|
25
|
+
* For example:
|
|
26
|
+
*
|
|
27
|
+
* ```
|
|
28
|
+
* function MyMixin(superClass) {
|
|
29
|
+
* class MixedClass extends superClass {
|
|
30
|
+
* // ...
|
|
31
|
+
* }
|
|
32
|
+
* return MixedClass;
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* You can read more about this pattern in the TypeScript docs here:
|
|
37
|
+
* https://www.typescriptlang.org/docs/handbook/mixins.html
|
|
38
|
+
*
|
|
39
|
+
* If the function is unannotated and does not match the above mixin shape, it
|
|
40
|
+
* will silently just be analyzed as a simple function and not a mixin. However,
|
|
41
|
+
* the `@mixin` annotation can be added to produce specific diagnostic errors
|
|
42
|
+
* when a condition for being analyzed as a mixin is not met.
|
|
43
|
+
*/
|
|
44
|
+
export declare const maybeGetMixinFromFunctionLike: (fn: ts.FunctionLikeDeclaration, name: string, analyzer: AnalyzerInterface) => MixinDeclarationInit | undefined;
|
|
45
|
+
//# sourceMappingURL=mixins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mixins.d.ts","sourceRoot":"","sources":["../../src/lib/javascript/mixins.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAC,iBAAiB,EAAE,oBAAoB,EAAC,MAAM,aAAa,CAAC;AA+BpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,6BAA6B,GACxC,IAAI,EAAE,CAAC,uBAAuB,EAC9B,MAAM,MAAM,EACZ,UAAU,iBAAiB,KAC1B,oBAAoB,GAAG,SAwHzB,CAAC"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2024 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
*/
|
|
6
|
+
import { getClassDeclaration } from './classes.js';
|
|
7
|
+
import { createDiagnostic } from '../errors.js';
|
|
8
|
+
import { DiagnosticCode } from '../diagnostic-code.js';
|
|
9
|
+
import { getSymbolForName } from '../references.js';
|
|
10
|
+
const nodeHasMixinHint = (node, analyzer) => analyzer.typescript
|
|
11
|
+
.getJSDocTags(node)
|
|
12
|
+
.some((tag) => tag.tagName.text === 'mixin');
|
|
13
|
+
const addDiagnosticIfMixin = (node, hasMixinHint, message, analyzer) => {
|
|
14
|
+
if (hasMixinHint) {
|
|
15
|
+
analyzer.addDiagnostic(createDiagnostic({
|
|
16
|
+
typescript: analyzer.typescript,
|
|
17
|
+
node,
|
|
18
|
+
message,
|
|
19
|
+
code: DiagnosticCode.UNSUPPORTED,
|
|
20
|
+
category: analyzer.typescript.DiagnosticCategory.Warning,
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* If the given variable declaration was a mixin function, returns a
|
|
27
|
+
* MixinDeclaration initialisation object, otherwise returns undefined.
|
|
28
|
+
*
|
|
29
|
+
* The mixin logic requires a few important syntactic heuristics to be met in
|
|
30
|
+
* order to be detected as a mixin:
|
|
31
|
+
*
|
|
32
|
+
* - a super class parameter (by any name) which is later used as a base class
|
|
33
|
+
* - a function body (rather than an arrow function)
|
|
34
|
+
* - an internal class which extends the previously mentioned super class parameter
|
|
35
|
+
* - a return statement returning the class
|
|
36
|
+
*
|
|
37
|
+
* For example:
|
|
38
|
+
*
|
|
39
|
+
* ```
|
|
40
|
+
* function MyMixin(superClass) {
|
|
41
|
+
* class MixedClass extends superClass {
|
|
42
|
+
* // ...
|
|
43
|
+
* }
|
|
44
|
+
* return MixedClass;
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* You can read more about this pattern in the TypeScript docs here:
|
|
49
|
+
* https://www.typescriptlang.org/docs/handbook/mixins.html
|
|
50
|
+
*
|
|
51
|
+
* If the function is unannotated and does not match the above mixin shape, it
|
|
52
|
+
* will silently just be analyzed as a simple function and not a mixin. However,
|
|
53
|
+
* the `@mixin` annotation can be added to produce specific diagnostic errors
|
|
54
|
+
* when a condition for being analyzed as a mixin is not met.
|
|
55
|
+
*/
|
|
56
|
+
export const maybeGetMixinFromFunctionLike = (fn, name, analyzer) => {
|
|
57
|
+
const hasMixinHint = nodeHasMixinHint(fn, analyzer);
|
|
58
|
+
if (fn.parameters === undefined || fn.parameters.length < 1) {
|
|
59
|
+
addDiagnosticIfMixin(fn, hasMixinHint, `Expected mixin to have a superClass parameter.`, analyzer);
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
const functionBody = fn.body;
|
|
63
|
+
if (functionBody === undefined) {
|
|
64
|
+
addDiagnosticIfMixin(fn, hasMixinHint, `Expected mixin to have a block function body.`, analyzer);
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
let classDeclaration;
|
|
68
|
+
let returnStatement;
|
|
69
|
+
if (analyzer.typescript.isBlock(functionBody)) {
|
|
70
|
+
for (const s of functionBody.statements) {
|
|
71
|
+
if (analyzer.typescript.isClassDeclaration(s)) {
|
|
72
|
+
classDeclaration = s;
|
|
73
|
+
}
|
|
74
|
+
if (analyzer.typescript.isReturnStatement(s)) {
|
|
75
|
+
returnStatement = s;
|
|
76
|
+
if (classDeclaration === undefined &&
|
|
77
|
+
s.expression !== undefined &&
|
|
78
|
+
analyzer.typescript.isClassLike(s.expression)) {
|
|
79
|
+
classDeclaration = s.expression;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (returnStatement === undefined) {
|
|
84
|
+
addDiagnosticIfMixin(fn, hasMixinHint, `Expected mixin to contain a return statement returning a class.`, analyzer);
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (analyzer.typescript.isClassLike(functionBody)) {
|
|
89
|
+
classDeclaration = functionBody;
|
|
90
|
+
}
|
|
91
|
+
if (classDeclaration === undefined) {
|
|
92
|
+
addDiagnosticIfMixin(fn, hasMixinHint, `Expected mixin to contain a class declaration statement.`, analyzer);
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
const extendsClause = classDeclaration.heritageClauses?.find((c) => c.token === analyzer.typescript.SyntaxKind.ExtendsKeyword);
|
|
96
|
+
if (extendsClause === undefined) {
|
|
97
|
+
addDiagnosticIfMixin(fn, hasMixinHint, `Expected mixin to contain class declaration extending a superClass argument to function.`, analyzer);
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
if (extendsClause.types.length !== 1) {
|
|
101
|
+
analyzer.addDiagnostic(createDiagnostic({
|
|
102
|
+
typescript: analyzer.typescript,
|
|
103
|
+
node: extendsClause,
|
|
104
|
+
message: 'Internal error: did not expect a mixin class extends clause to have multiple types',
|
|
105
|
+
code: DiagnosticCode.UNSUPPORTED,
|
|
106
|
+
category: analyzer.typescript.DiagnosticCategory.Warning,
|
|
107
|
+
}));
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
const superClassArgIndex = findSuperClassArgIndexFromHeritage(fn, extendsClause.types[0].expression, analyzer);
|
|
111
|
+
if (superClassArgIndex < 0) {
|
|
112
|
+
analyzer.addDiagnostic(createDiagnostic({
|
|
113
|
+
typescript: analyzer.typescript,
|
|
114
|
+
node: extendsClause,
|
|
115
|
+
message: 'Did not find a "superClass" argument used in the extends clause of mixin class.',
|
|
116
|
+
code: DiagnosticCode.UNSUPPORTED,
|
|
117
|
+
category: analyzer.typescript.DiagnosticCategory.Warning,
|
|
118
|
+
}));
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
const classDeclarationName = classDeclaration.name?.text ?? name;
|
|
122
|
+
return {
|
|
123
|
+
node: fn,
|
|
124
|
+
name,
|
|
125
|
+
superClassArgIndex,
|
|
126
|
+
classDeclaration: getClassDeclaration(classDeclaration, classDeclarationName, analyzer, undefined, true /* isMixinClass */),
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
const findSuperClassArgIndexFromHeritage = (mixinFunction, expression, analyzer) => {
|
|
130
|
+
if (analyzer.typescript.isIdentifier(expression)) {
|
|
131
|
+
const paramSymbol = getSymbolForName(expression.text, expression, analyzer);
|
|
132
|
+
if (paramSymbol?.declarations) {
|
|
133
|
+
const paramDecl = paramSymbol.declarations;
|
|
134
|
+
return mixinFunction.parameters.findIndex((param) => paramDecl.includes(param));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (analyzer.typescript.isCallExpression(expression)) {
|
|
138
|
+
for (const arg of expression.arguments) {
|
|
139
|
+
const index = findSuperClassArgIndexFromHeritage(mixinFunction, arg, analyzer);
|
|
140
|
+
if (index >= 0) {
|
|
141
|
+
return index;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return -1;
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=mixins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mixins.js","sourceRoot":"","sources":["../../src/lib/javascript/mixins.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license\n * Copyright 2024 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * @fileoverview\n *\n * Utilities for working with mixins\n */\n\nimport type ts from 'typescript';\nimport {AnalyzerInterface, MixinDeclarationInit} from '../model.js';\nimport {getClassDeclaration} from './classes.js';\nimport {createDiagnostic} from '../errors.js';\nimport {DiagnosticCode} from '../diagnostic-code.js';\nimport {getSymbolForName} from '../references.js';\n\nconst nodeHasMixinHint = (node: ts.Node, analyzer: AnalyzerInterface) =>\n analyzer.typescript\n .getJSDocTags(node)\n .some((tag) => tag.tagName.text === 'mixin');\n\nconst addDiagnosticIfMixin = (\n node: ts.Node,\n hasMixinHint: boolean,\n message: string,\n analyzer: AnalyzerInterface\n) => {\n if (hasMixinHint) {\n analyzer.addDiagnostic(\n createDiagnostic({\n typescript: analyzer.typescript,\n node,\n message,\n code: DiagnosticCode.UNSUPPORTED,\n category: analyzer.typescript.DiagnosticCategory.Warning,\n })\n );\n }\n return undefined;\n};\n\n/**\n * If the given variable declaration was a mixin function, returns a\n * MixinDeclaration initialisation object, otherwise returns undefined.\n *\n * The mixin logic requires a few important syntactic heuristics to be met in\n * order to be detected as a mixin:\n *\n * - a super class parameter (by any name) which is later used as a base class\n * - a function body (rather than an arrow function)\n * - an internal class which extends the previously mentioned super class parameter\n * - a return statement returning the class\n *\n * For example:\n *\n * ```\n * function MyMixin(superClass) {\n * class MixedClass extends superClass {\n * // ...\n * }\n * return MixedClass;\n * }\n * ```\n *\n * You can read more about this pattern in the TypeScript docs here:\n * https://www.typescriptlang.org/docs/handbook/mixins.html\n *\n * If the function is unannotated and does not match the above mixin shape, it\n * will silently just be analyzed as a simple function and not a mixin. However,\n * the `@mixin` annotation can be added to produce specific diagnostic errors\n * when a condition for being analyzed as a mixin is not met.\n */\nexport const maybeGetMixinFromFunctionLike = (\n fn: ts.FunctionLikeDeclaration,\n name: string,\n analyzer: AnalyzerInterface\n): MixinDeclarationInit | undefined => {\n const hasMixinHint = nodeHasMixinHint(fn, analyzer);\n if (fn.parameters === undefined || fn.parameters.length < 1) {\n addDiagnosticIfMixin(\n fn,\n hasMixinHint,\n `Expected mixin to have a superClass parameter.`,\n analyzer\n );\n return undefined;\n }\n const functionBody = fn.body;\n if (functionBody === undefined) {\n addDiagnosticIfMixin(\n fn,\n hasMixinHint,\n `Expected mixin to have a block function body.`,\n analyzer\n );\n return undefined;\n }\n let classDeclaration: ts.ClassLikeDeclaration | undefined;\n let returnStatement: ts.ReturnStatement | undefined;\n if (analyzer.typescript.isBlock(functionBody)) {\n for (const s of functionBody.statements) {\n if (analyzer.typescript.isClassDeclaration(s)) {\n classDeclaration = s;\n }\n if (analyzer.typescript.isReturnStatement(s)) {\n returnStatement = s;\n\n if (\n classDeclaration === undefined &&\n s.expression !== undefined &&\n analyzer.typescript.isClassLike(s.expression)\n ) {\n classDeclaration = s.expression;\n }\n }\n }\n\n if (returnStatement === undefined) {\n addDiagnosticIfMixin(\n fn,\n hasMixinHint,\n `Expected mixin to contain a return statement returning a class.`,\n analyzer\n );\n return undefined;\n }\n } else if (analyzer.typescript.isClassLike(functionBody)) {\n classDeclaration = functionBody;\n }\n if (classDeclaration === undefined) {\n addDiagnosticIfMixin(\n fn,\n hasMixinHint,\n `Expected mixin to contain a class declaration statement.`,\n analyzer\n );\n return undefined;\n }\n const extendsClause = classDeclaration.heritageClauses?.find(\n (c) => c.token === analyzer.typescript.SyntaxKind.ExtendsKeyword\n );\n if (extendsClause === undefined) {\n addDiagnosticIfMixin(\n fn,\n hasMixinHint,\n `Expected mixin to contain class declaration extending a superClass argument to function.`,\n analyzer\n );\n return undefined;\n }\n if (extendsClause.types.length !== 1) {\n analyzer.addDiagnostic(\n createDiagnostic({\n typescript: analyzer.typescript,\n node: extendsClause,\n message:\n 'Internal error: did not expect a mixin class extends clause to have multiple types',\n code: DiagnosticCode.UNSUPPORTED,\n category: analyzer.typescript.DiagnosticCategory.Warning,\n })\n );\n return undefined;\n }\n const superClassArgIndex = findSuperClassArgIndexFromHeritage(\n fn,\n extendsClause.types[0].expression,\n analyzer\n );\n if (superClassArgIndex < 0) {\n analyzer.addDiagnostic(\n createDiagnostic({\n typescript: analyzer.typescript,\n node: extendsClause,\n message:\n 'Did not find a \"superClass\" argument used in the extends clause of mixin class.',\n code: DiagnosticCode.UNSUPPORTED,\n category: analyzer.typescript.DiagnosticCategory.Warning,\n })\n );\n return undefined;\n }\n\n const classDeclarationName = classDeclaration.name?.text ?? name;\n\n return {\n node: fn,\n name,\n superClassArgIndex,\n classDeclaration: getClassDeclaration(\n classDeclaration,\n classDeclarationName,\n analyzer,\n undefined,\n true /* isMixinClass */\n ),\n };\n};\n\nconst findSuperClassArgIndexFromHeritage = (\n mixinFunction: ts.FunctionLikeDeclaration,\n expression: ts.Expression,\n analyzer: AnalyzerInterface\n): number => {\n if (analyzer.typescript.isIdentifier(expression)) {\n const paramSymbol = getSymbolForName(expression.text, expression, analyzer);\n\n if (paramSymbol?.declarations) {\n const paramDecl = paramSymbol.declarations;\n return mixinFunction.parameters.findIndex((param) =>\n paramDecl.includes(param)\n );\n }\n } else if (analyzer.typescript.isCallExpression(expression)) {\n for (const arg of expression.arguments) {\n const index = findSuperClassArgIndexFromHeritage(\n mixinFunction,\n arg,\n analyzer\n );\n if (index >= 0) {\n return index;\n }\n }\n }\n return -1;\n};\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* @fileoverview
|
|
8
|
+
*
|
|
9
|
+
* Utilities for analyzing ES modules
|
|
10
|
+
*/
|
|
11
|
+
import type ts from 'typescript';
|
|
12
|
+
import { Module, AnalyzerInterface, PackageInfo, Declaration, ModuleInfo } from '../model.js';
|
|
13
|
+
import { AbsolutePath } from '../paths.js';
|
|
14
|
+
export type TypeScript = typeof ts;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the sourcePath, jsPath, and package.json contents of the containing
|
|
17
|
+
* package for the given module path.
|
|
18
|
+
*
|
|
19
|
+
* This is a minimal subset of module information needed for constructing a
|
|
20
|
+
* Reference object for a module.
|
|
21
|
+
*/
|
|
22
|
+
export declare const getModuleInfo: (modulePath: AbsolutePath, analyzer: AnalyzerInterface, packageInfo?: PackageInfo) => ModuleInfo;
|
|
23
|
+
/**
|
|
24
|
+
* Returns an analyzer `Module` model for the given module path.
|
|
25
|
+
*/
|
|
26
|
+
export declare const getModule: (modulePath: AbsolutePath, analyzer: AnalyzerInterface, packageInfo?: PackageInfo) => Module;
|
|
27
|
+
/**
|
|
28
|
+
* Resolves a module specifier expression node to an absolute path on disk.
|
|
29
|
+
*/
|
|
30
|
+
export declare const getPathForModuleSpecifierExpression: (specifierExpression: ts.Expression, analyzer: AnalyzerInterface) => AbsolutePath | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a module specifier to an absolute path on disk.
|
|
33
|
+
*/
|
|
34
|
+
export declare const getPathForModuleSpecifier: (specifier: string, location: ts.Node, analyzer: AnalyzerInterface) => AbsolutePath | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Returns the declaration for the named export of the given module path;
|
|
37
|
+
* note that if the given module re-exported a declaration from another
|
|
38
|
+
* module, references are followed to the concrete declaration, which is
|
|
39
|
+
* returned.
|
|
40
|
+
*/
|
|
41
|
+
export declare const getResolvedExportFromSourcePath: (modulePath: AbsolutePath, name: string, analyzer: AnalyzerInterface) => Declaration;
|
|
42
|
+
//# sourceMappingURL=modules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../../src/lib/javascript/modules.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,WAAW,EACX,WAAW,EAIX,UAAU,EAEX,MAAM,aAAa,CAAC;AAOrB,OAAO,EAAC,YAAY,EAAiC,MAAM,aAAa,CAAC;AAWzE,MAAM,MAAM,UAAU,GAAG,OAAO,EAAE,CAAC;AAEnC;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GACxB,YAAY,YAAY,EACxB,UAAU,iBAAiB,EAC3B,cAAa,WAAkD,KAC9D,UAuBF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,GACpB,YAAY,YAAY,EACxB,UAAU,iBAAiB,EAC3B,cAAa,WAAkD,WAiHhE,CAAC;AA6IF;;GAEG;AACH,eAAO,MAAM,mCAAmC,GAC9C,qBAAqB,EAAE,CAAC,UAAU,EAClC,UAAU,iBAAiB,KAC1B,YAAY,GAAG,SAGjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,yBAAyB,GACpC,WAAW,MAAM,EACjB,UAAU,EAAE,CAAC,IAAI,EACjB,UAAU,iBAAiB,KAC1B,YAAY,GAAG,SAmBjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,GAC1C,YAAY,YAAY,EACxB,MAAM,MAAM,EACZ,UAAU,iBAAiB,gBACgC,CAAC"}
|