@playcanvas/attribute-parser 1.3.0 → 1.3.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.
@@ -7,25 +7,43 @@ on:
7
7
  branches: [ main ]
8
8
 
9
9
  jobs:
10
+ lint:
11
+ name: Lint Code
12
+ runs-on: ubuntu-latest
13
+ timeout-minutes: 10
14
+ steps:
15
+ - name: Checkout code
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Setup Node.js 18.x
19
+ uses: actions/setup-node@v4
20
+ with:
21
+ node-version: 18.x
22
+ cache: 'npm'
23
+
24
+ - name: Install dependencies
25
+ run: npm clean-install --progress=false --no-fund
26
+
27
+ - name: Run ESLint
28
+ run: npm run lint
29
+
10
30
  unit-test-node:
11
31
  name: Unit Test
12
32
  runs-on: ubuntu-latest
13
33
  timeout-minutes: 10
34
+ needs: lint # Ensure linting completes successfully first
14
35
  steps:
15
- - name: Checkout code
16
- uses: actions/checkout@v4
17
-
18
- - name: Setup Node.js 18.x
19
- uses: actions/setup-node@v4
20
- with:
21
- node-version: 18.x
22
- cache: 'npm'
36
+ - name: Checkout code
37
+ uses: actions/checkout@v4
23
38
 
24
- - name: Install dependencies
25
- run: npm clean-install --progress=false --no-fund
39
+ - name: Setup Node.js 18.x
40
+ uses: actions/setup-node@v4
41
+ with:
42
+ node-version: 18.x
43
+ cache: 'npm'
26
44
 
27
- - name: Run ESLint
28
- run: npm run lint
45
+ - name: Install dependencies
46
+ run: npm clean-install --progress=false --no-fund
29
47
 
30
- - name: Run unit tests
31
- run: npm test
48
+ - name: Run unit tests
49
+ run: npm test
@@ -0,0 +1,35 @@
1
+ import playcanvasConfig from '@playcanvas/eslint-config';
2
+ import globals from 'globals';
3
+
4
+ export default [
5
+ ...playcanvasConfig,
6
+ {
7
+ ignores: ['**/program.invalid.js'] // Exclude specific files
8
+ },
9
+ {
10
+ languageOptions: {
11
+ ecmaVersion: 2022,
12
+ sourceType: 'module',
13
+ globals: {
14
+ ...globals.browser,
15
+ ...globals.mocha,
16
+ ...globals.node
17
+ }
18
+ }
19
+ },
20
+ {
21
+ rules: {
22
+ 'no-unused-expressions': 'off',
23
+ 'jsdoc/check-tag-names': 'off',
24
+ 'import/no-unresolved': 'off'
25
+ }
26
+ },
27
+ {
28
+ // Configuration specific to test files
29
+ files: ['**/*.test.js'], // Match test files
30
+
31
+ rules: {
32
+ 'prefer-arrow-callback': 'off' // Allow function expressions in test files
33
+ }
34
+ }
35
+ ];
package/package.json CHANGED
@@ -5,18 +5,18 @@
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
7
7
  },
8
- "version": "1.3.0",
8
+ "version": "1.3.1",
9
9
  "dependencies": {
10
10
  "@microsoft/tsdoc": "^0.15.0",
11
- "@playcanvas/eslint-config": "^1.7.4",
11
+ "@playcanvas/eslint-config": "^1.7.4 || ^2.0.0",
12
12
  "@typescript/vfs": "^1.6.0",
13
- "eslint": "^8.56.0",
13
+ "eslint": "^8.56.0 || ^9.0.0",
14
14
  "typescript": "^5.5.3"
15
15
  },
16
16
  "devDependencies": {
17
- "chai": "^5.1.1",
18
- "globals": "^15.9.0",
19
- "mocha": "^10.6.0"
17
+ "chai": "5.1.2",
18
+ "globals": "15.14.0",
19
+ "mocha": "11.1.0"
20
20
  },
