@playcanvas/attribute-parser 1.0.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/.github/workflows/ci.yml +28 -0
- package/README.md +76 -0
- package/package.json +24 -0
- package/src/index.js +226 -0
- package/src/parsers/attribute-parser.js +280 -0
- package/src/parsers/parsing-error.js +14 -0
- package/src/parsers/script-parser.js +404 -0
- package/src/utils/attribute-utils.js +72 -0
- package/src/utils/generic-utils.js +18 -0
- package/src/utils/tag-utils.js +65 -0
- package/src/utils/ts-utils.js +501 -0
- package/test/fixtures/asset.invalid.js +26 -0
- package/test/fixtures/asset.valid.js +25 -0
- package/test/fixtures/checkbox.valid.js +23 -0
- package/test/fixtures/color.valid.js +23 -0
- package/test/fixtures/curve.invalid.js +28 -0
- package/test/fixtures/curve.valid.js +49 -0
- package/test/fixtures/entity.valid.js +23 -0
- package/test/fixtures/enum.valid.js +78 -0
- package/test/fixtures/export.import.js +3 -0
- package/test/fixtures/export.valid.js +23 -0
- package/test/fixtures/inherit.valid.js +52 -0
- package/test/fixtures/intellisense.valid.js +37 -0
- package/test/fixtures/json.invalid.js +22 -0
- package/test/fixtures/json.valid.js +103 -0
- package/test/fixtures/misc.invalid.js +25 -0
- package/test/fixtures/misc.valid.js +33 -0
- package/test/fixtures/numeric.invalid.js +38 -0
- package/test/fixtures/numeric.valid.js +63 -0
- package/test/fixtures/playcanvas.d.ts +47963 -0
- package/test/fixtures/program.invalid.js +11 -0
- package/test/fixtures/text.valid.js +30 -0
- package/test/fixtures/vector.invalid.js +13 -0
- package/test/fixtures/vector.valid.js +23 -0
- package/test/tests/invalid/asset.test.js +48 -0
- package/test/tests/invalid/curve.test.js +24 -0
- package/test/tests/invalid/json.test.js +28 -0
- package/test/tests/invalid/misc.test.js +42 -0
- package/test/tests/invalid/numeric.test.js +56 -0
- package/test/tests/invalid/program.test.js +18 -0
- package/test/tests/invalid/vector.test.js +29 -0
- package/test/tests/valid/asset.test.js +51 -0
- package/test/tests/valid/checkbox.test.js +50 -0
- package/test/tests/valid/color.test.js +50 -0
- package/test/tests/valid/curve.test.js +77 -0
- package/test/tests/valid/entity.test.js +50 -0
- package/test/tests/valid/enum.test.js +74 -0
- package/test/tests/valid/export.test.js +56 -0
- package/test/tests/valid/inherit.test.js +103 -0
- package/test/tests/valid/intellisense.test.js +36 -0
- package/test/tests/valid/json.test.js +150 -0
- package/test/tests/valid/misc.test.js +47 -0
- package/test/tests/valid/numeric.test.js +104 -0
- package/test/tests/valid/text.test.js +59 -0
- package/test/tests/valid/vector.test.js +54 -0
- package/test/utils.js +53 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import { DocNodeKind } from '@microsoft/tsdoc';
|
|
2
|
+
import { knownLibFilesForCompilerOptions } from '@typescript/vfs';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
import { ParsingError } from '../parsers/parsing-error.js';
|
|
5
|
+
|
|
6
|
+
export function createDefaultMapFromCDN(options, basePath, ts) {
|
|
7
|
+
var fsMap = new Map();
|
|
8
|
+
var files = knownLibFilesForCompilerOptions(options, ts);
|
|
9
|
+
var prefix = `${basePath}js/jsdoc-parser/types/`;
|
|
10
|
+
|
|
11
|
+
async function uncached() {
|
|
12
|
+
const contentPromises = files.map((lib) => {
|
|
13
|
+
return fetch(prefix + lib).then((resp) => {
|
|
14
|
+
if (!resp.ok) {
|
|
15
|
+
throw new Error(`Failed to fetch ${prefix + lib}: ${resp.statusText}`);
|
|
16
|
+
}
|
|
17
|
+
return resp.text();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const contents = await Promise.all(contentPromises);
|
|
22
|
+
|
|
23
|
+
contents.forEach(function (text, index) {
|
|
24
|
+
return fsMap.set('/' + files[index], text);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return fsMap;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return uncached();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolves the original symbol in case of alias.
|
|
35
|
+
* @param {import('typescript').TypeChecker} typeChecker - The TypeScript type checker
|
|
36
|
+
* @param {import('typescript').Symbol} symbol - The TypeScript symbol
|
|
37
|
+
* @returns {import('typescript').Symbol} - The resolved symbol
|
|
38
|
+
*/
|
|
39
|
+
function resolveAliasedSymbol(typeChecker, symbol) {
|
|
40
|
+
while (symbol.flags & ts.SymbolFlags.Alias) {
|
|
41
|
+
symbol = typeChecker.getAliasedSymbol(symbol);
|
|
42
|
+
}
|
|
43
|
+
return symbol;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns an array of exported nodes from a TypeScript source file.
|
|
48
|
+
* @param {import('typescript').Program} program - The TypeScript program
|
|
49
|
+
* @param {import('typescript').SourceFile} sourceFile - The TypeScript source file
|
|
50
|
+
* @returns {Set<import('typescript').Node>} - A Set of exported nodes
|
|
51
|
+
*/
|
|
52
|
+
export function getExportedNodes(program, sourceFile) {
|
|
53
|
+
if (!program || !sourceFile) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const typeChecker = program.getTypeChecker();
|
|
58
|
+
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
|
|
59
|
+
|
|
60
|
+
if (!moduleSymbol) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const exportedSymbols = typeChecker.getExportsOfModule(moduleSymbol);
|
|
65
|
+
|
|
66
|
+
// Find the actual declaration nodes for each exported symbol
|
|
67
|
+
const exportedNodes = [];
|
|
68
|
+
|
|
69
|
+
exportedSymbols.forEach((symbol) => {
|
|
70
|
+
const resolvedSymbol = resolveAliasedSymbol(typeChecker, symbol);
|
|
71
|
+
if (resolvedSymbol.declarations) {
|
|
72
|
+
exportedNodes.push(...resolvedSymbol.declarations);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return new Set(exportedNodes);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Checks if the node or its aliased symbol is a class declaration.
|
|
81
|
+
*
|
|
82
|
+
* @param {import('typescript').Node} node - The TypeScript node
|
|
83
|
+
* @param {import('typescript').TypeChecker} typeChecker - The TypeScript type checker
|
|
84
|
+
* @returns {boolean} - True if the node or its original declaration is a class declaration, false otherwise
|
|
85
|
+
*/
|
|
86
|
+
export function isAliasedClassDeclaration(node, typeChecker) {
|
|
87
|
+
if (ts.isClassDeclaration(node)) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
// Try to resolve the original declaration
|
|
91
|
+
if (ts.isIdentifier(node.name)) {
|
|
92
|
+
const symbol = typeChecker.getSymbolAtLocation(node.name);
|
|
93
|
+
if (symbol) {
|
|
94
|
+
const resolvedSymbol = resolveAliasedSymbol(typeChecker, symbol);
|
|
95
|
+
if (resolvedSymbol.declarations) {
|
|
96
|
+
return resolvedSymbol.declarations.some(ts.isClassDeclaration);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Determines if a TypeScript node inherits from a specific base class.
|
|
105
|
+
* @param {ts.ClassDeclaration} node - The TypeScript node to analyze
|
|
106
|
+
* @param {ts.TypeChecker} typeChecker - The TypeScript type checker
|
|
107
|
+
* @param {ts.Symbol} baseClassSymbol - The base class symbol to check inheritance against
|
|
108
|
+
* @returns {boolean} - True if the node inherits from the base class
|
|
109
|
+
*/
|
|
110
|
+
export function inheritsFrom(node, typeChecker, baseClassSymbol) {
|
|
111
|
+
if (node.heritageClauses) {
|
|
112
|
+
for (const clause of node.heritageClauses) {
|
|
113
|
+
for (const type of clause.types) {
|
|
114
|
+
// Get the symbol of the expression
|
|
115
|
+
const symbol = typeChecker.getTypeAtLocation(type.expression).getSymbol();
|
|
116
|
+
|
|
117
|
+
// Check if the symbol is the base class symbol
|
|
118
|
+
if (symbol === baseClassSymbol) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Get the declaration of the symbol to check its heritage clauses
|
|
123
|
+
if (symbol?.declarations.length > 0) {
|
|
124
|
+
const declaration = symbol.declarations[0];
|
|
125
|
+
if (inheritsFrom(declaration, typeChecker, baseClassSymbol)) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Recursively gathers and flat maps and nodes that meet a specific filter.
|
|
137
|
+
* @param {import('typescript').Node} node - The TypeScript node to analyze
|
|
138
|
+
* @param {function} filter - A function to filter the nodes
|
|
139
|
+
* @returns {import('typescript').Node[]} - An array of exported class declarations
|
|
140
|
+
*/
|
|
141
|
+
export function flatMapAnyNodes(node, filter = () => true) {
|
|
142
|
+
const results = [];
|
|
143
|
+
|
|
144
|
+
function visit(node) {
|
|
145
|
+
// Check if this node is an exported class declaration
|
|
146
|
+
if (filter(node)) {
|
|
147
|
+
results.push(node);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Visit each child node
|
|
151
|
+
ts.forEachChild(node, visit);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Start the search with the root node
|
|
155
|
+
visit(node);
|
|
156
|
+
|
|
157
|
+
return results;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Takes a AST Node, and if it's a class declaration, it will return it's super classes
|
|
162
|
+
*
|
|
163
|
+
* @param {import('typescript').Node} node - The node to check
|
|
164
|
+
* @param {import('typescript').TypeChecker} typeChecker - The programs typechecker
|
|
165
|
+
* @returns {import('typescript').Node[]} - The analyzed node and any super classes in the inheritance chain
|
|
166
|
+
*/
|
|
167
|
+
function getSuperClasses(node, typeChecker) {
|
|
168
|
+
const superClasses = [node];
|
|
169
|
+
|
|
170
|
+
let currentClass = node;
|
|
171
|
+
|
|
172
|
+
while (currentClass) {
|
|
173
|
+
const heritageClauses = currentClass.heritageClauses;
|
|
174
|
+
if (!heritageClauses) {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const extendsClause = heritageClauses.find(clause => clause.token === ts.SyntaxKind.ExtendsKeyword);
|
|
179
|
+
if (!extendsClause || extendsClause.types.length === 0) {
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const superClassType = typeChecker.getTypeAtLocation(extendsClause.types[0]);
|
|
184
|
+
const superClassSymbol = superClassType.getSymbol();
|
|
185
|
+
|
|
186
|
+
if (!superClassSymbol) {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const superClassDeclaration = superClassSymbol.declarations.find(ts.isClassDeclaration);
|
|
191
|
+
if (!superClassDeclaration) {
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
superClasses.push(superClassDeclaration);
|
|
196
|
+
currentClass = superClassDeclaration;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return superClasses; // .reverse(); // To return the array in order from the base class to the top-level class
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Retrieves the JSDoc-style comments associated with a specific AST node.
|
|
204
|
+
*
|
|
205
|
+
* @param {import('typescript').Node} node - The TypeScript node to analyze
|
|
206
|
+
* @param {string} text - The full text content of the source file
|
|
207
|
+
* @param {import('typescript').TypeChecker} typeChecker - The TypeScript type checker
|
|
208
|
+
* @returns {{ memberName: string, member: ts.Node, range: import('typescript').CommentRange}[]} - An array of comment ranges
|
|
209
|
+
*/
|
|
210
|
+
export function getJSDocCommentRanges(node, text, typeChecker) {
|
|
211
|
+
const commentRanges = [];
|
|
212
|
+
|
|
213
|
+
if (ts.isClassDeclaration(node)) {
|
|
214
|
+
// get an array of the class an all parent classed
|
|
215
|
+
const heritageChain = getSuperClasses(node, typeChecker);
|
|
216
|
+
|
|
217
|
+
// iterate over the heritance chain
|
|
218
|
+
heritageChain.forEach((classNode) => {
|
|
219
|
+
// for each class iterate over it's class members
|
|
220
|
+
classNode.members.forEach((member) => {
|
|
221
|
+
if (ts.isPropertyDeclaration(member) || ts.isSetAccessor(member)) {
|
|
222
|
+
const memberName = member.name && ts.isIdentifier(member.name) ? member.name.text : 'unnamed';
|
|
223
|
+
const ranges = getLeadingBlockCommentRanges(member, text);
|
|
224
|
+
if (ranges.length > 0) {
|
|
225
|
+
commentRanges.push({
|
|
226
|
+
memberName,
|
|
227
|
+
member,
|
|
228
|
+
range: ranges[0]
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return commentRanges;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const ASTERISK = 42;
|
|
240
|
+
const SLASH = 47;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Retrieves the JSDoc-style comments blocks associated with a specific AST node.
|
|
244
|
+
* @param {import('typescript').Node} node - The TypeScript node to analyze
|
|
245
|
+
* @param {string} text - The full text content of the source file
|
|
246
|
+
* @returns {import('typescript').CommentRange[]} - An array of comment ranges
|
|
247
|
+
*/
|
|
248
|
+
export function getLeadingBlockCommentRanges(node, text) {
|
|
249
|
+
const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
|
|
250
|
+
return ranges.filter(({ pos }) => {
|
|
251
|
+
return (
|
|
252
|
+
text.charCodeAt(pos + 1) === ASTERISK &&
|
|
253
|
+
text.charCodeAt(pos + 2) === ASTERISK &&
|
|
254
|
+
text.charCodeAt(pos + 3) !== SLASH
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Recursively extract text content from a DocNode.
|
|
261
|
+
* @param {tsdoc.DocNode} node - The DocNode from which to extract text.
|
|
262
|
+
* @returns {string} - The concatenated string of all text content.
|
|
263
|
+
*/
|
|
264
|
+
export function extractTextFromDocNode(node) {
|
|
265
|
+
// Check if the current node is a PlainText node
|
|
266
|
+
if (node.kind === DocNodeKind.PlainText) {
|
|
267
|
+
return node.text.trim(); // Cast and return as we found a PlainText node
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// If not, recursively search its children
|
|
271
|
+
for (const child of node.getChildNodes()) {
|
|
272
|
+
const result = extractTextFromDocNode(child);
|
|
273
|
+
if (result !== null) {
|
|
274
|
+
return result; // Return the first PlainText node found in the subtree
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Extracts all imports from a TypeScript source file.
|
|
283
|
+
* @param {ts.Node} node - The TypeScript node to analyze
|
|
284
|
+
* @returns {string[]} - An array of import statements
|
|
285
|
+
*/
|
|
286
|
+
export function getLeadingComments(node) {
|
|
287
|
+
const fullText = node.getFullText();
|
|
288
|
+
const commentRanges = ts.getLeadingCommentRanges(fullText, 0);
|
|
289
|
+
|
|
290
|
+
if (!commentRanges) return [];
|
|
291
|
+
return commentRanges.map(range => fullText.slice(range.pos, range.end));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Determines if a TypeScript node is an enum.
|
|
296
|
+
* @param {import('typescript').Node} node - The TypeScript node to analyze
|
|
297
|
+
* @returns {boolean} - True if the node is an enum
|
|
298
|
+
*/
|
|
299
|
+
export function isEnum(node) {
|
|
300
|
+
// Check for TypeScript native enum
|
|
301
|
+
if (ts.isEnumDeclaration(node)) {
|
|
302
|
+
return true; // The node is a TypeScript enum
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Check for JSDoc @enum tag in a variable declaration
|
|
306
|
+
if (ts.isVariableDeclaration(node)) {
|
|
307
|
+
const jsDocs = ts.getJSDocTags(node);
|
|
308
|
+
const hasEnumTag = jsDocs.some(doc => doc.tagName.text === 'enum');
|
|
309
|
+
return hasEnumTag;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Gets the inferred type of a TypeScript node.
|
|
317
|
+
*
|
|
318
|
+
* @param {ts.Node} node - The TypeScript node to analyze
|
|
319
|
+
* @param {ts.TypeChecker} typeChecker - The TypeScript type checker
|
|
320
|
+
* @returns {{ array: boolean, name: string | undefined, type: ts.Type | null }} - The inferred type of the node
|
|
321
|
+
*/
|
|
322
|
+
export function getType(node, typeChecker) {
|
|
323
|
+
if (node?.name?.kind === ts.SyntaxKind.Identifier) {
|
|
324
|
+
const type = typeChecker.getTypeAtLocation(node);
|
|
325
|
+
const array = typeChecker.isArrayType(type);
|
|
326
|
+
const actualType = array ? typeChecker.getElementTypeOfArrayType(type) : type;
|
|
327
|
+
const name = typeChecker.typeToString(actualType);
|
|
328
|
+
|
|
329
|
+
return { type: actualType, name, array };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Parses a numerical string into a number. Handles hexadecimal, binary,
|
|
337
|
+
* octal, exponential notation and underscore notation
|
|
338
|
+
*
|
|
339
|
+
* @param {string} input - The string to parse
|
|
340
|
+
* @returns {number} The parsed number, or NaN if the input is not a number
|
|
341
|
+
*/
|
|
342
|
+
export function parseNumber(input) {
|
|
343
|
+
// Remove underscores from the string to handle underscore notation
|
|
344
|
+
let cleanedInput = input.replace(/_/g, '').toLowerCase();
|
|
345
|
+
|
|
346
|
+
// Identify and isolate unary operators
|
|
347
|
+
let unaryOperator = null;
|
|
348
|
+
if (cleanedInput.startsWith('+') || cleanedInput.startsWith('-') || cleanedInput.startsWith('~')) {
|
|
349
|
+
unaryOperator = cleanedInput[0];
|
|
350
|
+
cleanedInput = cleanedInput.slice(1).trim();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Check for different bases and handle appropriately
|
|
354
|
+
let result;
|
|
355
|
+
if (cleanedInput === 'NaN' || cleanedInput === '') {
|
|
356
|
+
result = NaN;
|
|
357
|
+
} else if (cleanedInput.startsWith('0b')) {
|
|
358
|
+
result = parseInt(cleanedInput.slice(2), 2);
|
|
359
|
+
} else if (cleanedInput.startsWith('0o')) {
|
|
360
|
+
result = parseInt(cleanedInput.slice(2), 8);
|
|
361
|
+
} else if (cleanedInput.startsWith('0x')) {
|
|
362
|
+
result = parseInt(cleanedInput.slice(2), 16);
|
|
363
|
+
} else if (cleanedInput.includes('e')) {
|
|
364
|
+
result = parseFloat(cleanedInput); // Exponential notation
|
|
365
|
+
} else if (!isNaN(Number(cleanedInput))) {
|
|
366
|
+
result = Number(cleanedInput); // Decimal, including floating-point
|
|
367
|
+
} else {
|
|
368
|
+
result = NaN;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Apply unary operator if present
|
|
372
|
+
if (unaryOperator === '+') {
|
|
373
|
+
result = +result; // Positive sign can be ignored, but explicitly using for clarity
|
|
374
|
+
} else if (unaryOperator === '-') {
|
|
375
|
+
result = -result;
|
|
376
|
+
} else if (unaryOperator === '~') {
|
|
377
|
+
result = ~result; // Bitwise NOT
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Parses a string representationn of a numerical array into an array of numbers.
|
|
385
|
+
*
|
|
386
|
+
* @param {string} input - The string to parse
|
|
387
|
+
* @returns {number[]} - The parsed array of numbers
|
|
388
|
+
* @throws {Error} - If the input contains invalid characters or is not a valid array
|
|
389
|
+
*/
|
|
390
|
+
export function parseStringToNumericalArray(input) {
|
|
391
|
+
// Remove unnecessary whitespace
|
|
392
|
+
const sanitizedInput = input.replace(/\s+/g, '');
|
|
393
|
+
|
|
394
|
+
// Check if the input still contains valid array format
|
|
395
|
+
if (!sanitizedInput.startsWith('[') || !sanitizedInput.endsWith(']')) {
|
|
396
|
+
throw new Error('Invalid input string');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Remove the brackets and split the string into an array
|
|
400
|
+
const arrayString = sanitizedInput.slice(1, -1);
|
|
401
|
+
const elements = arrayString.split(',');
|
|
402
|
+
|
|
403
|
+
// Parse each element using the parseNumber function
|
|
404
|
+
const parsedArray = elements.map((element) => {
|
|
405
|
+
return parseNumber(element.trim());
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// Validate if the result is an array and contains only valid numbers
|
|
409
|
+
if (parsedArray.some(isNaN)) {
|
|
410
|
+
throw new Error('Parsed result contains invalid numbers');
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return parsedArray;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Checks if the given node is a numerical literal or a unary expression.
|
|
418
|
+
* @param {import('typescript').Node} node - The node to check
|
|
419
|
+
* @returns {boolean} - True if the node is a numeric literal
|
|
420
|
+
*/
|
|
421
|
+
export const isNumericLiteral = (node) => {
|
|
422
|
+
// Is the node a numeric literal '10'
|
|
423
|
+
const isNumericLiteral = ts.isNumericLiteral(node);
|
|
424
|
+
|
|
425
|
+
// Or is it a +/- unary expression '-10' or '+10'
|
|
426
|
+
const isUnaryExpression =
|
|
427
|
+
ts.isUnaryExpression(node) && //
|
|
428
|
+
(node.operator === ts.SyntaxKind.MinusToken || node.operator === ts.SyntaxKind.PlusToken) &&
|
|
429
|
+
node.operand.kind === ts.SyntaxKind.NumericLiteral;
|
|
430
|
+
|
|
431
|
+
return isNumericLiteral || isUnaryExpression;
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* If the given node is a numeric literal, returns the parsed number.
|
|
436
|
+
* @param {import('typescript').Node} node - The node to check
|
|
437
|
+
* @param {boolean} [isArgument] - True if the node is an argument in a parameter list
|
|
438
|
+
* @returns {number} - The parsed number
|
|
439
|
+
* @throws {ParsingError} - If the node is not a numeric literal
|
|
440
|
+
*/
|
|
441
|
+
export const parseFloatNode = (node, isArgument = false) => {
|
|
442
|
+
if (!isNumericLiteral(node)) {
|
|
443
|
+
if (ts.isPropertyAssignment(node.parent) || ts.isPropertyDeclaration(node.parent) || isArgument) {
|
|
444
|
+
throw new ParsingError(node, `"${node.parent.getText()}" must be defined with a numeric literal.`);
|
|
445
|
+
}
|
|
446
|
+
return 0;
|
|
447
|
+
}
|
|
448
|
+
return parseNumber(node.getText());
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* If the given node is a boolean literal, returns the parsed boolean.
|
|
453
|
+
* @param {import('typescript').Node} node - The node to check
|
|
454
|
+
* @returns {boolean} - The parsed boolean
|
|
455
|
+
*/
|
|
456
|
+
export const parseBooleanNode = (node) => {
|
|
457
|
+
if (node.kind !== ts.SyntaxKind.TrueKeyword && node.kind !== ts.SyntaxKind.FalseKeyword) {
|
|
458
|
+
if (ts.isPropertyAssignment(node.parent) || ts.isPropertyDeclaration(node.parent)) {
|
|
459
|
+
throw new ParsingError(node, `"${node.parent.getText()}" must be defined with a boolean literal.`);
|
|
460
|
+
}
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
return node.kind === ts.SyntaxKind.TrueKeyword;
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* If the given node is a string literal, returns the parsed string.
|
|
468
|
+
* @param {import('typescript').Node} node - The node to check
|
|
469
|
+
* @returns {string} - The parsed string
|
|
470
|
+
*/
|
|
471
|
+
export const parseStringNode = (node) => {
|
|
472
|
+
if (node.kind !== ts.SyntaxKind.StringLiteral) {
|
|
473
|
+
if (ts.isPropertyAssignment(node.parent) || ts.isPropertyDeclaration(node.parent)) {
|
|
474
|
+
throw new ParsingError(node, `"${node.parent.getText()}" must be defined with a string literal.`);
|
|
475
|
+
}
|
|
476
|
+
return '';
|
|
477
|
+
}
|
|
478
|
+
return node.getText().slice(1, -1);
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
export const parseArrayLiteralNode = (node, isArgument = false) => {
|
|
482
|
+
if (node.kind !== ts.SyntaxKind.ArrayLiteralExpression) {
|
|
483
|
+
if (ts.isPropertyAssignment(node.parent) || ts.isPropertyDeclaration(node.parent) || isArgument) {
|
|
484
|
+
throw new ParsingError(node, `"${node.parent.getText()}" must be defined with an array literal.`);
|
|
485
|
+
}
|
|
486
|
+
return [];
|
|
487
|
+
}
|
|
488
|
+
return JSON.parse(node.getText());
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Parses the first argument as an array literal.
|
|
493
|
+
* @param {ts.Node[]} args - The arguments to parse
|
|
494
|
+
* @returns {number[]} - The parsed array
|
|
495
|
+
*/
|
|
496
|
+
export const parseArrayLiteral = (args) => {
|
|
497
|
+
if (args.length > 0) {
|
|
498
|
+
return parseArrayLiteralNode(args[0], true);
|
|
499
|
+
}
|
|
500
|
+
return [];
|
|
501
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Script, Asset } from 'playcanvas';
|
|
2
|
+
|
|
3
|
+
class Example extends Script {
|
|
4
|
+
/**
|
|
5
|
+
* @attribute
|
|
6
|
+
* @type {Asset}
|
|
7
|
+
* @resource nothing
|
|
8
|
+
*/
|
|
9
|
+
a;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @attribute
|
|
13
|
+
* @type {Asset}
|
|
14
|
+
* @resource 1
|
|
15
|
+
*/
|
|
16
|
+
b;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @attribute
|
|
20
|
+
* @type {Asset}
|
|
21
|
+
* @resource
|
|
22
|
+
*/
|
|
23
|
+
c;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { Example };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Script, Asset } from 'playcanvas';
|
|
2
|
+
|
|
3
|
+
class Example extends Script {
|
|
4
|
+
/**
|
|
5
|
+
* @attribute
|
|
6
|
+
* @type {Asset}
|
|
7
|
+
*/
|
|
8
|
+
a;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @attribute
|
|
12
|
+
* @type {Asset}
|
|
13
|
+
* @resource texture
|
|
14
|
+
*/
|
|
15
|
+
b;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @attribute
|
|
19
|
+
* @type {Asset[]}
|
|
20
|
+
* @resource container
|
|
21
|
+
*/
|
|
22
|
+
c;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { Example };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Script } from 'playcanvas';
|
|
2
|
+
|
|
3
|
+
class Example extends Script {
|
|
4
|
+
/**
|
|
5
|
+
* @attribute
|
|
6
|
+
* @type {boolean}
|
|
7
|
+
*/
|
|
8
|
+
a;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @attribute
|
|
12
|
+
*/
|
|
13
|
+
b = true;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @attribute
|
|
17
|
+
* @type {boolean[]}
|
|
18
|
+
* @size 2
|
|
19
|
+
*/
|
|
20
|
+
c;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { Example };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Script, Color } from 'playcanvas';
|
|
2
|
+
|
|
3
|
+
class Example extends Script {
|
|
4
|
+
/**
|
|
5
|
+
* @attribute
|
|
6
|
+
* @type {Color}
|
|
7
|
+
*/
|
|
8
|
+
a;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @attribute
|
|
12
|
+
*/
|
|
13
|
+
b = new Color(1, 0, 0, 1);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @attribute
|
|
17
|
+
* @type {Color[]}
|
|
18
|
+
* @size 2
|
|
19
|
+
*/
|
|
20
|
+
c;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { Example };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Script, Curve } from 'playcanvas';
|
|
2
|
+
|
|
3
|
+
class Example extends Script {
|
|
4
|
+
/**
|
|
5
|
+
* @attribute
|
|
6
|
+
* @type {Curve}
|
|
7
|
+
* @color bad
|
|
8
|
+
*/
|
|
9
|
+
a;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @attribute
|
|
13
|
+
* @type {Curve}
|
|
14
|
+
* @color rgb
|
|
15
|
+
* @curves ['x', 'y', 'z', 'w']
|
|
16
|
+
*/
|
|
17
|
+
b;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @attribute
|
|
21
|
+
* @type {Curve}
|
|
22
|
+
* @color 1
|
|
23
|
+
* @curves 1
|
|
24
|
+
*/
|
|
25
|
+
c;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { Example };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Script, Curve } from 'playcanvas';
|
|
2
|
+
|
|
3
|
+
class Example extends Script {
|
|
4
|
+
/**
|
|
5
|
+
* @attribute
|
|
6
|
+
* @type {Curve}
|
|
7
|
+
*/
|
|
8
|
+
a;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @attribute
|
|
12
|
+
*/
|
|
13
|
+
b = new Curve([
|
|
14
|
+
0, 0,
|
|
15
|
+
1, 1
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @attribute
|
|
20
|
+
* @type {Curve}
|
|
21
|
+
* @color r
|
|
22
|
+
*/
|
|
23
|
+
c;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @attribute
|
|
27
|
+
* @type {Curve}
|
|
28
|
+
* @color rgb
|
|
29
|
+
* @curves ['x', 'y', 'z']
|
|
30
|
+
*/
|
|
31
|
+
d;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @attribute
|
|
35
|
+
* @type {Curve[]}
|
|
36
|
+
* @size 2
|
|
37
|
+
*/
|
|
38
|
+
e;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @attribute
|
|
42
|
+
* @type {Curve}
|
|
43
|
+
* @color rgba
|
|
44
|
+
* @curves ["x", 'y', `z`]
|
|
45
|
+
*/
|
|
46
|
+
f;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { Example };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Script, Entity } from 'playcanvas';
|
|
2
|
+
|
|
3
|
+
class Example extends Script {
|
|
4
|
+
/**
|
|
5
|
+
* @attribute
|
|
6
|
+
* @type {Entity}
|
|
7
|
+
*/
|
|
8
|
+
a;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @attribute
|
|
12
|
+
*/
|
|
13
|
+
b = new Entity();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @attribute
|
|
17
|
+
* @type {Entity[]}
|
|
18
|
+
* @size 2
|
|
19
|
+
*/
|
|
20
|
+
c;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { Example };
|