@playcanvas/attribute-parser 1.1.0 → 1.3.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/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
7
7
  },
8
- "version": "1.1.0",
8
+ "version": "1.3.0",
9
9
  "dependencies": {
10
10
  "@microsoft/tsdoc": "^0.15.0",
11
11
  "@playcanvas/eslint-config": "^1.7.4",
package/src/index.js CHANGED
@@ -8,9 +8,10 @@ import { createDefaultMapFromCDN, flatMapAnyNodes, getExportedNodes, getType, in
8
8
  const toLowerCamelCase = str => str[0].toLowerCase() + str.substring(1);
9
9
 
10
10
  const COMPILER_OPTIONS = {
11
+ noLib: true,
11
12
  strict: false,
12
13
  skipLibCheck: true, // Skip type checking of declaration files
13
- target: ts.ScriptTarget.ES2022, // If this version changes, the types must be updated in the /rollup.config.mjs
14
+ target: ts.ScriptTarget.ES2023, // If this version changes, the types must be updated in the /rollup.config.mjs
14
15
  module: ts.ModuleKind.CommonJS,
15
16
  checkJs: true, // Enable JSDoc parsing
16
17
  allowJs: true,
@@ -36,19 +37,30 @@ export class JSDocParser {
36
37
  /**
37
38
  * Initializes the JSDocParser with the standard library files
38
39
  *
39
- * @param {string} libPath - The path to standard library files
40
+ * @param {string} libPath - A path to a directory of library types, or a path to the '.d.ts' file itself
40
41
  * @returns {Promise<JSDocParser>} - The initialized JSDocParser
41
42
  */
42
- async init(libPath = '') {
43
+ async init(libPath) {
43
44
  if (this._env) {
44
45
  return this;
45
46
  }
46
47
 
47
48
  let fsMap;
48
- if (libPath) {
49
- fsMap = await createDefaultMapFromCDN({ target: ts.ScriptTarget.ES2022 }, libPath, ts);
50
- } else {
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.
51
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);
52
64
  }
53
65
 
54
66
  // Set up the virtual file system and environment
@@ -4,7 +4,7 @@ import * as ts from 'typescript';
4
4
  import { ParsingError } from './parsing-error.js';
5
5
  import { hasTag } from '../utils/attribute-utils.js';
6
6
  import { parseTag, validateTag } from '../utils/tag-utils.js';
7
- import { extractTextFromDocNode, getLeadingBlockCommentRanges, getType } from '../utils/ts-utils.js';
7
+ import { extractTextFromDocNode, getLeadingBlockCommentRanges, getLiteralValue, getType } from '../utils/ts-utils.js';
8
8
 
9
9
  /**
10
10
  * A class to parse JSDoc comments and extract attribute metadata.
@@ -167,7 +167,7 @@ export class AttributeParser {
167
167
  let value = null;
168
168
 
169
169
  // we don't need to serialize the value for arrays
170
- const serializer = !array && this.typeSerializerMap.get(type);
170
+ const serializer = !array && this.typeSerializerMap.get(typeName);
171
171
  if (serializer) {
172
172
  try {
173
173
  value = serializer(node.initializer ?? node, this.typeChecker);
@@ -192,8 +192,8 @@ export class AttributeParser {
192
192
  let members = [];
193
193
 
194
194
  // Check if there's a type annotation directly on the variable declaration
195
- if (ts.isVariableDeclaration(node) && node.type) {
196
- typeNode = node.type;
195
+ if (node.type) {
196
+ typeNode = node;
197
197
  } else {
198
198
  // Check for JSDoc annotations
199
199
  const jsDocs = ts.getJSDocTags(node);
@@ -203,10 +203,13 @@ export class AttributeParser {
203
203
  }
204
204
  }
205
205
 
206
- if (typeNode && ts.isTypeReferenceNode(typeNode.type)) {
206
+ // Also consider the elementType
207
+ const type = typeNode && (typeNode.type.elementType ?? typeNode.type);
208
+
209
+ if (typeNode && ts.isTypeReferenceNode(type)) {
207
210
 
208
211
  // resolve the symbol of the type
209
- let symbol = this.typeChecker.getSymbolAtLocation(typeNode.type.typeName);
212
+ let symbol = this.typeChecker.getSymbolAtLocation(type.typeName);
210
213
 
211
214
  // Resolve aliases, which are common with imports
212
215
  if (symbol && symbol.flags & ts.SymbolFlags.Alias) {
@@ -222,7 +225,7 @@ export class AttributeParser {
222
225
 
223
226
  // Check if the declaration is a TypeScript enum
224
227
  if (ts.isEnumDeclaration(declaration)) {
225
- members = declaration.members.map(member => member.name.getText());
228
+ members = declaration.members.map(member => ({ [member.name.getText()]: member.initializer.text }));
226
229
  }
227
230
 
228
231
  // Additionally check for JSDoc enum tag
@@ -256,19 +259,7 @@ export class AttributeParser {
256
259
 
257
260
  if (ts.isPropertyAssignment(property)) {
258
261
  const name = property.name && ts.isIdentifier(property.name) && property.name.text;
259
- let value;
260
-
261
- const node = property.initializer;
262
-
263
- // Enums can only contain primitives (string|number|boolean)
264
- if (ts.isNumericLiteral(node)) {
265
- value = parseFloat(node.getText());
266
- } else if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) {
267
- value = node.kind === ts.SyntaxKind.TrueKeyword;
268
- } else {
269
- value = node.getText();
270
- }
271
-
262
+ const value = getLiteralValue(property.initializer, this.typeChecker);
272
263
  members.push({ [name]: value });
273
264
  }
274
265
  });
@@ -147,7 +147,8 @@ const SUPPORTED_BLOCK_TAGS = new Map([
147
147
  ['precision', 'number'],
148
148
  ['size', 'number'],
149
149
  ['step', 'number'],
150
- ['title', 'string']
150
+ ['title', 'string'],
151
+ ['default', 'any']
151
152
  ]);
152
153
 
153
154
  /**
@@ -195,7 +196,7 @@ const mapAttributesToOutput = (attribute) => {
195
196
  }
196
197
 
197
198
  // set the default value
198
- if (attribute.value !== undefined) attribute.default = attribute.value;
199
+ if (attribute.value !== undefined) attribute.default = attribute.default ?? attribute.value;
199
200
 
200
201
  // Curve Attributes specifically should not expose a default value if it's an empty array
201
202
  if (attribute.type === 'curve' && Array.isArray(attribute.value) && attribute.value.length === 0) {
@@ -251,7 +252,7 @@ export class ScriptParser {
251
252
  const typeName = this.typeChecker.typeToString(type);
252
253
  const serializer = SUPPORTED_INITIALIZABLE_TYPE_NAMES.get(typeName);
253
254
  if (serializer) {
254
- this.typeSerializerMap.set(type, serializer);
255
+ this.typeSerializerMap.set(typeName, serializer);
255
256
  }
256
257
  });
257
258
 
@@ -208,7 +208,7 @@ function getSuperClasses(node, typeChecker) {
208
208
  export function getJSDocCommentRanges(node, text, typeChecker) {
209
209
  const commentRanges = [];
210
210
 
211
- if (ts.isClassDeclaration(node)) {
211
+ if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
212
212
  // get an array of the class an all parent classed
213
213
  const heritageChain = getSuperClasses(node, typeChecker);
214
214
 
@@ -216,7 +216,7 @@ export function getJSDocCommentRanges(node, text, typeChecker) {
216
216
  heritageChain.forEach((classNode) => {
217
217
  // for each class iterate over it's class members
218
218
  classNode.members.forEach((member) => {
219
- if (ts.isPropertyDeclaration(member) || ts.isSetAccessor(member)) {
219
+ if (ts.isPropertyDeclaration(member) || ts.isSetAccessor(member) || ts.isPropertySignature(member)) {
220
220
  const memberName = member.name && ts.isIdentifier(member.name) ? member.name.text : 'unnamed';
221
221
  const ranges = getLeadingBlockCommentRanges(member, text);
222
222
  if (ranges.length > 0) {
@@ -310,6 +310,31 @@ export function isEnum(node) {
310
310
  return false;
311
311
  }
312
312
 
313
+ /**
314
+ * Determines the primitive type for enums, or falls back to the actual type name.
315
+ *
316
+ * @param {ts.Type} type - The type to inspect.
317
+ * @param {ts.TypeChecker} typeChecker - The TypeScript type checker.
318
+ * @returns {'string' | 'boolean' | 'number' | null} - The primitive type of the enum or the type's name.
319
+ */
320
+ export function getPrimitiveEnumType(type, typeChecker) {
321
+ // Check if the type is an enum type
322
+ if (!type.symbol?.declarations?.some(decl => ts.isEnumDeclaration(decl))) return null;
323
+
324
+ // Get the type of enum members
325
+ const enumMembers = type.symbol.declarations[0].members;
326
+ const firstMemberValue = typeChecker.getConstantValue(enumMembers[0]);
327
+
328
+ const validEnumType = [
329
+ 'number',
330
+ 'string',
331
+ 'boolean'
332
+ ];
333
+
334
+ const typeOf = typeof firstMemberValue;
335
+ return validEnumType.includes(typeOf) ? typeOf : null;
336
+ }
337
+
313
338
  /**
314
339
  * Gets the inferred type of a TypeScript node.
315
340
  *
@@ -322,7 +347,7 @@ export function getType(node, typeChecker) {
322
347
  const type = typeChecker.getTypeAtLocation(node);
323
348
  const array = typeChecker.isArrayType(type);
324
349
  const actualType = array ? typeChecker.getElementTypeOfArrayType(type) : type;
325
- const name = typeChecker.typeToString(actualType);
350
+ const name = getPrimitiveEnumType(actualType, typeChecker) ?? typeChecker.typeToString(actualType);
326
351
 
327
352
  return { type: actualType, name, array };
328
353
  }
@@ -489,10 +514,15 @@ const resolvePropertyAccess = (node, typeChecker) => {
489
514
  if (ts.isPropertyAssignment(declaration) && declaration.initializer) {
490
515
  return getLiteralValue(declaration.initializer, typeChecker);
491
516
  }
517
+
492
518
  if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
493
519
  return getLiteralValue(declaration.initializer, typeChecker);
494
520
  }
495
- // Handle other kinds of declarations if needed
521
+
522
+ if (ts.isEnumMember(declaration)) {
523
+ return declaration.initializer ? getLiteralValue(declaration.initializer, typeChecker) : declaration.name.getText();
524
+ }
525
+
496
526
  }
497
527
  }
498
528
 
@@ -0,0 +1,24 @@
1
+ // eslint-disable-next-line
2
+ import { Script, Asset } from 'playcanvas';
3
+
4
+ class Example extends Script {
5
+ /**
6
+ * @attribute
7
+ * @resource nothing
8
+ */
9
+ a: Asset;
10
+
11
+ /**
12
+ * @attribute
13
+ * @resource 1
14
+ */
15
+ b : Asset;
16
+
17
+ /**
18
+ * @attribute
19
+ * @resource
20
+ */
21
+ c : Asset;
22
+ }
23
+
24
+ export { Example };
@@ -0,0 +1,21 @@
1
+ // eslint-disable-next-line
2
+ import { Script, Asset } from 'playcanvas';
3
+
4
+ class Example extends Script {
5
+ /** @attribute */
6
+ a : Asset;
7
+
8
+ /**
9
+ * @attribute
10
+ * @resource texture
11
+ */
12
+ b : Asset;
13
+
14
+ /**
15
+ * @attribute
16
+ * @resource container
17
+ */
18
+ c : Asset[];
19
+ }
20
+
21
+ export { Example };
@@ -1,12 +1,12 @@
1
- import { Script, Vec3 } from 'playcanvas';
1
+ import { Script } from 'playcanvas';
2
2
 
3
3
  /**
4
4
  * @enum {number}
5
5
  */
6
6
  const NumberEnum = {
7
- A: 0,
8
- B: 1,
9
- C: 2
7
+ A: 13,
8
+ B: 14,
9
+ C: 23
10
10
  };
11
11
 
12
12
  /**
@@ -19,16 +19,6 @@ const StringEnum = {
19
19
  C: 'c'
20
20
  };
21
21
 
22
- /**
23
- * @enum {Vec3}
24
- */
25
- // eslint-disable-next-line
26
- const Vec3Enum = {
27
- A: new Vec3(1, 2, 3),
28
- B: new Vec3(4, 5, 6),
29
- C: new Vec3(7, 8, 9)
30
- };
31
-
32
22
  /**
33
23
  * @enum {NumberEnum}
34
24
  */
@@ -65,17 +55,11 @@ class Example extends Script {
65
55
  */
66
56
  h;
67
57
 
68
- /**
69
- * @attribute
70
- * @type {Vec3Enum}
71
- */
72
- i;
73
-
74
58
  /**
75
59
  * @attribute
76
60
  * @type {NumberNumberEnum}
77
61
  */
78
- j = 2;
62
+ i = 2;
79
63
  }
80
64
 
81
65
  export { Example };
@@ -0,0 +1,49 @@
1
+ import { Script, Vec3 } from 'playcanvas';
2
+
3
+ enum NumberEnum {
4
+ A = 13,
5
+ B = 14,
6
+ C = 23
7
+ };
8
+
9
+ enum StringEnum {
10
+ A = 'a',
11
+ B = 'b',
12
+ C = 'c'
13
+ };
14
+
15
+ enum NumberNumberEnum {
16
+ A = NumberEnum.A,
17
+ B = NumberEnum.B,
18
+ C = NumberEnum.C
19
+ };
20
+
21
+ class Example extends Script {
22
+ /**
23
+ * @attribute
24
+ */
25
+ e : NumberEnum = NumberEnum.A;
26
+
27
+ /**
28
+ * @attribute
29
+ */
30
+ f : NumberEnum = 1;
31
+
32
+ /**
33
+ * @attribute
34
+ * @size 2
35
+ */
36
+ g : NumberEnum[];
37
+
38
+ /**
39
+ * @attribute
40
+ */
41
+ h : StringEnum;
42
+
43
+ /**
44
+ * @attribute
45
+ */
46
+ i : NumberNumberEnum = 2;
47
+ }
48
+
49
+ export { Example };
@@ -0,0 +1,72 @@
1
+ import { Script, Vec3 } from 'playcanvas';
2
+
3
+ interface Folder {
4
+ /**
5
+ * @attribute
6
+ */
7
+ a: boolean;
8
+
9
+ /**
10
+ * @attribute
11
+ * @default 10
12
+ */
13
+ b: number;
14
+
15
+ /**
16
+ * @attribute
17
+ * @default hello
18
+ */
19
+ c : string;
20
+
21
+ /**
22
+ * @attribute
23
+ */
24
+ d : Vec3[];
25
+ }
26
+
27
+ interface NestedFolder {
28
+ /**
29
+ * @attribute
30
+ */
31
+ x : Folder;
32
+ }
33
+
34
+ /**
35
+ * @interface
36
+ */
37
+ class Example extends Script {
38
+ /**
39
+ * @attribute
40
+ */
41
+ f : Folder;
42
+
43
+ /**
44
+ * @attribute
45
+ * @size 2
46
+ */
47
+ g : Folder[];
48
+
49
+ /**
50
+ * @attribute
51
+ */
52
+ h : NestedFolder;
53
+
54
+ /**
55
+ * @attribute
56
+ */
57
+ i = { a: true, b: 10, c: 'hello', d: [new Vec3(1, 2, 3)] };
58
+
59
+ /**
60
+ * @attribute
61
+ */
62
+ l : { x: number, y: number };
63
+
64
+ /**
65
+ * @attribute
66
+ */
67
+ m : Example;
68
+
69
+ n = 10;
70
+ }
71
+
72
+ export { Example };
@@ -3,48 +3,57 @@ import { describe, it, before } from 'mocha';
3
3
 
4
4
  import { parseAttributes } from '../../utils.js';
5
5
 
6
- describe('VALID: Asset attribute', function () {
7
- let data;
8
- before(async function () {
9
- data = await parseAttributes('./asset.valid.js');
10
- });
11
-
12
- it('only results should exist', function () {
13
- expect(data).to.exist;
14
- expect(data[0]).to.not.be.empty;
15
- expect(data[1]).to.be.empty;
16
- });
17
-
18
- it('Example: should exist without errors', function () {
19
- expect(data[0]?.example).to.exist;
20
- expect(data[0].example.attributes).to.not.be.empty;
21
- expect(data[0].example.errors).to.be.empty;
22
- });
23
-
24
- it('a: should be a asset attribute', function () {
25
- expect(data[0].example.attributes.a).to.exist;
26
- expect(data[0].example.attributes.a.name).to.equal('a');
27
- expect(data[0].example.attributes.a.type).to.equal('asset');
28
- expect(data[0].example.attributes.a.array).to.equal(false);
29
- expect(data[0].example.attributes.a.default).to.equal(null);
30
- });
31
-
32
- it('b: should be a asset attribute with a texture resource', function () {
33
- expect(data[0].example.attributes.b).to.exist;
34
- expect(data[0].example.attributes.b.name).to.equal('b');
35
- expect(data[0].example.attributes.b.type).to.equal('asset');
36
- expect(data[0].example.attributes.b.array).to.equal(false);
37
- expect(data[0].example.attributes.b.default).to.equal(null);
38
- expect(data[0].example.attributes.b.resource).to.equal('texture');
39
- });
6
+ function runTests(fileName) {
7
+
8
+ const isTS = fileName.endsWith('.ts');
9
+ const script = isTS ? 'TS' : 'JS';
10
+
11
+ describe(`${script}: VALID: Asset attribute`, function () {
12
+ let data;
13
+ before(async function () {
14
+ data = await parseAttributes(fileName);
15
+ });
16
+
17
+ it('only results should exist', function () {
18
+ expect(data).to.exist;
19
+ expect(data[0]).to.not.be.empty;
20
+ expect(data[1]).to.be.empty;
21
+ });
22
+
23
+ it('Example: should exist without errors', function () {
24
+ expect(data[0]?.example).to.exist;
25
+ expect(data[0].example.attributes).to.not.be.empty;
26
+ expect(data[0].example.errors).to.be.empty;
27
+ });
28
+
29
+ it('a: should be a asset attribute', function () {
30
+ expect(data[0].example.attributes.a).to.exist;
31
+ expect(data[0].example.attributes.a.name).to.equal('a');
32
+ expect(data[0].example.attributes.a.type).to.equal('asset');
33
+ expect(data[0].example.attributes.a.array).to.equal(false);
34
+ expect(data[0].example.attributes.a.default).to.equal(null);
35
+ });
36
+
37
+ it('b: should be a asset attribute with a texture resource', function () {
38
+ expect(data[0].example.attributes.b).to.exist;
39
+ expect(data[0].example.attributes.b.name).to.equal('b');
40
+ expect(data[0].example.attributes.b.type).to.equal('asset');
41
+ expect(data[0].example.attributes.b.array).to.equal(false);
42
+ expect(data[0].example.attributes.b.default).to.equal(null);
43
+ expect(data[0].example.attributes.b.resource).to.equal('texture');
44
+ });
45
+
46
+ it('c: should be a asset attribute array with a container resource', function () {
47
+ expect(data[0].example.attributes.c).to.exist;
48
+ expect(data[0].example.attributes.c.name).to.equal('c');
49
+ expect(data[0].example.attributes.c.type).to.equal('asset');
50
+ expect(data[0].example.attributes.c.array).to.equal(true);
51
+ expect(data[0].example.attributes.c.default).to.equal(null);
52
+ expect(data[0].example.attributes.c.resource).to.equal('container');
53
+ });
40
54
 
41
- it('c: should be a asset attribute array with a container resource', function () {
42
- expect(data[0].example.attributes.c).to.exist;
43
- expect(data[0].example.attributes.c.name).to.equal('c');
44
- expect(data[0].example.attributes.c.type).to.equal('asset');
45
- expect(data[0].example.attributes.c.array).to.equal(true);
46
- expect(data[0].example.attributes.c.default).to.equal(null);
47
- expect(data[0].example.attributes.c.resource).to.equal('container');
48
55
  });
56
+ }
49
57
 
50
- });
58
+ runTests('./asset.valid.js');
59
+ runTests('./asset.valid.ts');
@@ -3,47 +3,55 @@ import { describe, it, before } from 'mocha';
3
3
 
4
4
  import { parseAttributes } from '../../utils.js';
5
5
 
6
- describe('VALID: Checkbox attribute', function () {
7
- let data;
8
- before(async function () {
9
- data = await parseAttributes('./checkbox.valid.js');
10
- });
11
-
12
- it('only results should exist', function () {
13
- expect(data).to.exist;
14
- expect(data[0]).to.not.be.empty;
15
- expect(data[1]).to.be.empty;
16
- });
17
-
18
- it('Example: should exist without errors', function () {
19
- expect(data[0]?.example).to.exist;
20
- expect(data[0].example.attributes).to.not.be.empty;
21
- expect(data[0].example.errors).to.be.empty;
22
- });
23
-
24
- it('a: should be a checkbox attribute', function () {
25
- expect(data[0].example.attributes.a).to.exist;
26
- expect(data[0].example.attributes.a.name).to.equal('a');
27
- expect(data[0].example.attributes.a.type).to.equal('boolean');
28
- expect(data[0].example.attributes.a.array).to.equal(false);
29
- expect(data[0].example.attributes.a.default).to.equal(false);
30
- });
31
-
32
- it('b: should be a checkbox attribute with a default value', function () {
33
- expect(data[0].example.attributes.b).to.exist;
34
- expect(data[0].example.attributes.b.name).to.equal('b');
35
- expect(data[0].example.attributes.b.type).to.equal('boolean');
36
- expect(data[0].example.attributes.b.array).to.equal(false);
37
- expect(data[0].example.attributes.b.default).to.equal(true);
38
- });
6
+ function runTests(fileName) {
7
+
8
+ const isTS = fileName.endsWith('.ts');
9
+ const script = isTS ? 'TS' : 'JS';
10
+
11
+ describe(`${script}: VALID: Checkbox attribute`, function () {
12
+ let data;
13
+ before(async function () {
14
+ data = await parseAttributes(fileName);
15
+ });
16
+
17
+ it('only results should exist', function () {
18
+ expect(data).to.exist;
19
+ expect(data[0]).to.not.be.empty;
20
+ expect(data[1]).to.be.empty;
21
+ });
22
+
23
+ it('Example: should exist without errors', function () {
24
+ expect(data[0]?.example).to.exist;
25
+ expect(data[0].example.attributes).to.not.be.empty;
26
+ expect(data[0].example.errors).to.be.empty;
27
+ });
28
+
29
+ it('a: should be a checkbox attribute', function () {
30
+ expect(data[0].example.attributes.a).to.exist;
31
+ expect(data[0].example.attributes.a.name).to.equal('a');
32
+ expect(data[0].example.attributes.a.type).to.equal('boolean');
33
+ expect(data[0].example.attributes.a.array).to.equal(false);
34
+ expect(data[0].example.attributes.a.default).to.equal(false);
35
+ });
36
+
37
+ it('b: should be a checkbox attribute with a default value', function () {
38
+ expect(data[0].example.attributes.b).to.exist;
39
+ expect(data[0].example.attributes.b.name).to.equal('b');
40
+ expect(data[0].example.attributes.b.type).to.equal('boolean');
41
+ expect(data[0].example.attributes.b.array).to.equal(false);
42
+ expect(data[0].example.attributes.b.default).to.equal(true);
43
+ });
44
+
45
+ it('c: should be a checkbox attribute array with a size', function () {
46
+ expect(data[0].example.attributes.c).to.exist;
47
+ expect(data[0].example.attributes.c.name).to.equal('c');
48
+ expect(data[0].example.attributes.c.type).to.equal('boolean');
49
+ expect(data[0].example.attributes.c.array).to.equal(true);
50
+ expect(data[0].example.attributes.c.size).to.equal(2);
51
+ expect(data[0].example.attributes.c.default).equal(null);
52
+ });
39
53
 
40
- it('c: should be a checkbox attribute array with a size', function () {
41
- expect(data[0].example.attributes.c).to.exist;
42
- expect(data[0].example.attributes.c.name).to.equal('c');
43
- expect(data[0].example.attributes.c.type).to.equal('boolean');
44
- expect(data[0].example.attributes.c.array).to.equal(true);
45
- expect(data[0].example.attributes.c.size).to.equal(2);
46
- expect(data[0].example.attributes.c.default).equal(null);
47
54
  });
55
+ }
48
56
 
49
- });
57
+ runTests('./checkbox.valid.js');
@@ -3,71 +3,76 @@ import { describe, it, before } from 'mocha';
3
3
 
4
4
  import { parseAttributes } from '../../utils.js';
5
5
 
6
- describe('VALID: Enum attribute', function () {
7
- let data;
8
- before(async function () {
9
- data = await parseAttributes('./enum.valid.js');
10
- });
6
+ function runTests(fileName) {
11
7
 
12
- it('only results should exist', function () {
13
- expect(data).to.exist;
14
- expect(data[0]).to.not.be.empty;
15
- expect(data[1]).to.be.empty;
16
- });
8
+ const isTS = fileName.endsWith('.ts');
9
+ const script = isTS ? 'TS' : 'JS';
17
10
 
18
- it('Example: should exist without errors', function () {
19
- expect(data[0]?.example).to.exist;
20
- expect(data[0].example.attributes).to.not.be.empty;
21
- expect(data[0].example.errors).to.be.empty;
22
- });
11
+ describe(`${script}: VALID: Enum attribute`, function () {
12
+ let data;
13
+ before(async function () {
14
+ data = await parseAttributes(fileName);
15
+ });
23
16
 
24
- it('e: should be a enum attribute', function () {
25
- expect(data[0].example.attributes.e).to.exist;
26
- expect(data[0].example.attributes.e.name).to.equal('e');
27
- expect(data[0].example.attributes.e.type).to.equal('number');
28
- expect(data[0].example.attributes.e.array).to.equal(false);
29
- expect(data[0].example.attributes.e.default).to.equal(0);
30
- });
17
+ it('only results should exist', function () {
18
+ expect(data).to.exist;
19
+ expect(data[0]).to.not.be.empty;
20
+ expect(data[1]).to.be.empty;
21
+ });
31
22
 
32
- it('f: should be a enum attribute with a default value', function () {
33
- expect(data[0].example.attributes.f).to.exist;
34
- expect(data[0].example.attributes.f.name).to.equal('f');
35
- expect(data[0].example.attributes.f.type).to.equal('number');
36
- expect(data[0].example.attributes.f.array).to.equal(false);
37
- expect(data[0].example.attributes.f.default).to.equal(1);
38
- });
23
+ it('Example: should exist without errors', function () {
24
+ expect(data[0]?.example).to.exist;
25
+ expect(data[0].example.errors).to.be.empty;
26
+ expect(data[0].example.attributes).to.not.be.empty;
27
+ });
39
28
 
40
- it('g: should be a enum attribute array with a size', function () {
41
- expect(data[0].example.attributes.g).to.exist;
42
- expect(data[0].example.attributes.g.name).to.equal('g');
43
- expect(data[0].example.attributes.g.type).to.equal('number');
44
- expect(data[0].example.attributes.g.array).to.equal(true);
45
- expect(data[0].example.attributes.g.size).to.equal(2);
46
- expect(data[0].example.attributes.g.default).equal(null);
47
- });
29
+ it('e: should be a enum attribute', function () {
30
+ expect(data[0].example.attributes.e).to.exist;
31
+ expect(data[0].example.attributes.e.name).to.equal('e');
32
+ expect(data[0].example.attributes.e.type).to.equal('number');
33
+ expect(data[0].example.attributes.e.array).to.equal(false);
34
+ expect(data[0].example.attributes.e.default).to.equal(13);
35
+ });
48
36
 
49
- it('h: should be a enum attribute', function () {
50
- expect(data[0].example.attributes.h).to.exist;
51
- expect(data[0].example.attributes.h.name).to.equal('h');
52
- expect(data[0].example.attributes.h.type).to.equal('string');
53
- expect(data[0].example.attributes.h.array).to.equal(false);
54
- expect(data[0].example.attributes.h.default).to.equal('');
55
- });
37
+ it('f: should be a enum attribute with a default value', function () {
38
+ expect(data[0].example.attributes.f).to.exist;
39
+ expect(data[0].example.attributes.f.name).to.equal('f');
40
+ expect(data[0].example.attributes.f.type).to.equal('number');
41
+ expect(data[0].example.attributes.f.array).to.equal(false);
42
+ expect(data[0].example.attributes.f.default).to.equal(1);
43
+ });
56
44
 
57
- it('i: should be a enum attribute with a default value', function () {
58
- expect(data[0].example.attributes.i).to.exist;
59
- expect(data[0].example.attributes.i.name).to.equal('i');
60
- expect(data[0].example.attributes.i.type).to.equal('vec3');
61
- expect(data[0].example.attributes.i.array).to.equal(false);
62
- expect(data[0].example.attributes.i.default).to.eql([0, 0, 0]);
63
- });
45
+ it('g: should be a enum attribute array with a size', function () {
46
+ expect(data[0].example.attributes.g).to.exist;
47
+ expect(data[0].example.attributes.g.name).to.equal('g');
48
+ expect(data[0].example.attributes.g.type).to.equal('number');
49
+ expect(data[0].example.attributes.g.array).to.equal(true);
50
+ expect(data[0].example.attributes.g.size).to.equal(2);
51
+ expect(data[0].example.attributes.g.default).equal(null);
52
+ });
53
+
54
+ it('h: should be a enum attribute', function () {
55
+ expect(data[0].example.attributes.h).to.exist;
56
+ expect(data[0].example.attributes.h.name).to.equal('h');
57
+ expect(data[0].example.attributes.h.type).to.equal('string');
58
+ expect(data[0].example.attributes.h.array).to.equal(false);
59
+ expect(data[0].example.attributes.h.enum).to.be.an('array').with.lengthOf(3);
60
+ expect(data[0].example.attributes.h.enum[0]).to.deep.equal({ A: 'a' });
61
+ expect(data[0].example.attributes.h.enum[1]).to.deep.equal({ B: 'b' });
62
+ expect(data[0].example.attributes.h.enum[2]).to.deep.equal({ C: 'c' });
63
+ expect(data[0].example.attributes.h.default).to.equal('');
64
+ });
65
+
66
+ it('j: should be a enum attribute array with a size', function () {
67
+ expect(data[0].example.attributes.i).to.exist;
68
+ expect(data[0].example.attributes.i.name).to.equal('i');
69
+ expect(data[0].example.attributes.i.type).to.equal('number');
70
+ expect(data[0].example.attributes.i.array).to.equal(false);
71
+ expect(data[0].example.attributes.i.default).equal(2);
72
+ });
64
73
 
65
- it('j: should be a enum attribute array with a size', function () {
66
- expect(data[0].example.attributes.j).to.exist;
67
- expect(data[0].example.attributes.j.name).to.equal('j');
68
- expect(data[0].example.attributes.j.type).to.equal('number');
69
- expect(data[0].example.attributes.j.array).to.equal(false);
70
- expect(data[0].example.attributes.j.default).equal(2);
71
74
  });
75
+ }
72
76
 
73
- });
77
+ runTests('./enum.valid.js');
78
+ runTests('./enum.valid.ts');
@@ -3,7 +3,8 @@ import { describe, it, before } from 'mocha';
3
3
 
4
4
  import { parseAttributes } from '../../utils.js';
5
5
 
6
- describe('VALID: JSON attribute', function () {
6
+ describe('JS: VALID: Asset attribute', function () {
7
+
7
8
  let data;
8
9
  before(async function () {
9
10
  data = await parseAttributes('./json.valid.js');
@@ -147,3 +148,130 @@ describe('VALID: JSON attribute', function () {
147
148
  expect(data[0].example.attributes.o).to.not.exist;
148
149
  });
149
150
  });
151
+
152
+
153
+ describe('TS: VALID: Asset attribute', function () {
154
+
155
+ let data;
156
+ before(async function () {
157
+ data = await parseAttributes('./json.valid.ts');
158
+ });
159
+
160
+ it('only results should exist', function () {
161
+ expect(data).to.exist;
162
+ expect(data[0]).to.not.be.empty;
163
+ expect(data[1]).to.be.empty;
164
+ });
165
+
166
+ it('Example: should exist without errors', function () {
167
+ expect(data[0]?.example).to.exist;
168
+ expect(data[0].example.attributes).to.not.be.empty;
169
+ expect(data[0].example.errors).to.be.empty;
170
+ });
171
+
172
+ it('f: should be a json attribute', function () {
173
+ expect(data[0].example.attributes.f).to.exist;
174
+ expect(data[0].example.attributes.f.name).to.equal('f');
175
+ expect(data[0].example.attributes.f.type).to.equal('json');
176
+ expect(data[0].example.attributes.f.array).to.equal(false);
177
+ expect(data[0].example.attributes.f.default).to.equal(null);
178
+
179
+ expect(data[0].example.attributes.f.schema).to.exist;
180
+ });
181
+
182
+ it('f[a]: should be a boolean attribute', function () {
183
+ expect(data[0].example.attributes.f.schema[0].name).to.equal('a');
184
+ expect(data[0].example.attributes.f.schema[0].type).to.equal('boolean');
185
+ expect(data[0].example.attributes.f.schema[0].array).to.equal(false);
186
+ expect(data[0].example.attributes.f.schema[0].default).to.equal(false);
187
+ });
188
+
189
+ it('f[b]: should be a numeric attribute', function () {
190
+ expect(data[0].example.attributes.f.schema[1].name).to.equal('b');
191
+ expect(data[0].example.attributes.f.schema[1].type).to.equal('number');
192
+ expect(data[0].example.attributes.f.schema[1].array).to.equal(false);
193
+ expect(data[0].example.attributes.f.schema[1].default).to.equal(10);
194
+ });
195
+
196
+ it('f[c]: should be a string attribute', function () {
197
+ expect(data[0].example.attributes.f.schema[2].name).to.equal('c');
198
+ expect(data[0].example.attributes.f.schema[2].type).to.equal('string');
199
+ expect(data[0].example.attributes.f.schema[2].array).to.equal(false);
200
+ expect(data[0].example.attributes.f.schema[2].default).to.equal('hello');
201
+ });
202
+
203
+ it('f[d]: should be a vec3 attribute array', function () {
204
+ expect(data[0].example.attributes.f.schema[3].name).to.equal('d');
205
+ expect(data[0].example.attributes.f.schema[3].type).to.equal('vec3');
206
+ expect(data[0].example.attributes.f.schema[3].array).to.equal(true);
207
+ expect(data[0].example.attributes.f.schema[3].default).to.eql(null);
208
+ });
209
+
210
+ it('g: should be a json attribute array with a size', function () {
211
+ expect(data[0].example.attributes.g).to.exist;
212
+ expect(data[0].example.attributes.g.name).to.equal('g');
213
+ expect(data[0].example.attributes.g.type).to.equal('json');
214
+ expect(data[0].example.attributes.g.array).to.equal(true);
215
+ expect(data[0].example.attributes.g.size).to.equal(2);
216
+ expect(data[0].example.attributes.g.default).to.eql(null);
217
+
218
+ expect(data[0].example.attributes.g.schema).to.exist;
219
+ });
220
+
221
+ it('h: should be a nested json attribute (parent)', function () {
222
+ expect(data[0].example.attributes.h).to.exist;
223
+ expect(data[0].example.attributes.h.name).to.equal('h');
224
+ expect(data[0].example.attributes.h.type).to.equal('json');
225
+ expect(data[0].example.attributes.h.array).to.equal(false);
226
+ expect(data[0].example.attributes.h.default).to.eql(null);
227
+
228
+ expect(data[0].example.attributes.h.schema).to.exist;
229
+ });
230
+
231
+ it('h[x]: should be a nested json attribute (child)', function () {
232
+ expect(data[0].example.attributes.h.schema[0].name).to.equal('x');
233
+ expect(data[0].example.attributes.h.schema[0].type).to.equal('json');
234
+ expect(data[0].example.attributes.h.schema[0].array).to.equal(false);
235
+ expect(data[0].example.attributes.h.schema[0].default).to.eql(null);
236
+
237
+ expect(data[0].example.attributes.h.schema[0].schema).to.exist;
238
+ });
239
+
240
+ it('i: should be an inline default json attribute', function () {
241
+ expect(data[0].example.attributes.i).to.exist;
242
+ expect(data[0].example.attributes.i.name).to.equal('i');
243
+ expect(data[0].example.attributes.i.type).to.equal('json');
244
+ expect(data[0].example.attributes.i.array).to.equal(false);
245
+ expect(data[0].example.attributes.i.default).to.eql(null);
246
+
247
+ expect(data[0].example.attributes.i.schema).to.exist;
248
+ });
249
+
250
+ it('l: should be an inline type json attribute', function () {
251
+ expect(data[0].example.attributes.l).to.exist;
252
+ expect(data[0].example.attributes.l.name).to.equal('l');
253
+ expect(data[0].example.attributes.l.type).to.equal('json');
254
+ expect(data[0].example.attributes.l.array).to.equal(false);
255
+ expect(data[0].example.attributes.l.default).to.eql(null);
256
+
257
+ expect(data[0].example.attributes.l.schema).to.exist;
258
+ });
259
+
260
+ it('m: should be a self reference json attribute', function () {
261
+ expect(data[0].example.attributes.m).to.exist;
262
+ expect(data[0].example.attributes.m.name).to.equal('m');
263
+ expect(data[0].example.attributes.m.type).to.equal('json');
264
+ expect(data[0].example.attributes.m.array).to.equal(false);
265
+ expect(data[0].example.attributes.m.default).to.eql(null);
266
+
267
+ expect(data[0].example.attributes.m.schema).to.exist;
268
+ });
269
+
270
+ it('n: should not be recognised as an attribute (no attribute tag)', function () {
271
+ expect(data[0].example.attributes.n).to.not.exist;
272
+ });
273
+
274
+ it('o: should not be recognised as an attribute (type not an interface)', function () {
275
+ expect(data[0].example.attributes.o).to.not.exist;
276
+ });
277
+ });