21
21
  "scripts": {
22
22
  "test": "mocha test/**/*.test.js --timeout 10000",
package/renovate.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "config:recommended"
5
+ ],
6
+ "packageRules": [
7
+ {
8
+ "matchManagers": [
9
+ "npm"
10
+ ],
11
+ "groupName": "all npm dependencies",
12
+ "schedule": [
13
+ "on monday at 10:00am"
14
+ ]
15
+ },
16
+ {
17
+ "matchDepTypes": ["devDependencies"],
18
+ "rangeStrategy": "pin"
19
+ },
20
+ {
21
+ "matchDepTypes": ["dependencies"],
22
+ "rangeStrategy": "widen"
23
+ }
24
+ ],
25
+ }
package/src/index.js CHANGED
@@ -110,7 +110,7 @@ export class JSDocParser {
110
110
  /**
111
111
  * Returns all the valid ESM Scripts within a file
112
112
  * @param {string} fileName - The file name in the program to check
113
- * @returns {import('typescript').Node[]} - An array of any exported ESM Script nodes within the file
113
+ * @returns {Map<string, import('typescript').Node>} - A map of valid ESM Script <names, nodes> within the file
114
114
  */
115
115
  getAllEsmScripts(fileName) {
116
116
 
@@ -135,9 +135,15 @@ export class JSDocParser {
135
135
 
136
136
  const esmScriptClass = pcTypes.statements.find(node => node.kind === ts.SyntaxKind.ClassDeclaration && node.name.text === 'Script')?.symbol;
137
137
 
138
+ const esmScripts = new Map();
138
139
  // Check if the file exports a class that inherits from `Script`
139
- return Array.from(nodes).filter(node => isAliasedClassDeclaration(node, typeChecker) && inheritsFrom(node, typeChecker, esmScriptClass));
140
+ nodes.forEach((node, name) => {
141
+ if (isAliasedClassDeclaration(node, typeChecker) && inheritsFrom(node, typeChecker, esmScriptClass)) {
142
+ esmScripts.set(name, node);
143
+ }
144
+ });
140
145
 
146
+ return esmScripts;
141
147
  }
142
148
 
143
149
  /**
@@ -158,9 +164,8 @@ export class JSDocParser {
158
164
  const nodes = this.getAllEsmScripts(fileName);
159
165
 
160
166
  // Extract attributes from each script
161
- nodes.forEach((node) => {
162
- const name = toLowerCamelCase(node.name.text);
163
- const opts = results[name] = { attributes: {}, errors: [] };
167
+ nodes.forEach((node, name) => {
168
+ const opts = results[toLowerCamelCase(name)] = { attributes: {}, errors: [] };
164
169
  this.parser.extractAttributes(node, opts);
165
170
  });
166
171
 
@@ -321,17 +321,17 @@ export class ScriptParser {
321
321
  return attributes;
322
322
  }
323
323
 
324
- const buffer = node.getSourceFile().getFullText();
325
-
326
324
  // Find "/** */" style comments associated with this node.
327
- const comments = getJSDocCommentRanges(node, buffer, this.typeChecker);
325
+ const comments = getJSDocCommentRanges(node, this.typeChecker);
328
326
 
329
327
  // Parse the comments for attribute metadata
330
328
  for (const comment of comments) {
331
329
 
330
+ const memberFileText = comment.member.getSourceFile().getFullText();
331
+
332
332
  // Parse the comment for attribute metadata
333
333
  const attributeMetadata = this.attributeParser.parseAttributeComment(
334
- TextRange.fromStringRange(buffer, comment.range.pos, comment.range.end),
334
+ TextRange.fromStringRange(memberFileText, comment.range.pos, comment.range.end),
335
335
  comment.member,
336
336
  errors,
337
337
  requiresAttributeTag
@@ -24,12 +24,12 @@ export function parseTag(input = '') {
24
24
  try {
25
25
  const arr = parseStringToNumericalArray(input);
26
26
  if (Array.isArray(arr)) return arr;
27
- } catch { /* eslint-disable-next-line no-empty */ }
27
+ } catch { }
28
28
 
29
29
  try {
30
30
  const arr = JSON.parse(input);
31
31
  if (Array.isArray(arr)) return arr;
32
- } catch { /* eslint-disable-next-line no-empty */ }
32
+ } catch { }
33
33
 
34
34
  // If none of the above, return the string
35
35
  return input;
@@ -5,8 +5,8 @@ import * as ts from 'typescript';
5
5
  import { ParsingError } from '../parsers/parsing-error.js';
6
6
 
7
7
  export function createDefaultMapFromCDN(options, prefix, ts) {
8
- var fsMap = new Map();
9
- var files = knownLibFilesForCompilerOptions(options, ts);
8
+ const fsMap = new Map();
9
+ const files = knownLibFilesForCompilerOptions(options, ts);
10
10
 
11
11
  async function uncached() {
12
12
  const contentPromises = files.map((lib) => {
@@ -45,7 +45,7 @@ function resolveAliasedSymbol(typeChecker, symbol) {
45
45
  * Returns an array of exported nodes from a TypeScript source file.
46
46
  * @param {import('typescript').Program} program - The TypeScript program
47
47
  * @param {import('typescript').SourceFile} sourceFile - The TypeScript source file
48
- * @returns {Set<import('typescript').Node>} - A Set of exported nodes
48
+ * @returns {let map: Map<string, Set<import('typescript').Node>>} - A Map of exported nodes
49
49
  */
50
50
  export function getExportedNodes(program, sourceFile) {
51
51
  if (!program || !sourceFile) {
@@ -62,16 +62,18 @@ export function getExportedNodes(program, sourceFile) {
62
62
  const exportedSymbols = typeChecker.getExportsOfModule(moduleSymbol);
63
63
 
64
64
  // Find the actual declaration nodes for each exported symbol
65
- const exportedNodes = [];
65
+ const exportedNodes = new Map();
66
66
 
67
67
  exportedSymbols.forEach((symbol) => {
68
68
  const resolvedSymbol = resolveAliasedSymbol(typeChecker, symbol);
69
69
  if (resolvedSymbol.declarations) {
70
- exportedNodes.push(...resolvedSymbol.declarations);
70
+ const node = resolvedSymbol.declarations[0];
71
+ const name = symbol.name === 'default' ? node.name.getText() : symbol.name;
72
+ exportedNodes.set(name, node);
71
73
  }
72
74
  });
73
75
 
74
- return new Set(exportedNodes);
76
+ return exportedNodes;
75
77
  }
76
78
 
77
79
  /**
@@ -205,12 +207,13 @@ function getSuperClasses(node, typeChecker) {
205
207
  * @param {import('typescript').TypeChecker} typeChecker - The TypeScript type checker
206
208
  * @returns {{ memberName: string, member: ts.Node, range: import('typescript').CommentRange}[]} - An array of comment ranges
207
209
  */
208
- export function getJSDocCommentRanges(node, text, typeChecker) {
210
+ export function getJSDocCommentRanges(node, typeChecker) {
209
211
  const commentRanges = [];
210
212
 
211
213
  if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
212
214
  // get an array of the class an all parent classed
213
- const heritageChain = getSuperClasses(node, typeChecker);
215
+ const heritageChain = getSuperClasses(node, typeChecker)
216
+ .filter(classNode => !classNode.getSourceFile().isDeclarationFile);
214
217
 
215
218
  // iterate over the heritance chain
216
219
  heritageChain.forEach((classNode) => {
@@ -218,7 +221,7 @@ export function getJSDocCommentRanges(node, text, typeChecker) {
218
221
  classNode.members.forEach((member) => {
219
222
  if (ts.isPropertyDeclaration(member) || ts.isSetAccessor(member) || ts.isPropertySignature(member)) {
220
223
  const memberName = member.name && ts.isIdentifier(member.name) ? member.name.text : 'unnamed';
221
- const ranges = getLeadingBlockCommentRanges(member, text);
224
+ const ranges = getLeadingBlockCommentRanges(member, member.getSourceFile().getFullText());
222
225
  if (ranges.length > 0) {
223
226
  commentRanges.push({
224
227
  memberName,
@@ -0,0 +1,9 @@
1
+ import { Script } from 'playcanvas';
2
+
3
+ export class Example extends Script {
4
+ /**
5
+ * @attribute
6
+ * @type {boolean}
7
+ */
8
+ a;
9
+ }
@@ -1,3 +1,8 @@
1
1
  import { Script } from 'playcanvas';
2
2
 
3
- export class ExampleImport extends Script {}
3
+ export class ExampleImport extends Script {
4
+ /**
5
+ * @attribute
6
+ */
7
+ prop = 10;
8
+ }
@@ -4,9 +4,12 @@ import { ExampleImport } from './export.import.js';
4
4
 
5
5
  class ExampleImportExtend extends ExampleImport {}
6
6
 
7
- export { ExampleImport as ExampleImportExportAs } from './export.import.js';
7
+ export { ExampleImport as ExampleImportAsExport } from './export.import.js';
8
8
 
9
- class Example extends Script {}
9
+ class Example extends Script {
10
+ /** @attribute */
11
+ num = 10;
12
+ }
10
13
 
11
14
  export default class ExampleDefault extends Script {}
12
15
 
@@ -1,12 +1,6 @@
1
1
  import { Script } from 'playcanvas';
2
2
 
3
- class Example extends Script {
4
- /**
5
- * @attribute
6
- * @type {boolean}
7
- */
8
- a;
9
- }
3
+ import { Example } from './example.dep.js';
10
4
 
11
5
  class ExampleExtended extends Example {
12
6
  /**
@@ -15,9 +15,9 @@ describe('VALID: Export Script', function () {
15
15
  expect(data[1]).to.be.empty;
16
16
  });
17
17
 
18
- it('Example: should exist without attributes or errors', function () {
18
+ it('Example: should exist with attributes', function () {
19
19
  expect(data[0]?.example).to.exist;
20
- expect(data[0].example.attributes).to.be.empty;
20
+ expect(data[0].example.attributes).to.not.be.empty;
21
21
  expect(data[0].example.errors).to.be.empty;
22
22
  });
23
23
 
@@ -33,19 +33,31 @@ describe('VALID: Export Script', function () {
33
33
  expect(data[0].exampleDefault.errors).to.be.empty;
34
34
  });
35
35
 
36
+ it('ExampleImportAsExport: should not exist', function () {
37
+ expect(data[0]?.exampleImportAsExport).to.exist;
38
+ expect(data[0].exampleImportAsExport.attributes).to.not.be.empty;
39
+ expect(data[0].exampleImportAsExport.errors).to.be.empty;
40
+ });
41
+
36
42
  it('ExampleExportAs: should not exist', function () {
37
- expect(data[0]?.exampleExportAs).to.not.exist;
43
+ expect(data[0]?.exampleExportAs).to.exist;
44
+ expect(data[0].exampleExportAs.attributes).to.not.be.empty;
45
+ expect(data[0].exampleExportAs.errors).to.be.empty;
38
46
  });
39
47
 
40
- it('ExampleImport: should exist without attributes or errors', function () {
48
+ it('ExampleImport: should exist with attributes', function () {
41
49
  expect(data[0]?.exampleImport).to.exist;
42
- expect(data[0].exampleImport.attributes).to.be.empty;
50
+ expect(data[0].exampleImport.attributes).to.not.be.empty;
51
+ expect(data[0].exampleImport.attributes.prop.type).to.equal('number');
52
+ expect(data[0].exampleImport.attributes.prop.default).to.equal(10);
43
53
  expect(data[0].exampleImport.errors).to.be.empty;
44
54
  });
45
55
 
46
- it('ExampleImportExtend: should exist without attributes or errors', function () {
56
+ it('ExampleImportExtend: should exist with attributes', function () {
47
57
  expect(data[0]?.exampleImportExtend).to.exist;
48
- expect(data[0].exampleImportExtend.attributes).to.be.empty;
58
+ expect(data[0].exampleImportExtend.attributes).to.not.be.empty;
59
+ expect(data[0].exampleImportExtend.attributes.prop.type).to.equal('number');
60
+ expect(data[0].exampleImportExtend.attributes.prop.default).to.equal(10);
49
61
  expect(data[0].exampleImportExtend.errors).to.be.empty;
50
62
  });
51
63
 
@@ -6,7 +6,7 @@ import { parseAttributes } from '../../utils.js';
6
6
  describe('VALID: Script inheritance attributes', function () {
7
7
  let data;
8
8
  before(async function () {
9
- data = await parseAttributes('./inherit.valid.js');
9
+ data = await parseAttributes('./inherit.valid.js', './example.dep.js');
10
10
  });
11
11
 
12
12
  it('only results should exist', function () {
@@ -24,6 +24,14 @@ describe('VALID: Script inheritance attributes', function () {
24
24
  it('ExampleExtended: should exist without errors', function () {
25
25
  expect(data[0]?.exampleExtended).to.exist;
26
26
  expect(data[0].exampleExtended.attributes).to.not.be.empty;
27
+ expect(data[0].exampleExtended.attributes.a).to.exist;
28
+ expect(data[0].exampleExtended.attributes.a.type).to.equal('boolean');
29
+ expect(data[0].exampleExtended.attributes.a.array).to.equal(false);
30
+ expect(data[0].exampleExtended.attributes.a.default).to.equal(false);
31
+ expect(data[0].exampleExtended.attributes.b).to.be.exists;
32
+ expect(data[0].exampleExtended.attributes.b.type).to.equal('number');
33
+ expect(data[0].exampleExtended.attributes.b.array).to.equal(false);
34
+ expect(data[0].exampleExtended.attributes.b.default).to.equal(0);
27
35
  expect(data[0].exampleExtended.errors).to.be.empty;
28
36
  });
29
37
 
package/.eslintignore DELETED
@@ -1 +0,0 @@
1
- /**/program.invalid.js
package/.eslintrc DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "extends": [
3
- "@playcanvas/eslint-config"
4
- ],
5
- "rules": {
6
- "no-unused-expressions": "off",
7
- "jsdoc/check-tag-names": "off",
8
- "import/no-unresolved": "off"
9
- },
10
- "overrides": [
11
- {
12
- "files": ["./**/*.test.js"],
13
- "rules": {
14
- "prefer-arrow-callback": "off",
15
- }
16
- }
17
- ]
18
- }
19
-