@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.
Files changed (78) hide show
  1. package/LICENSE.md +19 -0
  2. package/dist/index.js +445 -0
  3. package/dist/libs.d.ts +50978 -0
  4. package/package.json +50 -10
  5. package/types/index.d.ts +55 -0
  6. package/types/parsers/attribute-parser.d.ts +55 -0
  7. package/types/parsers/parsing-error.d.ts +6 -0
  8. package/types/parsers/script-parser.d.ts +124 -0
  9. package/types/utils/attribute-utils.d.ts +5 -0
  10. package/{src/utils/generic-utils.js → types/utils/generic-utils.d.ts} +1 -10
  11. package/types/utils/tag-utils.d.ts +16 -0
  12. package/types/utils/ts-utils.d.ts +103 -0
  13. package/.github/workflows/ci.yml +0 -49
  14. package/eslint.config.mjs +0 -35
  15. package/renovate.json +0 -25
  16. package/src/index.js +0 -273
  17. package/src/parsers/attribute-parser.js +0 -197
  18. package/src/parsers/parsing-error.js +0 -14
  19. package/src/parsers/script-parser.js +0 -432
  20. package/src/utils/attribute-utils.js +0 -53
  21. package/src/utils/tag-utils.js +0 -73
  22. package/src/utils/ts-utils.js +0 -634
  23. package/test/fixtures/asset.invalid.js +0 -27
  24. package/test/fixtures/asset.invalid.ts +0 -24
  25. package/test/fixtures/asset.valid.js +0 -26
  26. package/test/fixtures/asset.valid.ts +0 -21
  27. package/test/fixtures/checkbox.valid.js +0 -23
  28. package/test/fixtures/color.valid.js +0 -23
  29. package/test/fixtures/conditional.valid.js +0 -22
  30. package/test/fixtures/curve.invalid.js +0 -29
  31. package/test/fixtures/curve.valid.js +0 -49
  32. package/test/fixtures/empty.valid.js +0 -0
  33. package/test/fixtures/entity.valid.js +0 -23
  34. package/test/fixtures/enum.valid.js +0 -66
  35. package/test/fixtures/enum.valid.ts +0 -50
  36. package/test/fixtures/example.dep.js +0 -9
  37. package/test/fixtures/export.import.js +0 -8
  38. package/test/fixtures/export.valid.js +0 -26
  39. package/test/fixtures/inherit.valid.js +0 -45
  40. package/test/fixtures/intellisense.valid.js +0 -40
  41. package/test/fixtures/json.invalid.js +0 -23
  42. package/test/fixtures/json.valid.js +0 -115
  43. package/test/fixtures/json.valid.ts +0 -81
  44. package/test/fixtures/misc.invalid.js +0 -24
  45. package/test/fixtures/misc.valid.js +0 -51
  46. package/test/fixtures/numeric.invalid.js +0 -38
  47. package/test/fixtures/numeric.valid.js +0 -63
  48. package/test/fixtures/playcanvas.d.ts +0 -47963
  49. package/test/fixtures/program.invalid.js +0 -12
  50. package/test/fixtures/program.valid.js +0 -27
  51. package/test/fixtures/text.valid.js +0 -31
  52. package/test/fixtures/vector.invalid.js +0 -14
  53. package/test/fixtures/vector.valid.js +0 -24
  54. package/test/tests/invalid/asset.test.js +0 -47
  55. package/test/tests/invalid/curve.test.js +0 -23
  56. package/test/tests/invalid/json.test.js +0 -27
  57. package/test/tests/invalid/misc.test.js +0 -41
  58. package/test/tests/invalid/numeric.test.js +0 -55
  59. package/test/tests/invalid/program.test.js +0 -17
  60. package/test/tests/invalid/vector.test.js +0 -28
  61. package/test/tests/valid/asset.test.js +0 -59
  62. package/test/tests/valid/checkbox.test.js +0 -57
  63. package/test/tests/valid/color.test.js +0 -49
  64. package/test/tests/valid/conditional.test.js +0 -45
  65. package/test/tests/valid/curve.test.js +0 -76
  66. package/test/tests/valid/empty.test.js +0 -17
  67. package/test/tests/valid/entity.test.js +0 -49
  68. package/test/tests/valid/enum.test.js +0 -79
  69. package/test/tests/valid/export.test.js +0 -67
  70. package/test/tests/valid/inherit.test.js +0 -110
  71. package/test/tests/valid/intellisense.test.js +0 -39
  72. package/test/tests/valid/json.test.js +0 -315
  73. package/test/tests/valid/misc.test.js +0 -54
  74. package/test/tests/valid/numeric.test.js +0 -103
  75. package/test/tests/valid/program.test.js +0 -23
  76. package/test/tests/valid/text.test.js +0 -58
  77. package/test/tests/valid/vector.test.js +0 -53
  78. package/test/utils.js +0 -54
