@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.
- package/.github/workflows/ci.yml +32 -14
- package/eslint.config.mjs +35 -0
- package/package.json +6 -6
- package/renovate.json +25 -0
- package/src/index.js +10 -5
- package/src/parsers/script-parser.js +4 -4
- package/src/utils/tag-utils.js +2 -2
- package/src/utils/ts-utils.js +12 -9
- package/test/fixtures/example.dep.js +9 -0
- package/test/fixtures/export.import.js +6 -1
- package/test/fixtures/export.valid.js +5 -2
- package/test/fixtures/inherit.valid.js +1 -7
- package/test/tests/valid/export.test.js +19 -7
- package/test/tests/valid/inherit.test.js +9 -1
- package/.eslintignore +0 -1
- package/.eslintrc +0 -19
package/.github/workflows/ci.yml
CHANGED
|
@@ -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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
28
|
-
|
|
45
|
+
- name: Install dependencies
|
|
46
|
+
run: npm clean-install --progress=false --no-fund
|
|
29
47
|
|
|
30
|
-
|
|
31
|
-
|
|
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.
|
|
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": "
|
|
18
|
-
"globals": "
|
|
19
|
-
"mocha": "
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
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(
|
|
334
|
+
TextRange.fromStringRange(memberFileText, comment.range.pos, comment.range.end),
|
|
335
335
|
comment.member,
|
|
336
336
|
errors,
|
|
337
337
|
requiresAttributeTag
|
package/src/utils/tag-utils.js
CHANGED
|
@@ -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 {
|
|
27
|
+
} catch { }
|
|
28
28
|
|
|
29
29
|
try {
|
|
30
30
|
const arr = JSON.parse(input);
|
|
31
31
|
if (Array.isArray(arr)) return arr;
|
|
32
|
-
} catch {
|
|
32
|
+
} catch { }
|
|
33
33
|
|
|
34
34
|
// If none of the above, return the string
|
|
35
35
|
return input;
|
package/src/utils/ts-utils.js
CHANGED
|
@@ -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
|
-
|
|
9
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
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,
|
|
224
|
+
const ranges = getLeadingBlockCommentRanges(member, member.getSourceFile().getFullText());
|
|
222
225
|
if (ranges.length > 0) {
|
|
223
226
|
commentRanges.push({
|
|
224
227
|
memberName,
|
|
@@ -4,9 +4,12 @@ import { ExampleImport } from './export.import.js';
|
|
|
4
4
|
|
|
5
5
|
class ExampleImportExtend extends ExampleImport {}
|
|
6
6
|
|
|
7
|
-
export { ExampleImport as
|
|
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
|
|
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|