@playcanvas/attribute-parser 1.6.1 → 1.7.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.md +19 -0
- package/dist/index.js +445 -0
- package/dist/libs.d.ts +50978 -0
- package/package.json +50 -10
- package/types/index.d.ts +55 -0
- package/types/parsers/attribute-parser.d.ts +55 -0
- package/types/parsers/parsing-error.d.ts +6 -0
- package/types/parsers/script-parser.d.ts +124 -0
- package/types/utils/attribute-utils.d.ts +5 -0
- package/{src/utils/generic-utils.js → types/utils/generic-utils.d.ts} +1 -10
- package/types/utils/tag-utils.d.ts +16 -0
- package/types/utils/ts-utils.d.ts +103 -0
- package/.github/workflows/ci.yml +0 -49
- package/eslint.config.mjs +0 -35
- package/renovate.json +0 -25
- package/src/index.js +0 -273
- package/src/parsers/attribute-parser.js +0 -197
- package/src/parsers/parsing-error.js +0 -14
- package/src/parsers/script-parser.js +0 -432
- package/src/utils/attribute-utils.js +0 -53
- package/src/utils/tag-utils.js +0 -73
- package/src/utils/ts-utils.js +0 -634
- package/test/fixtures/asset.invalid.js +0 -27
- package/test/fixtures/asset.invalid.ts +0 -24
- package/test/fixtures/asset.valid.js +0 -26
- package/test/fixtures/asset.valid.ts +0 -21
- package/test/fixtures/checkbox.valid.js +0 -23
- package/test/fixtures/color.valid.js +0 -23
- package/test/fixtures/conditional.valid.js +0 -22
- package/test/fixtures/curve.invalid.js +0 -29
- package/test/fixtures/curve.valid.js +0 -49
- package/test/fixtures/empty.valid.js +0 -0
- package/test/fixtures/entity.valid.js +0 -23
- package/test/fixtures/enum.valid.js +0 -66
- package/test/fixtures/enum.valid.ts +0 -50
- package/test/fixtures/example.dep.js +0 -9
- package/test/fixtures/export.import.js +0 -8
- package/test/fixtures/export.valid.js +0 -26
- package/test/fixtures/inherit.valid.js +0 -45
- package/test/fixtures/intellisense.valid.js +0 -40
- package/test/fixtures/json.invalid.js +0 -23
- package/test/fixtures/json.valid.js +0 -115
- package/test/fixtures/json.valid.ts +0 -81
- package/test/fixtures/misc.invalid.js +0 -24
- package/test/fixtures/misc.valid.js +0 -51
- package/test/fixtures/numeric.invalid.js +0 -38
- package/test/fixtures/numeric.valid.js +0 -63
- package/test/fixtures/playcanvas.d.ts +0 -47963
- package/test/fixtures/program.invalid.js +0 -12
- package/test/fixtures/program.valid.js +0 -27
- package/test/fixtures/text.valid.js +0 -31
- package/test/fixtures/vector.invalid.js +0 -14
- package/test/fixtures/vector.valid.js +0 -24
- package/test/tests/invalid/asset.test.js +0 -47
- package/test/tests/invalid/curve.test.js +0 -23
- package/test/tests/invalid/json.test.js +0 -27
- package/test/tests/invalid/misc.test.js +0 -41
- package/test/tests/invalid/numeric.test.js +0 -55
- package/test/tests/invalid/program.test.js +0 -17
- package/test/tests/invalid/vector.test.js +0 -28
- package/test/tests/valid/asset.test.js +0 -59
- package/test/tests/valid/checkbox.test.js +0 -57
- package/test/tests/valid/color.test.js +0 -49
- package/test/tests/valid/conditional.test.js +0 -45
- package/test/tests/valid/curve.test.js +0 -76
- package/test/tests/valid/empty.test.js +0 -17
- package/test/tests/valid/entity.test.js +0 -49
- package/test/tests/valid/enum.test.js +0 -79
- package/test/tests/valid/export.test.js +0 -67
- package/test/tests/valid/inherit.test.js +0 -110
- package/test/tests/valid/intellisense.test.js +0 -39
- package/test/tests/valid/json.test.js +0 -315
- package/test/tests/valid/misc.test.js +0 -54
- package/test/tests/valid/numeric.test.js +0 -103
- package/test/tests/valid/program.test.js +0 -23
- package/test/tests/valid/text.test.js +0 -58
- package/test/tests/valid/vector.test.js +0 -53
- package/test/utils.js +0 -54
package/src/index.js
DELETED
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { createSystem, createDefaultMapFromNodeModules, createVirtualTypeScriptEnvironment } from '@typescript/vfs';
|
|
2
|
-
import * as ts from 'typescript';
|
|
3
|
-
|
|
4
|
-
import { ScriptParser } from './parsers/script-parser.js';
|
|
5
|
-
import { isInterface, isStaticMember } from './utils/attribute-utils.js';
|
|
6
|
-
import { createDefaultMapFromCDN, flatMapAnyNodes, getExportedNodes, getType, inheritsFrom, isAliasedClassDeclaration } from './utils/ts-utils.js';
|
|
7
|
-
|
|
8
|
-
const toLowerCamelCase = str => str[0].toLowerCase() + str.substring(1);
|
|
9
|
-
|
|
10
|
-
const COMPILER_OPTIONS = {
|
|
11
|
-
noLib: true,
|
|
12
|
-
strict: false,
|
|
13
|
-
skipLibCheck: true, // Skip type checking of declaration files
|
|
14
|
-
target: ts.ScriptTarget.ES2023, // If this version changes, the types must be updated in the /rollup.config.mjs
|
|
15
|
-
module: ts.ModuleKind.CommonJS,
|
|
16
|
-
checkJs: true, // Enable JSDoc parsing
|
|
17
|
-
allowJs: true,
|
|
18
|
-
baseUrl: './',
|
|
19
|
-
paths: {
|
|
20
|
-
'playcanvas': ['/playcanvas.js']
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export class JSDocParser {
|
|
25
|
-
/**
|
|
26
|
-
* @type {import('@typescript/vfs').VirtualTypeScriptEnvironment}
|
|
27
|
-
* @private
|
|
28
|
-
*/
|
|
29
|
-
_env;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* An internal array of program errors
|
|
33
|
-
* @private
|
|
34
|
-
*/
|
|
35
|
-
_errors = [];
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Initializes the JSDocParser with the standard library files
|
|
39
|
-
*
|
|
40
|
-
* @param {string} libPath - A path to a directory of library types, or a path to the '.d.ts' file itself
|
|
41
|
-
* @returns {Promise<JSDocParser>} - The initialized JSDocParser
|
|
42
|
-
*/
|
|
43
|
-
async init(libPath) {
|
|
44
|
-
if (this._env) {
|
|
45
|
-
return this;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
let fsMap;
|
|
49
|
-
if (!libPath) {
|
|
50
|
-
|
|
51
|
-
// This is a node only option. If no lib path is passed, attempt to resolve ES types from node_modules.
|
|
52
|
-
fsMap = await createDefaultMapFromNodeModules(COMPILER_OPTIONS, ts);
|
|
53
|
-
|
|
54
|
-
} else if (libPath.endsWith('.d.ts')) {
|
|
55
|
-
|
|
56
|
-
// If the libPath is a '.d.ts' file then load it and add it
|
|
57
|
-
const types = await fetch(libPath).then(r => r.text());
|
|
58
|
-
fsMap = new Map([['/lib.d.ts', types]]);
|
|
59
|
-
|
|
60
|
-
} else {
|
|
61
|
-
|
|
62
|
-
// A libPath was supplied, but not to a '.d.ts', so assume this is a path to types
|
|
63
|
-
fsMap = await createDefaultMapFromCDN(COMPILER_OPTIONS, libPath, ts);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Set up the virtual file system and environment
|
|
67
|
-
const system = createSystem(fsMap);
|
|
68
|
-
this._env = createVirtualTypeScriptEnvironment(system, Array.from(fsMap.keys()), ts, COMPILER_OPTIONS);
|
|
69
|
-
|
|
70
|
-
return this;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Creates program from fileName and scriptsContents
|
|
75
|
-
*
|
|
76
|
-
* @param {[string, string][]} newFiles - An array of {[path]: [content]} which represent the files in the program
|
|
77
|
-
* @param {string[]} removedFiles - An array of file paths that should be removed from the program
|
|
78
|
-
* @returns {{ parser: ScriptParser, program: ts.Program,
|
|
79
|
-
* errors: import('./parsers/script-parser.js').ValidationError[]}} - The extracted attributes
|
|
80
|
-
*/
|
|
81
|
-
updateProgram(newFiles = [], removedFiles = []) {
|
|
82
|
-
const errors = [];
|
|
83
|
-
|
|
84
|
-
// Add custom files to the map
|
|
85
|
-
newFiles.forEach(([filePath, fileContent]) => {
|
|
86
|
-
if (this._env.getSourceFile(filePath)) {
|
|
87
|
-
this._env.updateFile(filePath, fileContent);
|
|
88
|
-
} else {
|
|
89
|
-
this._env.createFile(filePath, fileContent);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Remove deleted Files
|
|
94
|
-
removedFiles.forEach((filePath) => {
|
|
95
|
-
this._env.deleteFile(filePath);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
this.parser = new ScriptParser(this._env);
|
|
100
|
-
this.program = this._env.languageService.getProgram();
|
|
101
|
-
|
|
102
|
-
// Check for compiler errors
|
|
103
|
-
this.parser.validateProgram(errors);
|
|
104
|
-
|
|
105
|
-
this._errors = errors;
|
|
106
|
-
|
|
107
|
-
return errors;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Returns all the valid ESM Scripts within a file
|
|
112
|
-
* @param {string} fileName - The file name in the program to check
|
|
113
|
-
* @returns {Map<string, import('typescript').Node>} - A map of valid ESM Script <names, nodes> within the file
|
|
114
|
-
*/
|
|
115
|
-
getAllEsmScripts(fileName) {
|
|
116
|
-
|
|
117
|
-
const typeChecker = this.program.getTypeChecker();
|
|
118
|
-
|
|
119
|
-
// Find the Script class in the PlayCanvas namespace
|
|
120
|
-
const pcTypes = this.program.getSourceFile('/playcanvas.d.ts');
|
|
121
|
-
|
|
122
|
-
if (!pcTypes) {
|
|
123
|
-
throw new Error('PlayCanvas Types must be supplied');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const esmScripts = new Map();
|
|
127
|
-
|
|
128
|
-
// Parse the source file and pc types
|
|
129
|
-
const sourceFile = this.program.getSourceFile(fileName);
|
|
130
|
-
|
|
131
|
-
// In a special edge case where the files contents are empty, the sourcefile will be undefined
|
|
132
|
-
// So we should early out
|
|
133
|
-
if (sourceFile === undefined) {
|
|
134
|
-
return esmScripts;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (sourceFile === null) {
|
|
138
|
-
throw new Error(`Source file ${fileName} not found`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Extract all exported nodes
|
|
142
|
-
const nodes = getExportedNodes(this.program, sourceFile);
|
|
143
|
-
|
|
144
|
-
const esmScriptClass = pcTypes.statements.find(node => node.kind === ts.SyntaxKind.ClassDeclaration && node.name.text === 'Script')?.symbol;
|
|
145
|
-
|
|
146
|
-
const isScriptNameMember = member => (
|
|
147
|
-
ts.isPropertyDeclaration(member) && // Is a property declaration
|
|
148
|
-
ts.isIdentifier(member.name) &&
|
|
149
|
-
member.name.text === 'scriptName' &&
|
|
150
|
-
isStaticMember(member) &&
|
|
151
|
-
ts.isStringLiteral(member.initializer)
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
// Check if the file exports a class that inherits from `Script`
|
|
155
|
-
nodes.forEach((node, name) => {
|
|
156
|
-
if (isAliasedClassDeclaration(node, typeChecker) && inheritsFrom(node, typeChecker, esmScriptClass)) {
|
|
157
|
-
|
|
158
|
-
// Check for a static scriptName property and use that as the name if it exists
|
|
159
|
-
const scriptNameMember = node.members.find(isScriptNameMember);
|
|
160
|
-
|
|
161
|
-
// If the scriptName property exists, use that as the name
|
|
162
|
-
if (scriptNameMember) {
|
|
163
|
-
name = scriptNameMember.initializer.text;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
esmScripts.set(name, node);
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
return esmScripts;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Analyzes the specified TypeScript file and extracts metadata from JSDoc comments.
|
|
175
|
-
*
|
|
176
|
-
* @param {string} fileName - The name of the file in the program to run
|
|
177
|
-
* @returns {[results: object, errors: import('./parsers/script-parser.js').ValidationError[]]} - The extracted attributes
|
|
178
|
-
*/
|
|
179
|
-
parseAttributes(fileName) {
|
|
180
|
-
const results = {};
|
|
181
|
-
const errors = [...this._errors];
|
|
182
|
-
|
|
183
|
-
if (errors.length > 0) {
|
|
184
|
-
return [results, errors];
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Extract all exported nodes
|
|
188
|
-
const nodes = this.getAllEsmScripts(fileName);
|
|
189
|
-
|
|
190
|
-
// Extract attributes from each script
|
|
191
|
-
nodes.forEach((node, name) => {
|
|
192
|
-
const opts = results[toLowerCamelCase(name)] = { attributes: {}, errors: [] };
|
|
193
|
-
this.parser.extractAttributes(node, opts);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
return [results, errors];
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Analyzes the specified TypeScript file and extracts metadata from JSDoc comments.
|
|
201
|
-
* @param {string} fileName - The name of the file to run
|
|
202
|
-
* @returns {[results: object, errors: import('./parsers/script-parser.js').ValidationError[]]} - The extracted attributes
|
|
203
|
-
*/
|
|
204
|
-
getAttributes(fileName) {
|
|
205
|
-
const results = {};
|
|
206
|
-
const errors = [...this._errors];
|
|
207
|
-
|
|
208
|
-
if (errors.length > 0) {
|
|
209
|
-
return [results, errors];
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const typeChecker = this.program.getTypeChecker();
|
|
213
|
-
|
|
214
|
-
// Find the Script class in the PlayCanvas namespace
|
|
215
|
-
const pcTypes = this.program.getSourceFile('/playcanvas.d.ts');
|
|
216
|
-
const esmScriptClass = pcTypes.statements.find(node => node.kind === ts.SyntaxKind.ClassDeclaration && node.name.text === 'Script')?.symbol;
|
|
217
|
-
|
|
218
|
-
// Parse the source file and pc types
|
|
219
|
-
const sourceFile = this.program.getSourceFile(fileName);
|
|
220
|
-
|
|
221
|
-
if (!sourceFile) {
|
|
222
|
-
throw new Error(`Source file ${fileName} not found`);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const nodes = flatMapAnyNodes(sourceFile, (node) => {
|
|
226
|
-
if (!ts.isClassDeclaration(node)) {
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return inheritsFrom(node, typeChecker, esmScriptClass) || isInterface(node);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
for (const node of nodes) {
|
|
234
|
-
const name = toLowerCamelCase(node.name.text);
|
|
235
|
-
const members = [];
|
|
236
|
-
|
|
237
|
-
for (const member of node.members) {
|
|
238
|
-
if (member.kind !== ts.SyntaxKind.PropertyDeclaration || isStaticMember(member)) {
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Extract JSDoc tags
|
|
243
|
-
const tags = member.jsDoc ? member.jsDoc.map(jsdoc => jsdoc.tags?.map(tag => tag.tagName.getText()) ?? []).flat() : [];
|
|
244
|
-
|
|
245
|
-
const name = member.name.getText();
|
|
246
|
-
const type = getType(member, typeChecker);
|
|
247
|
-
if (!type) {
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const namePos = ts.getLineAndCharacterOfPosition(member.getSourceFile(), member.name.getStart());
|
|
252
|
-
|
|
253
|
-
const jsdocNode = member.jsDoc && member.jsDoc[member.jsDoc.length - 1];
|
|
254
|
-
const jsdocPos = jsdocNode ? ts.getLineAndCharacterOfPosition(member.getSourceFile(), jsdocNode.getStart()) : null;
|
|
255
|
-
|
|
256
|
-
const data = {
|
|
257
|
-
name,
|
|
258
|
-
type: type.name + (type.array ? '[]' : ''),
|
|
259
|
-
isAttribute: tags.includes('attribute'),
|
|
260
|
-
start: jsdocNode ? { line: jsdocPos.line + 1, column: jsdocPos.character + 1 } :
|
|
261
|
-
{ line: namePos.line + 1, column: namePos.character + 1 },
|
|
262
|
-
end: { line: namePos.line + 1, column: namePos.character + name.length + 1 }
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
members.push(data);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
results[name] = members;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return [results, errors];
|
|
272
|
-
}
|
|
273
|
-
}
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import * as ts from 'typescript';
|
|
2
|
-
|
|
3
|
-
import { ParsingError } from './parsing-error.js';
|
|
4
|
-
import { hasTag } from '../utils/attribute-utils.js';
|
|
5
|
-
import { parseTag, validateTag } from '../utils/tag-utils.js';
|
|
6
|
-
import { getLiteralValue, getType } from '../utils/ts-utils.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A class to parse JSDoc comments and extract attribute metadata.
|
|
10
|
-
*/
|
|
11
|
-
export class AttributeParser {
|
|
12
|
-
/**
|
|
13
|
-
* Creates a new instance of the ScriptParser class.
|
|
14
|
-
*
|
|
15
|
-
* @param {Map<string, string>} tags - An set of tag definitions and their validators to add to the parser
|
|
16
|
-
* @param {object} env - The TypeScript environment to use
|
|
17
|
-
* @param {Map<string, Function>} typeSerializerMap - A map of custom serializers
|
|
18
|
-
*/
|
|
19
|
-
constructor(tags = new Map(), env, typeSerializerMap) {
|
|
20
|
-
|
|
21
|
-
this.env = env;
|
|
22
|
-
this.tagTypeAnnotations = tags;
|
|
23
|
-
this.typeSerializerMap = typeSerializerMap;
|
|
24
|
-
this.program = env.languageService.getProgram();
|
|
25
|
-
this.typeChecker = this.program.getTypeChecker();
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Parses a JSdoc comment and extracts any associated metadata if the `@attribute`
|
|
31
|
-
* modifier is used. Returns null if this is not an attribute comment.
|
|
32
|
-
*
|
|
33
|
-
* @param {ts.Node} node - The node to parse
|
|
34
|
-
* @param {ParsingError[]} errors - An array to store any parsing errors
|
|
35
|
-
* @param {boolean} requiresAttributeTag - Whether the comment must have an attribute tag
|
|
36
|
-
* @returns {*} - The extracted metadata or null if no metadata was found
|
|
37
|
-
*/
|
|
38
|
-
parseAttributeComment(node, errors = [], requiresAttributeTag = true) {
|
|
39
|
-
|
|
40
|
-
// Check if the comment has an attribute tag
|
|
41
|
-
if (!requiresAttributeTag || hasTag('attribute', node)) {
|
|
42
|
-
|
|
43
|
-
// Fetch the primary attribute metadata
|
|
44
|
-
const attribute = this.getNodeAsAttribute(node, errors);
|
|
45
|
-
|
|
46
|
-
if (!attribute) return;
|
|
47
|
-
|
|
48
|
-
// Extract the description from the summary section
|
|
49
|
-
attribute.description = node.jsDoc?.[0]?.comment || '';
|
|
50
|
-
|
|
51
|
-
// Extract metadata from custom tags
|
|
52
|
-
const jsDocTags = node.jsDoc?.[0]?.tags ?? [];
|
|
53
|
-
jsDocTags.forEach((tag) => {
|
|
54
|
-
let commentText = tag.comment;
|
|
55
|
-
|
|
56
|
-
// Check if the tag is a supported tag
|
|
57
|
-
if (this.tagTypeAnnotations.has(tag.tagName.text)) {
|
|
58
|
-
try {
|
|
59
|
-
const value = parseTag(commentText);
|
|
60
|
-
const tagTypeAnnotation = this.tagTypeAnnotations.get(tag.tagName.text);
|
|
61
|
-
|
|
62
|
-
// Tags like @resource with string values do not need quotations, so we need to manually add them
|
|
63
|
-
if (typeof value === 'string') commentText = `"${commentText}"`;
|
|
64
|
-
|
|
65
|
-
validateTag(commentText, tagTypeAnnotation, this.env);
|
|
66
|
-
attribute[tag.tagName.text] = value;
|
|
67
|
-
|
|
68
|
-
} catch (error) {
|
|
69
|
-
const file = node.getSourceFile();
|
|
70
|
-
const { line, character } = file.getLineAndCharacterOfPosition(tag.getStart());
|
|
71
|
-
const parseError = new ParsingError(tag, `Error (${line}, ${character}): Parsing Tag '@${tag.tagName.text} ${commentText}' - ${error.message}`);
|
|
72
|
-
errors.push(parseError);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
return attribute;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Extract the public members and their values from a TypeScript node.
|
|
83
|
-
*
|
|
84
|
-
* @param {ts.Node} node - The node to parse
|
|
85
|
-
* @param {ParsingError[]} errors - An array to store any parsing errors
|
|
86
|
-
* @returns {*} - The extracted members
|
|
87
|
-
*/
|
|
88
|
-
getNodeAsAttribute(node, errors = []) {
|
|
89
|
-
|
|
90
|
-
const name = node.name && ts.isIdentifier(node.name) && node.name.text;
|
|
91
|
-
const { type, name: typeName, array } = getType(node, this.typeChecker);
|
|
92
|
-
const enums = this.getEnumMembers(node, errors);
|
|
93
|
-
let value = null;
|
|
94
|
-
|
|
95
|
-
// we don't need to serialize the value for arrays
|
|
96
|
-
const serializer = !array && this.typeSerializerMap.get(typeName);
|
|
97
|
-
if (serializer) {
|
|
98
|
-
try {
|
|
99
|
-
value = serializer(node.initializer ?? node, this.typeChecker);
|
|
100
|
-
} catch (error) {
|
|
101
|
-
errors.push(error);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return { type, name, typeName, array, value, enum: enums };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Extracts the members of an enum from a TypeScript node.
|
|
111
|
-
*
|
|
112
|
-
* @param {import('typescript').Node} node - The TypeScript node to analyze
|
|
113
|
-
* @param {ParsingError[]} errors - An array to store any parsing errors
|
|
114
|
-
* @returns {Object<string, *>[]} - An array of enum members
|
|
115
|
-
*/
|
|
116
|
-
getEnumMembers(node, errors) {
|
|
117
|
-
let typeNode = null;
|
|
118
|
-
let members = [];
|
|
119
|
-
|
|
120
|
-
// Check if there's a type annotation directly on the variable declaration
|
|
121
|
-
if (node.type) {
|
|
122
|
-
typeNode = node;
|
|
123
|
-
} else {
|
|
124
|
-
// Check for JSDoc annotations
|
|
125
|
-
const jsDocs = ts.getJSDocTags(node);
|
|
126
|
-
const typeTag = jsDocs.find(doc => doc.tagName.text === 'type');
|
|
127
|
-
if (typeTag && ts.isJSDocTypeTag(typeTag)) {
|
|
128
|
-
typeNode = typeTag.typeExpression;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Also consider the elementType
|
|
133
|
-
const type = typeNode && (typeNode.type.elementType ?? typeNode.type);
|
|
134
|
-
|
|
135
|
-
if (typeNode && ts.isTypeReferenceNode(type)) {
|
|
136
|
-
|
|
137
|
-
// resolve the symbol of the type
|
|
138
|
-
let symbol = this.typeChecker.getSymbolAtLocation(type.typeName);
|
|
139
|
-
|
|
140
|
-
// Resolve aliases, which are common with imports
|
|
141
|
-
if (symbol && symbol.flags & ts.SymbolFlags.Alias) {
|
|
142
|
-
symbol = this.typeChecker.getAliasedSymbol(symbol);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (symbol) {
|
|
146
|
-
const declarations = symbol.getDeclarations();
|
|
147
|
-
|
|
148
|
-
if (declarations && declarations.length > 0) {
|
|
149
|
-
|
|
150
|
-
declarations.forEach((declaration) => {
|
|
151
|
-
|
|
152
|
-
// Check if the declaration is a TypeScript enum
|
|
153
|
-
if (ts.isEnumDeclaration(declaration)) {
|
|
154
|
-
members = declaration.members.map(member => ({ [member.name.text]: member.initializer.text }));
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Additionally check for JSDoc enum tag
|
|
158
|
-
const jsDocs = ts.getJSDocTags(declaration);
|
|
159
|
-
const enumTag = jsDocs.find(doc => doc.tagName.text === 'enum');
|
|
160
|
-
if (enumTag && ts.isJSDocEnumTag(enumTag)) {
|
|
161
|
-
// This assumes enum members are correctly annotated in JSDoc comments
|
|
162
|
-
// Example: @enum {type} EnumName { Member1, Member2 }
|
|
163
|
-
members = members.concat(this.getMembers(declaration.initializer, errors)); // declaration.initializer.properties.map(member => member.name.getText());
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return members;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Extract the public members and their values from a TypeScript node.
|
|
175
|
-
*
|
|
176
|
-
* @param {ts.Node} node - The node to parse
|
|
177
|
-
* @returns {any[]} - The extracted members
|
|
178
|
-
*/
|
|
179
|
-
getMembers(node) {
|
|
180
|
-
|
|
181
|
-
const members = [];
|
|
182
|
-
|
|
183
|
-
if (ts.isObjectLiteralExpression(node)) {
|
|
184
|
-
node.properties.forEach((property) => {
|
|
185
|
-
|
|
186
|
-
if (ts.isPropertyAssignment(property)) {
|
|
187
|
-
const isValidName = ts.isIdentifier(property.name) || ts.isStringLiteral(property.name);
|
|
188
|
-
const name = isValidName && property.name.text;
|
|
189
|
-
const value = getLiteralValue(property.initializer, this.typeChecker);
|
|
190
|
-
members.push({ [name]: value });
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return members;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export class ParsingError {
|
|
2
|
-
constructor(node, message) {
|
|
3
|
-
this.node = node; // AST node which caused the error
|
|
4
|
-
this.message = message; // Description of the error
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
toString() {
|
|
8
|
-
// Ensure node.location exists and has the necessary properties
|
|
9
|
-
const file = this.node.getSourceFile();
|
|
10
|
-
const fileName = file.fileName;
|
|
11
|
-
const start = this.node.getSourceFile().getLineAndCharacterOfPosition(this.node.getStart());
|
|
12
|
-
return `ParsingError: ${this.message} at ${fileName}:${start.line + 1}:${start.character}`;
|
|
13
|
-
}
|
|
14
|
-
}
|