@@ -1,432 +0,0 @@
1
- import * as ts from 'typescript';
2
-
3
- import { AttributeParser } from './attribute-parser.js';
4
- import { ParsingError } from './parsing-error.js';
5
- import { hasTag } from '../utils/attribute-utils.js';
6
- import { zipArrays } from '../utils/generic-utils.js';
7
- import { flatMapAnyNodes, getJSDocTags, getLiteralValue, parseArrayLiteral, parseFloatNode } from '../utils/ts-utils.js';
8
-
9
- /**
10
- * @typedef {object} Attribute
11
- * @property {string} type - The type of the attribute
12
- * @property {boolean} array - Whether the attribute is an array type of not
13
- * @property {string} [description] - The description of the attribute
14
- * @property {string} [placeholder] - Placeholder for Editor's for field UI.
15
- * @property {*} [defaultValue] - The default value of the attribute
16
- * @property {Object<string, number|boolean|string>[]} [enum] - An array of possible values for the attribute
17
- * @property {{ min: number, max: number}} [range] - Numerical constraints for the attribute
18
- * @property {number} [precision] - Numerical constraints for the attribute
19
- * @property {number} [step] - Numerical constraints for the attribute
20
- * @property {string} [resource] - Name of asset type to be used in 'asset' type attribute
21
- */
22
-
23
- /**
24
- * @typedef {object} ValidationError
25
- * @property {string} message - The error message
26
- * @property {string} [file] - The file the error occurred in
27
- * @property {number} [line] - The line the error occurred on
28
- * @property {number} [column] - The column the error occurred on
29
- * @property {number} [length] - The length of the error
30
- */
31
-
32
- /**
33
- * Creates a parser for new expressions.
34
- *
35
- * @param {string} name - The name of the type
36
- * @param {Function} argProcessor - Function to process arguments
37
- * @param {number[]} [defaultArr] - The default array to use if the node is not a valid array
38
- * @returns {Function} - The parser function that extracts the values from the node
39
- */
40
- const createNewExpressionParser = (name, argProcessor, defaultArr = []) => {
41
- return (node) => {
42
- if (node.kind !== ts.SyntaxKind.NewExpression) {
43
- if (ts.isPropertyAssignment(node.parent) || ts.isPropertyDeclaration(node.parent)) {
44
- throw new ParsingError(node, `Property "${node.name.text}" is not a ${name}.`);
45
- }
46
- return defaultArr;
47
- }
48
- const args = argProcessor(node.arguments);
49
-
50
- // combine the two arrays together, filling any missing values with the defaultArr
51
- return zipArrays(args, defaultArr);
52
- };
53
- };
54
-
55
- /**
56
- * Parses single arguments directly.
57
- * @param {ts.Node[]} args - The arguments to parse
58
- * @returns {number[]} - The parsed arguments
59
- */
60
- const parseNumberArguments = args => args.map(arg => parseFloatNode(arg, true));
61
-
62
- // Creating specific parser instances
63
- const createNumberArgumentParser = (name, defaultArr = []) => (
64
- createNewExpressionParser(name, parseNumberArguments, defaultArr));
65
-
66
- const createArrayArgumentParser = (name, defaultArr = []) => (
67
- createNewExpressionParser(name, parseArrayLiteral, defaultArr));
68
-
69
- /**
70
- * Map of pc types that can be initialized with values and their corresponding argument parsers.
71
- * eg `new Vec3(1, 2, 3)`
72
- */
73
- const SUPPORTED_INITIALIZABLE_TYPE_NAMES = new Map([
74
- ['Curve', createArrayArgumentParser('Curve')],
75
- ['Vec2', createNumberArgumentParser('Vec2', [0, 0])],
76
- ['Vec3', createNumberArgumentParser('Vec3', [0, 0, 0])],
77
- ['Vec4', createNumberArgumentParser('Vec4', [0, 0, 0, 0])],
78
- ['Color', createNumberArgumentParser('Color', [1, 1, 1, 1])],
79
- ['number', getLiteralValue],
80
- ['string', getLiteralValue],
81
- ['boolean', getLiteralValue]
82
- ]);
83
-
84
- /**
85
- * The full set of supported PlayCanvas types.
86
- */
87
- const SUPPORTED_PLAYCANVAS_TYPES = new Set([
88
- 'Curve',
89
- 'Asset',
90
- 'Entity',
91
- 'Vec2',
92
- 'Vec3',
93
- 'Vec4',
94
- 'Color'
95
- ]);
96
-
97
- /**
98
- * Checks if a node is a supported PlayCanvas type.
99
- * @param {ts.Node} node - The node to check
100
- * @returns {boolean} - True if the node is a supported PlayCanvas type
101
- */
102
- const isSupportedPlayCanvasNode = (node) => {
103
- return ts.isClassDeclaration(node) && SUPPORTED_PLAYCANVAS_TYPES.has(node.name.text);
104
- };
105
-
106
- // valid values for asset.type
107
- const EDITOR_ASSET_TYPES = [
108
- 'animation',
109
- 'animstategraph',
110
- 'audio',
111
- 'bundle',
112
- 'binary',
113
- 'container',
114
- 'cubemap',
115
- 'css',
116
- 'font',
117
- 'folder',
118
- 'json',
119
- 'html',
120
- 'gsplat',
121
- 'material',
122
- 'model',
123
- 'render',
124
- 'scene',
125
- 'script',
126
- 'shader',
127
- 'sprite',
128
- 'template',
129
- 'text',
130
- 'texture',
131
- 'textureAtlas',
132
- 'wasm'
133
- ];
134
-
135
- const resourceTypesAsUnion = EDITOR_ASSET_TYPES.map(type => `"${type}"`).join('|');
136
-
137
- /**
138
- * The set of supported block tags and validators
139
- */
140
- const SUPPORTED_BLOCK_TAGS = new Map([
141
- ['resource', resourceTypesAsUnion],
142
- ['color', '"r"|"rg"|"rgb"|"rgba"'],
143
- ['curves', '["x"] | ["x", "y"] | ["x", "y", "z"] | ["x", "z"] | ["y"] | ["y", "z"] | ["z"]'],
144
- ['range', 'number[]'],
145
- ['placeholder', 'string'],
146
- ['precision', 'number'],
147
- ['size', 'number'],
148
- ['step', 'number'],
149
- ['title', 'string'],
150
- ['default', 'any'],
151
- ['enabledif', 'any'],
152
- ['visibleif', 'any']
153
- ]);
154
-
155
- /**
156
- * A mapping function that transforms the Script Parsers internal representation of an attribute to a required output.
157
- * @param {*} attribute - The attribute to map
158
- * @returns {*} - The mapped attribute
159
- */
160
- const mapAttributesToOutput = (attribute) => {
161
-
162
- attribute.type = attribute.typeName.toLowerCase();
163
-
164
- // Flatten the range into min and max
165
- if (attribute.range) {
166
- if (attribute.range.length > 0) {
167
- attribute.min = attribute.range[0];
168
- if (attribute.range.length > 1) {
169
- attribute.max = attribute.range[1];
170
- }
171
- }
172
- delete attribute.range;
173
- }
174
-
175
- // We have a complex type, map to json schema
176
- if (typeof attribute.attributes == 'object') {
177
- attribute.type = 'json';
178
- attribute.schema = Object.keys(attribute.attributes).map((key) => {
179
- return attribute.attributes[key];
180
- });
181
- delete attribute.attributes;
182
- }
183
-
184
- // 'color' type needs to be mapped to 'rgba'
185
- if (attribute.type === 'color') {
186
- attribute.type = 'rgba';
187
- }
188
-
189
- // 'resource' type needs to be mapped to 'assetType'
190
- if (attribute.resource) {
191
- attribute.assetType = attribute.resource;
192
- delete attribute.resource;
193
- }
194
-
195
- // remove enum if it's empty
196
- if (attribute.enum.length === 0) delete attribute.enum;
197
-
198
- // If the attribute has no default value then set it
199
- if (attribute.value === undefined) {
200
- if (attribute.type === 'string') attribute.value = '';
201
- if (attribute.type === 'number') attribute.value = 0;
202
- if (attribute.type === 'boolean') attribute.value = false;
203
- }
204
-
205
- // set the default value
206
- if (attribute.value !== undefined) attribute.default = attribute.default ?? attribute.value;
207
-
208
- // Curve Attributes specifically should not expose a default value if it's an empty array
209
- if (attribute.type === 'curve' && Array.isArray(attribute.value) && attribute.value.length === 0) {
210
- delete attribute.default;
211
- }
212
-
213
- // enabledif is in a {}, so strip it out
214
- if (attribute.enabledif?.startsWith('{') && attribute.enabledif?.endsWith('}')) {
215
- attribute.enabledif = attribute.enabledif.slice(1, -1);
216
- }
217
-
218
- // enabledif is in a {}, so strip it out
219
- if (attribute.visibleif?.startsWith('{') && attribute.visibleif?.endsWith('}')) {
220
- attribute.visibleif = attribute.visibleif.slice(1, -1);
221
- }
222
-
223
- // remove typeName from the output
224
- delete attribute.typeName;
225
- delete attribute.value;
226
-
227
- return attribute;
228
- };
229
-
230
- /**
231
- * A class to parse ESM Scripts and extract attribute metadata from JSDoc comments.
232
- * This class uses the TypeScript compiler API to parse the script and extract the metadata.
233
- */
234
- export class ScriptParser {
235
- /**
236
- * @param {import('@typescript/vfs').VirtualTypeScriptEnvironment} env - The TypeScript virtual environment
237
- * @param {number} [maxDepth] - The maximum recursion depth
238
- */
239
- constructor(env, maxDepth = 2) {
240
-
241
- if (!env) {
242
- throw new Error('A TypeScript Environment must be provided to the ScriptParser.');
243
- }
244
-
245
- this.env = env;
246
- this.program = env.languageService.getProgram();
247
- this.typeChecker = this.program.getTypeChecker();
248
- this.maxDepth = maxDepth;
249
-
250
- // Initialize the supported types
251
- const playcanvasTypesFile = this.program.getSourceFile('./playcanvas.d.ts');
252
-
253
- if (!playcanvasTypesFile) {
254
- throw new Error('The PlayCanvas types file could not be found.');
255
- }
256
-
257
- // Return an array of all nodes that are supported PlayCanvas types eg Vec3, Color, etc.
258
- const allSupportedTypes = flatMapAnyNodes(playcanvasTypesFile, isSupportedPlayCanvasNode).map(node => this.typeChecker.getTypeAtLocation(node));
259
-
260
- // Add primitive types
261
- allSupportedTypes.push(this.typeChecker.getStringType());
262
- allSupportedTypes.push(this.typeChecker.getNumberType());
263
- allSupportedTypes.push(this.typeChecker.getBooleanType());
264
-
265
- this.typeSerializerMap = new Map();
266
- this.allSupportedTypesSet = new Set(allSupportedTypes);
267
-
268
- allSupportedTypes.forEach((type) => {
269
- const typeName = this.typeChecker.typeToString(type);
270
- const serializer = SUPPORTED_INITIALIZABLE_TYPE_NAMES.get(typeName);
271
- if (serializer) {
272
- this.typeSerializerMap.set(typeName, serializer);
273
- }
274
- });
275
-
276
- this.attributeParser = new AttributeParser(SUPPORTED_BLOCK_TAGS, env, this.typeSerializerMap);
277
-
278
- }
279
-
280
- /**
281
- * Checks a program for compile errors and returns a list of formatted error messages.
282
- * @param {ValidationError[]} errors - The array to store error messages
283
- * @returns {boolean} - True if the program has no errors, false otherwise
284
- */
285
- validateProgram(errors = []) {
286
-
287
- // Report any compiler errors
288
- const compilerDiagnostics = this.program.getSyntacticDiagnostics();
289
-
290
- if (compilerDiagnostics.length > 0) {
291
- for (const diagnostic of compilerDiagnostics) {
292
- const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
293
- if (diagnostic.file) {
294
- const location = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
295
- errors.push({
296
- message,
297
- file: diagnostic.file.fileName,
298
- line: location.line + 1,
299
- column: location.character + 1,
300
- length: diagnostic.length
301
- });
302
- } else {
303
- errors.push({
304
- message
305
- });
306
- }
307
- }
308
- }
309
-
310
- return errors.length === 0;
311
- }
312
-
313
- /**
314
- * @typedef {Object} ParserOptions
315
- * @property {object} [attributes] - The attributes object to add to
316
- * @property {ParsingError[]} [errors] - The array to store parsing errors
317
- * @property {boolean} [requiresAttributeTag] - Whether the attribute requires an @attribute tag
318
- * @property {number} [depth] - The current recursion depth
319
- */
320
-
321
- /**
322
- * Extracts Script Attributes from JSDoc style tag from a AST node
323
- *
324
- * @param {import('typescript').Node} node - The TypeScript node to analyze
325
- * @param {ParserOptions} [opts] - The input options
326
- * @returns {Attribute[]} - The extracted attributes;
327
- */
328
- extractAttributes(node, opts = {}) {
329
-
330
- // Default params
331
- const attributes = opts.attributes || {};
332
- const errors = opts.errors ?? [];
333
- const requiresAttributeTag = opts.requiresAttributeTag ?? true;
334
- const depth = opts.depth ?? 0;
335
-
336
- // return early if we've reached the maximum depth
337
- if (depth > 10) {
338
- return attributes;
339
- }
340
-
341
- // Find "/** */" style comments associated with this node.
342
- const members = getJSDocTags(node, this.typeChecker);
343
-
344
- // Parse the comments for attribute metadata
345
- for (const { member, memberName } of members) {
346
-
347
- // Parse the comment for attribute metadata
348
- const attributeMetadata = this.attributeParser.parseAttributeComment(
349
- member,
350
- errors,
351
- requiresAttributeTag
352
- );
353
-
354
- // If we found metadata, extract the type and add it to the found attributes
355
- if (attributeMetadata) {
356
-
357
- const { type, typeName } = attributeMetadata;
358
- const initializer = member?.initializer;
359
-
360
- if (typeName === 'any') {
361
-
362
- const error = new ParsingError(member, `The attribute "${memberName}" is not initialized and does not have a @type tag.`);
363
- errors.push(error);
364
- continue;
365
-
366
- } else if (!this.allSupportedTypesSet.has(type) && attributeMetadata.enum.length === 0) {
367
- // If this is a complex type, but not an enum, we may need to recurse through it's nested nested properties
368
-
369
- // extract attributes for the Types properties
370
- const symbol = type.aliasSymbol || type.symbol;
371
- const typeNode = symbol?.valueDeclaration || symbol.declarations?.[0];
372
-
373
- if (!typeNode) {
374
- // The attribute does not have a valid type
375
- continue;
376
- }
377
-
378
- // Type constraints: Must be either an interface, typedef or initialized object literal, or a jsdoc type literal
379
- const typeIsJSDocTypeDef = ts.isJSDocTypedefTag(typeNode);
380
- const typeIsInterface = hasTag('interface', typeNode) || ts.isInterfaceDeclaration(typeNode);
381
- const isInitialized = !!initializer && ts.isObjectLiteralExpression(initializer);
382
- const isJSDocTypeLiteral = type.symbol.name === '__type';
383
-
384
- // Check if the type is a valid interface
385
- if (!isInitialized && !typeIsInterface && !typeIsJSDocTypeDef && !isJSDocTypeLiteral) {
386
- const error = new ParsingError(member, `Attribute "${memberName}" is an invalid type.`);
387
- errors.push(error);
388
- continue;
389
- }
390
-
391
- // Extract the attributes from the nested type
392
- const members = typeNode.members ?? // interface members
393
- typeNode.properties ?? // an object literals properties
394
- Array.from(type.symbol.members).map(arr => arr[1].declarations[0]) ?? // a typedefs property declarations
395
- [];
396
-
397
- // If the type is an interface or an initialized object, we need to parse the attribute comments, if not, just the intitialized object
398
- let commentAttributes;
399
- if (typeIsInterface || isInitialized) {
400
- commentAttributes = this.extractAttributes(typeNode, { errors, requiresAttributeTag: false, depth: depth + 1 });
401
- }
402
-
403
- // Iterate through the nested attributes and extract their metadata
404
- const nestedAttributes = members.reduce((attributes, prop) => {
405
-
406
- const attribute = this.attributeParser.getNodeAsAttribute(prop, errors);
407
-
408
- // If the attribute is a supported type, add it to the list of found attributes
409
- if (attribute && this.allSupportedTypesSet.has(attribute.type)) {
410
- attributes[attribute.name] = mapAttributesToOutput(attribute);
411
- }
412
-
413
- return attributes;
414
- }, {});
415
-
416
-
417
- // Merge the property and comment attributes
418
- const mergedAttributes = { ...nestedAttributes, ...commentAttributes };
419
-
420
- // Add the nested attributes to the attribute metadata
421
- attributeMetadata.attributes = mergedAttributes;
422
- }
423
-
424
- // Add the attribute to the list of found attributes
425
- attributes[memberName] = mapAttributesToOutput(attributeMetadata);
426
-
427
- }
428
- }
429
-
430
- return attributes;
431
- }
432
- }
@@ -1,53 +0,0 @@
1
- import { getCombinedModifierFlags, ModifierFlags } from 'typescript';
2
-
3
- /**
4
- * @file Utility functions for parsing Script attributes.
5
- */
6
-
7
-
8
- /**
9
- * Returns a jsdoc tag from a JSDoc comment.
10
- * @param {string} tag - The tag to search for
11
- * @param {import('typescript').Node} doc - The JSDoc comment node
12
- * @returns {import('typescript').Node} | null} - The tag node
13
- */
14
- export const getTagFromJsdoc = (tag, doc) => {
15
- return doc?.tags?.find(doc => doc.tagName.text === tag);
16
- };
17
-
18
- /**
19
- * Checks if a node has a specific JSDoc tag.
20
- * @param {string} tag - The tag to search for
21
- * @param {import("typescript").Node} node - The node to analyze
22
- * @returns {import('typescript').Node} - True if the tag is found
23
- */
24
- export const getTag = (tag, node) => {
25
- return node?.jsDoc?.find(doc => getTagFromJsdoc(tag, doc));
26
- };
27
-
28
- /**
29
- * Checks if a node has a specific JSDoc tag.
30
- * @param {string} tag - The tag to search for
31
- * @param {import("typescript").Node} node - The node to analyze
32
- * @returns {boolean} - True if the tag is found
33
- */
34
- export const hasTag = (tag, node) => {
35
- return !!getTag(tag, node);
36
- };
37
-
38
-
39
- /**
40
- * Checks if the JSDoc node node has an '@interface' tag
41
- * @param {import('typescript').Node} node - The node to analyze
42
- * @returns {boolean} - True if the node is an interface
43
- */
44
- export const isInterface = (node) => {
45
- return hasTag('interface', node);
46
- };
47
-
48
- /**
49
- * Checks if a node is a scriptName property
50
- * @param {import('typescript').Node} member - The node to analyze
51
- * @returns {boolean} - True if the node is a scriptName property
52
- */
53
- export const isStaticMember = member => (getCombinedModifierFlags(member) & ModifierFlags.Static) !== 0;
@@ -1,73 +0,0 @@
1
- import { DiagnosticCategory } from 'typescript';
2
-
3
- import { parseNumber, parseStringToNumericalArray } from './ts-utils.js';
4
-
5
- /**
6
- * Parses a tag value into a boolean, number, array or string
7
- * @param {string} input - The tag value to parse
8
- * @returns {boolean|number|string|Array<any>} - The parsed value
9
- */
10
- export function parseTag(input = '') {
11
-
12
- // Remove any leading or trailing whitespace, replace single quotes with double quotes
13
- input = input.replace(/['`]/g, '"').trim();
14
-
15
- // Check for boolean
16
- if (input === 'true') return true;
17
- if (input === 'false') return false;
18
-
19
- // Check for number
20
- const num = parseNumber(input);
21
- if (!isNaN(num)) return num;
22
-
23
- // Check for numerical array
24
- try {
25
- const arr = parseStringToNumericalArray(input);
26
- if (Array.isArray(arr)) return arr;
27
- } catch { }
28
-
29
- try {
30
- const arr = JSON.parse(input);
31
- if (Array.isArray(arr)) return arr;
32
- } catch { }
33
-
34
- // If none of the above, return the string
35
- return input;
36
- }
37
-
38
- /**
39
- * Validates that a tag value matches the expected type
40
- *
41
- * @param {String} value - The value to be validated.
42
- * @param {String} typeAnnotation - The TypeScript type annotation as a string.
43
- * @param {import('@typescript/vfs').VirtualTypeScriptEnvironment} env - The environment containing the language service and file creation utilities.
44
- * @throws Will throw an error if the value does not conform to the typeAnnotation.
45
- * @returns {true} if validation passes without type errors.
46
- */
47
- export function validateTag(value, typeAnnotation, env) {
48
- const virtualFileName = '/___virtual__.ts';
49
- const sourceText = `let a: ${typeAnnotation} = ${value};`;
50
-
51
- // Create or overwrite the virtual file with the new source text
52
- env.createFile(virtualFileName, sourceText);
53
-
54
- // Retrieve the language service from the environment
55
- const languageService = env.languageService;
56
-
57
- // Fetch semantic diagnostics only for the virtual file
58
- const errors = languageService.getSemanticDiagnostics(virtualFileName);
59
-
60
- // Filter for type assignment errors (Error Code 2322: Type 'X' is not assignable to type 'Y')
61
- const typeErrors = errors.filter(
62
- error => error.code === 2322 && error.category === DiagnosticCategory.Error
63
- );
64
-
65
- // If any type error is found, throw an error with the diagnostic message
66
- if (typeErrors.length > 0) {
67
- const errorMessage = typeErrors[0].messageText.toString();
68
- throw new Error(`Type Validation Error: ${errorMessage}`);
69
- }
70
-
71
- // If no type errors are found, return true indicating successful validation
72
- return true;
73
- }