@playcanvas/attribute-parser 1.7.0 → 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 (3) hide show
  1. package/dist/index.js +83 -194204
  2. package/package.json +2 -2
  3. package/src/index.js +0 -273
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcanvas/attribute-parser",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "author": "PlayCanvas <support@playcanvas.com>",
5
5
  "homepage": "https://github.com/playcanvas/attribute-parser#readme",
6
6
  "description": "The PlayCanvas Attribute Parser",
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "license": "MIT",
13
13
  "type": "module",
14
- "main": "src/index.js",
14
+ "main": "dist/index.js",
15
15
  "types": "types/index.d.ts",
16
16
  "exports": {
17
17
  ".": {
package/src/index.js DELETED
@@ -1,273 +0,0 @@
1
- import { createSystem, createDefaultMapFromNodeModules, createVirtualTypeScriptEnvironment } from '@typescript/vfs';
2
- import * as ts from 'typescript';
3
-
4
- import { ScriptParser } from './parsers/script-parser.js';
5
- import { isInterface, isStaticMember } from './utils/attribute-utils.js';
6
- import { createDefaultMapFromCDN, flatMapAnyNodes, getExportedNodes, getType, inheritsFrom, isAliasedClassDeclaration } from './utils/ts-utils.js';
7
-
8
- const toLowerCamelCase = str => str[0].toLowerCase() + str.substring(1);
9
-
10
- const COMPILER_OPTIONS = {
11
- noLib: true,
12
- strict: false,
13
- skipLibCheck: true, // Skip type checking of declaration files
14
- target: ts.ScriptTarget.ES2023, // If this version changes, the types must be updated in the /rollup.config.mjs
15
- module: ts.ModuleKind.CommonJS,
16
- checkJs: true, // Enable JSDoc parsing
17
- allowJs: true,
18
- baseUrl: './',
19
- paths: {
20
- 'playcanvas': ['/playcanvas.js']
21
- }
22
- };
23
-
24
- export class JSDocParser {
25
- /**
26
- * @type {import('@typescript/vfs').VirtualTypeScriptEnvironment}
27
- * @private
28
- */
29
- _env;
30
-
31
- /**
32
- * An internal array of program errors
33
- * @private
34
- */
35
- _errors = [];
36
-
37
- /**
38
- * Initializes the JSDocParser with the standard library files
39
- *
40
- * @param {string} libPath - A path to a directory of library types, or a path to the '.d.ts' file itself
41
- * @returns {Promise<JSDocParser>} - The initialized JSDocParser
42
- */
43
- async init(libPath) {
44
- if (this._env) {
45
- return this;
46
- }
47
-
48
- let fsMap;
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.
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);
64
- }
65
-
66
- // Set up the virtual file system and environment
67
- const system = createSystem(fsMap);
68
- this._env = createVirtualTypeScriptEnvironment(system, Array.from(fsMap.keys()), ts, COMPILER_OPTIONS);
69
-
70
- return this;
71
- }
72
-
73
- /**
74
- * Creates program from fileName and scriptsContents
75
- *
76
- * @param {[string, string][]} newFiles - An array of {[path]: [content]} which represent the files in the program
77
- * @param {string[]} removedFiles - An array of file paths that should be removed from the program
78
- * @returns {{ parser: ScriptParser, program: ts.Program,
79
- * errors: import('./parsers/script-parser.js').ValidationError[]}} - The extracted attributes
80
- */
81
- updateProgram(newFiles = [], removedFiles = []) {
82
- const errors = [];
83
-
84
- // Add custom files to the map
85
- newFiles.forEach(([filePath, fileContent]) => {
86
- if (this._env.getSourceFile(filePath)) {
87
- this._env.updateFile(filePath, fileContent);
88
- } else {
89
- this._env.createFile(filePath, fileContent);
90
- }
91
- });
92
-
93
- // Remove deleted Files
94
- removedFiles.forEach((filePath) => {
95
- this._env.deleteFile(filePath);
96
- });
97
-
98
-
99
- this.parser = new ScriptParser(this._env);
100
- this.program = this._env.languageService.getProgram();
101
-
102
- // Check for compiler errors
103
- this.parser.validateProgram(errors);
104
-
105
- this._errors = errors;
106
-
107
- return errors;
108
- }
109
-
110
- /**
111
- * Returns all the valid ESM Scripts within a file
112
- * @param {string} fileName - The file name in the program to check
113
- * @returns {Map<string, import('typescript').Node>} - A map of valid ESM Script <names, nodes> within the file
114
- */
115
- getAllEsmScripts(fileName) {
116
-
117
- const typeChecker = this.program.getTypeChecker();
118
-
119
- // Find the Script class in the PlayCanvas namespace
120
- const pcTypes = this.program.getSourceFile('/playcanvas.d.ts');
121
-
122
- if (!pcTypes) {
123
- throw new Error('PlayCanvas Types must be supplied');
124
- }
125
-
126
- const esmScripts = new Map();
127
-
128
- // Parse the source file and pc types
129
- const sourceFile = this.program.getSourceFile(fileName);
130
-
131
- // In a special edge case where the files contents are empty, the sourcefile will be undefined
132
- // So we should early out
133
- if (sourceFile === undefined) {
134
- return esmScripts;
135
- }
136
-
137
- if (sourceFile === null) {
138
- throw new Error(`Source file ${fileName} not found`);
139
- }
140
-
141
- // Extract all exported nodes
142
- const nodes = getExportedNodes(this.program, sourceFile);
143
-
144
- const esmScriptClass = pcTypes.statements.find(node => node.kind === ts.SyntaxKind.ClassDeclaration && node.name.text === 'Script')?.symbol;
145
-
146
- const isScriptNameMember = member => (
147
- ts.isPropertyDeclaration(member) && // Is a property declaration
148
- ts.isIdentifier(member.name) &&
149
- member.name.text === 'scriptName' &&
150
- isStaticMember(member) &&
151
- ts.isStringLiteral(member.initializer)
152
- );
153
-
154
- // Check if the file exports a class that inherits from `Script`
155
- nodes.forEach((node, name) => {
156
- if (isAliasedClassDeclaration(node, typeChecker) && inheritsFrom(node, typeChecker, esmScriptClass)) {
157
-
158
- // Check for a static scriptName property and use that as the name if it exists
159
- const scriptNameMember = node.members.find(isScriptNameMember);
160
-
161
- // If the scriptName property exists, use that as the name
162
- if (scriptNameMember) {
163
- name = scriptNameMember.initializer.text;
164
- }
165
-
166
- esmScripts.set(name, node);
167
- }
168
- });
169
-
170
- return esmScripts;
171
- }
172
-
173
- /**
174
- * Analyzes the specified TypeScript file and extracts metadata from JSDoc comments.
175
- *
176
- * @param {string} fileName - The name of the file in the program to run
177
- * @returns {[results: object, errors: import('./parsers/script-parser.js').ValidationError[]]} - The extracted attributes
178
- */
179
- parseAttributes(fileName) {
180
- const results = {};
181
- const errors = [...this._errors];
182
-
183
- if (errors.length > 0) {
184
- return [results, errors];
185
- }
186
-
187
- // Extract all exported nodes
188
- const nodes = this.getAllEsmScripts(fileName);
189
-
190
- // Extract attributes from each script
191
- nodes.forEach((node, name) => {
192
- const opts = results[toLowerCamelCase(name)] = { attributes: {}, errors: [] };
193
- this.parser.extractAttributes(node, opts);
194
- });
195
-
196
- return [results, errors];
197
- }
198
-
199
- /**
200
- * Analyzes the specified TypeScript file and extracts metadata from JSDoc comments.
201
- * @param {string} fileName - The name of the file to run
202
- * @returns {[results: object, errors: import('./parsers/script-parser.js').ValidationError[]]} - The extracted attributes
203
- */
204
- getAttributes(fileName) {
205
- const results = {};
206
- const errors = [...this._errors];
207
-
208
- if (errors.length > 0) {
209
- return [results, errors];
210
- }
211
-
212
- const typeChecker = this.program.getTypeChecker();
213
-
214
- // Find the Script class in the PlayCanvas namespace
215
- const pcTypes = this.program.getSourceFile('/playcanvas.d.ts');
216
- const esmScriptClass = pcTypes.statements.find(node => node.kind === ts.SyntaxKind.ClassDeclaration && node.name.text === 'Script')?.symbol;
217
-
218
- // Parse the source file and pc types
219
- const sourceFile = this.program.getSourceFile(fileName);
220
-
221
- if (!sourceFile) {
222
- throw new Error(`Source file ${fileName} not found`);
223
- }
224
-
225
- const nodes = flatMapAnyNodes(sourceFile, (node) => {
226
- if (!ts.isClassDeclaration(node)) {
227
- return false;
228
- }
229
-
230
- return inheritsFrom(node, typeChecker, esmScriptClass) || isInterface(node);
231
- });
232
-
233
- for (const node of nodes) {
234
- const name = toLowerCamelCase(node.name.text);
235
- const members = [];
236
-
237
- for (const member of node.members) {
238
- if (member.kind !== ts.SyntaxKind.PropertyDeclaration || isStaticMember(member)) {
239
- continue;
240
- }
241
-
242
- // Extract JSDoc tags
243
- const tags = member.jsDoc ? member.jsDoc.map(jsdoc => jsdoc.tags?.map(tag => tag.tagName.getText()) ?? []).flat() : [];
244
-
245
- const name = member.name.getText();
246
- const type = getType(member, typeChecker);
247
- if (!type) {
248
- continue;
249
- }
250
-
251
- const namePos = ts.getLineAndCharacterOfPosition(member.getSourceFile(), member.name.getStart());
252
-
253
- const jsdocNode = member.jsDoc && member.jsDoc[member.jsDoc.length - 1];
254
- const jsdocPos = jsdocNode ? ts.getLineAndCharacterOfPosition(member.getSourceFile(), jsdocNode.getStart()) : null;
255
-
256
- const data = {
257
- name,
258
- type: type.name + (type.array ? '[]' : ''),
259
- isAttribute: tags.includes('attribute'),
260
- start: jsdocNode ? { line: jsdocPos.line + 1, column: jsdocPos.character + 1 } :
261
- { line: namePos.line + 1, column: namePos.character + 1 },
262
- end: { line: namePos.line + 1, column: namePos.character + name.length + 1 }
263
- };
264
-
265
- members.push(data);
266
- }
267
-
268
- results[name] = members;
269
- }
270
-
271
- return [results, errors];
272
- }
273
- }