From 12eddf472523f4190e2ef0fed67ccfc7ddc449b2 Mon Sep 17 00:00:00 2001 From: Mark Lundin Date: Fri, 13 Sep 2024 15:52:30 +0100 Subject: [PATCH 01/13] Added check incase of missing jsdoc tag --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 0761c04..f8a1429 100644 --- a/src/index.js +++ b/src/index.js @@ -209,7 +209,7 @@ export class JSDocParser { const namePos = ts.getLineAndCharacterOfPosition(member.getSourceFile(), member.name.getStart()); - const jsdocNode = member.jsDoc[member.jsDoc.length - 1]; + const jsdocNode = member.jsDoc && member.jsDoc[member.jsDoc.length - 1]; const jsdocPos = jsdocNode ? ts.getLineAndCharacterOfPosition(member.getSourceFile(), jsdocNode.getStart()) : null; const data = { From c74bda5a50c319dbc96d5e952ef5d1328fe1f1b2 Mon Sep 17 00:00:00 2001 From: Mark Lundin Date: Fri, 13 Sep 2024 16:11:28 +0100 Subject: [PATCH 02/13] 1.0.8 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0fb6e2f..1db1b46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@playcanvas/attribute-parser", - "version": "1.0.7", + "version": "1.0.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@playcanvas/attribute-parser", - "version": "1.0.7", + "version": "1.0.8", "dependencies": { "@microsoft/tsdoc": "^0.15.0", "@playcanvas/eslint-config": "^1.7.4", diff --git a/package.json b/package.json index efcaed0..f325b86 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "engines": { "node": ">=18.0.0" }, - "version": "1.0.7", + "version": "1.0.8", "dependencies": { "@microsoft/tsdoc": "^0.15.0", "@playcanvas/eslint-config": "^1.7.4", From 90e51829649c8aaf82abfe508625ab24d4d30ae3 Mon Sep 17 00:00:00 2001 From: Mark Lundin Date: Mon, 16 Sep 2024 16:34:57 +0100 Subject: [PATCH 03/13] initial test --- package.json | 2 +- src/parsers/attribute-parser.js | 2 +- src/parsers/script-parser.js | 8 +-- src/utils/ts-utils.js | 109 ++++++++++++++++++++++++++++++++ test/fixtures/enum.valid.js | 2 +- 5 files changed, 116 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index f325b86..dca4f2a 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "mocha": "^10.6.0" }, "scripts": { - "test": "mocha test/**/*.test.js --timeout 10000", + "test": "mocha test/**/valid/enum.test.js --timeout 10000", "lint": "eslint ." }, "publishConfig": { diff --git a/src/parsers/attribute-parser.js b/src/parsers/attribute-parser.js index 670aee0..f97190d 100644 --- a/src/parsers/attribute-parser.js +++ b/src/parsers/attribute-parser.js @@ -170,7 +170,7 @@ export class AttributeParser { const serializer = !array && this.typeSerializerMap.get(type); if (serializer) { try { - value = serializer(node.initializer ?? node); + value = serializer(node.initializer ?? node, this.typeChecker); } catch (error) { errors.push(error); return; diff --git a/src/parsers/script-parser.js b/src/parsers/script-parser.js index 6907b33..2d7268c 100644 --- a/src/parsers/script-parser.js +++ b/src/parsers/script-parser.js @@ -5,7 +5,7 @@ import { AttributeParser } from './attribute-parser.js'; import { ParsingError } from './parsing-error.js'; import { hasTag } from '../utils/attribute-utils.js'; import { zipArrays } from '../utils/generic-utils.js'; -import { flatMapAnyNodes, getJSDocCommentRanges, parseArrayLiteral, parseBooleanNode, parseFloatNode, parseStringNode } from '../utils/ts-utils.js'; +import { flatMapAnyNodes, getJSDocCommentRanges, getLiteralValue, parseArrayLiteral, parseBooleanNode, parseFloatNode, parseStringNode } from '../utils/ts-utils.js'; /** * @typedef {object} Attribute @@ -77,9 +77,9 @@ const SUPPORTED_INITIALIZABLE_TYPE_NAMES = new Map([ ['Vec3', createNumberArgumentParser('Vec3', [0, 0, 0])], ['Vec4', createNumberArgumentParser('Vec4', [0, 0, 0, 0])], ['Color', createNumberArgumentParser('Color', [1, 1, 1, 1])], - ['number', parseFloatNode], - ['string', parseStringNode], - ['boolean', parseBooleanNode] + ['number', getLiteralValue], + ['string', getLiteralValue], + ['boolean', getLiteralValue] ]); /** diff --git a/src/utils/ts-utils.js b/src/utils/ts-utils.js index 833ebd2..40c3e80 100644 --- a/src/utils/ts-utils.js +++ b/src/utils/ts-utils.js @@ -461,6 +461,115 @@ export const parseBooleanNode = (node) => { return node.kind === ts.SyntaxKind.TrueKeyword; }; +function resolveIdentifier(node, typeChecker) { + const symbol = typeChecker.getSymbolAtLocation(node); + if (symbol && symbol.declarations) { + for (const declaration of symbol.declarations) { + if (ts.isVariableDeclaration(declaration) && declaration.initializer) { + return getLiteralValue(declaration.initializer, typeChecker); + } + // Handle other kinds of declarations if needed + } + } + return undefined; +} + +function resolvePropertyAccess(node, typeChecker) { + const symbol = typeChecker.getSymbolAtLocation(node); + if (symbol && symbol.declarations) { + for (const declaration of symbol.declarations) { + if (ts.isPropertyAssignment(declaration) && declaration.initializer) { + return getLiteralValue(declaration.initializer, typeChecker); + } + if (ts.isVariableDeclaration(declaration) && declaration.initializer) { + return getLiteralValue(declaration.initializer, typeChecker); + } + // Handle other kinds of declarations if needed + } + } + + // If symbol not found directly, attempt to resolve the object first + const objValue = getLiteralValue(node.expression, typeChecker); + if (objValue && typeof objValue === 'object') { + const propName = node.name.text; + return objValue[propName]; + } + + return undefined; +} + +function evaluatePrefixUnaryExpression(node, typeChecker) { + const operandValue = getLiteralValue(node.operand, typeChecker); + if (operandValue !== undefined) { + switch (node.operator) { + case ts.SyntaxKind.PlusToken: + return +operandValue; + case ts.SyntaxKind.MinusToken: + return -operandValue; + case ts.SyntaxKind.ExclamationToken: + return !operandValue; + case ts.SyntaxKind.TildeToken: + return ~operandValue; + } + } + return undefined; +} + +function handleObjectLiteral(node, typeChecker) { + const obj = {}; + node.properties.forEach(prop => { + if (ts.isPropertyAssignment(prop)) { + const key = prop.name.getText(); + const value = getLiteralValue(prop.initializer, typeChecker); + obj[key] = value; + } else if (ts.isShorthandPropertyAssignment(prop)) { + const key = prop.name.getText(); + const value = resolveIdentifier(prop.name, typeChecker); + obj[key] = value; + } + }); + return obj; +} + +// Modify getLiteralValue to handle ObjectLiteralExpression +export function getLiteralValue(node, typeChecker) { + if (!node) return undefined; + + if (ts.isLiteralExpression(node)) { + if (ts.isStringLiteral(node)) { + return node.text; + } + if (ts.isNumericLiteral(node)) { + return Number(node.text); + } + if (node.kind === ts.SyntaxKind.TrueKeyword) { + return true; + } + if (node.kind === ts.SyntaxKind.FalseKeyword) { + return false; + } + } + + switch (node.kind) { + case ts.SyntaxKind.NullKeyword: + return null; + case ts.SyntaxKind.ArrayLiteralExpression: + return (node).elements.map(element => getLiteralValue(element, typeChecker)); + case ts.SyntaxKind.ObjectLiteralExpression: + return handleObjectLiteral(node, typeChecker); + case ts.SyntaxKind.Identifier: + return resolveIdentifier(node, typeChecker); + case ts.SyntaxKind.PropertyAccessExpression: + return resolvePropertyAccess(node, typeChecker); + case ts.SyntaxKind.ParenthesizedExpression: + return getLiteralValue((node).expression, typeChecker); + case ts.SyntaxKind.PrefixUnaryExpression: + return evaluatePrefixUnaryExpression(node, typeChecker); + default: + return undefined; + } +} + /** * If the given node is a string literal, returns the parsed string. * @param {import('typescript').Node} node - The node to check diff --git a/test/fixtures/enum.valid.js b/test/fixtures/enum.valid.js index 049146d..251107f 100644 --- a/test/fixtures/enum.valid.js +++ b/test/fixtures/enum.valid.js @@ -44,7 +44,7 @@ class Example extends Script { * @attribute * @type {NumberEnum} */ - e; + e = NumberEnum.A; /** * @attribute From 876416dbdb80d2d4d5f1a10ea5c70b35e711db0c Mon Sep 17 00:00:00 2001 From: Mark Lundin Date: Mon, 16 Sep 2024 16:50:35 +0100 Subject: [PATCH 04/13] Fix type validation error in tag-utils.js --- src/utils/tag-utils.js | 44 ++++++++++++++++++++------------ test/fixtures/program.valid.js | 8 ++++-- test/tests/valid/program.test.js | 1 + 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/utils/tag-utils.js b/src/utils/tag-utils.js index ace641e..937d5e3 100644 --- a/src/utils/tag-utils.js +++ b/src/utils/tag-utils.js @@ -35,31 +35,41 @@ export function parseTag(input = '') { /** * Validates that a tag value matches the expected type - * @throws {Error} - If the tag value is not the expected type - * - * @param {String} value - The string representation of the value to type check - * @param {string} typeAnnotation - The expected type - * @param {import('@typescript/vfs').VirtualTypeScriptEnvironment} env - The environment to validate in - * @returns {boolean} - Whether the tag value is valid + * + * @param value - The value to be validated. + * @param typeAnnotation - The TypeScript type annotation as a string. + * @param env - The environment containing the language service and file creation utilities. + * @throws Will throw an error if the value does not conform to the typeAnnotation. + * @returns `true` if validation passes without type errors. */ export function validateTag(value, typeAnnotation, env) { - + const virtualFileName = "/___virtual__.ts"; const sourceText = `let a: ${typeAnnotation} = ${value};`; - env.createFile('/___virtual__.ts', sourceText); - // Get the program and check for semantic errors - const program = env.languageService.getProgram(); - const errors = program.getSemanticDiagnostics(); + // Create or overwrite the virtual file with the new source text + env.createFile(virtualFileName, sourceText); + + // Retrieve the language service from the environment + const languageService = env.languageService; + + // Fetch semantic diagnostics only for the virtual file + const errors = languageService.getSemanticDiagnostics(virtualFileName); - // Filter against the type errors we're concerned with - const typeErrors = errors.filter(error => error.category === 1 && error.code === 2322); + // Filter for type assignment errors (Error Code 2322: Type 'X' is not assignable to type 'Y') + const typeErrors = errors.filter( + error => error.code === 2322 && error.category === ts.DiagnosticCategory.Error + ); - // return first error - const typeError = typeErrors[0]; + // If any type error is found, throw an error with the diagnostic message + if (typeErrors.length > 0) { + // TypeScript's messageText can be a string or a DiagnosticMessageChain + const errorMessage = typeErrors[0].messageText instanceof ts.DiagnosticMessageChain + ? flattenDiagnosticMessageText(typeErrors[0].messageText, "\n") + : typeErrors[0].messageText.toString(); - if (typeError) { - throw new Error(`${typeError.messageText}`); + throw new Error(`Type Validation Error: ${errorMessage}`); } + // If no type errors are found, return true indicating successful validation return true; } diff --git a/test/fixtures/program.valid.js b/test/fixtures/program.valid.js index 2cb91ed..6ff593d 100644 --- a/test/fixtures/program.valid.js +++ b/test/fixtures/program.valid.js @@ -8,13 +8,17 @@ export const MyEnum = { value: 0 }; class Example extends Script { /** * @attribute - * @type {boolean} + * @precision 1 + * @type {number} */ - a = false; + a; initialize() { confetti(); new TWEEN.Tween({ x: 0 }).to({ x: 100 }, 1000).start(); + + // This is an intentional type error, but the parser should ignore these + this.a = 'string'; } } diff --git a/test/tests/valid/program.test.js b/test/tests/valid/program.test.js index 427b4d7..0d03ea3 100644 --- a/test/tests/valid/program.test.js +++ b/test/tests/valid/program.test.js @@ -13,5 +13,6 @@ describe('VALID: Program ', function () { expect(data).to.exist; expect(data[0]).to.not.be.empty; expect(data[1]).to.be.empty; + expect(data[0].example.errors).to.be.empty; }); }); From 54ed89cc5ea642d8c0835416130a8ebfd33ab9f5 Mon Sep 17 00:00:00 2001 From: Mark Lundin Date: Mon, 16 Sep 2024 17:22:02 +0100 Subject: [PATCH 05/13] Add TypeScript import and update parameter types in validateTag function --- src/utils/tag-utils.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/utils/tag-utils.js b/src/utils/tag-utils.js index 937d5e3..47f479e 100644 --- a/src/utils/tag-utils.js +++ b/src/utils/tag-utils.js @@ -1,3 +1,5 @@ +import { DiagnosticCategory, flattenDiagnosticMessageText } from 'typescript'; + import { parseNumber, parseStringToNumericalArray } from './ts-utils.js'; /** @@ -35,15 +37,15 @@ export function parseTag(input = '') { /** * Validates that a tag value matches the expected type - * - * @param value - The value to be validated. - * @param typeAnnotation - The TypeScript type annotation as a string. - * @param env - The environment containing the language service and file creation utilities. + * + * @param {String} value - The value to be validated. + * @param {String} typeAnnotation - The TypeScript type annotation as a string. + * @param {import('@typescript/vfs').VirtualTypeScriptEnvironment} env - The environment containing the language service and file creation utilities. * @throws Will throw an error if the value does not conform to the typeAnnotation. - * @returns `true` if validation passes without type errors. + * @returns {true} if validation passes without type errors. */ export function validateTag(value, typeAnnotation, env) { - const virtualFileName = "/___virtual__.ts"; + const virtualFileName = '/___virtual__.ts'; const sourceText = `let a: ${typeAnnotation} = ${value};`; // Create or overwrite the virtual file with the new source text @@ -57,16 +59,12 @@ export function validateTag(value, typeAnnotation, env) { // Filter for type assignment errors (Error Code 2322: Type 'X' is not assignable to type 'Y') const typeErrors = errors.filter( - error => error.code === 2322 && error.category === ts.DiagnosticCategory.Error + error => error.code === 2322 && error.category === DiagnosticCategory.Error ); // If any type error is found, throw an error with the diagnostic message if (typeErrors.length > 0) { - // TypeScript's messageText can be a string or a DiagnosticMessageChain - const errorMessage = typeErrors[0].messageText instanceof ts.DiagnosticMessageChain - ? flattenDiagnosticMessageText(typeErrors[0].messageText, "\n") - : typeErrors[0].messageText.toString(); - + const errorMessage = typeErrors[0].messageText.toString(); throw new Error(`Type Validation Error: ${errorMessage}`); } From 2df1d72c490a9338f6b8e9db8e7a8fbe1b31468d Mon Sep 17 00:00:00 2001 From: Mark Lundin Date: Mon, 16 Sep 2024 17:22:30 +0100 Subject: [PATCH 06/13] Remove unused import in tag-utils.js --- src/utils/tag-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/tag-utils.js b/src/utils/tag-utils.js index 47f479e..ba0b167 100644 --- a/src/utils/tag-utils.js +++ b/src/utils/tag-utils.js @@ -1,4 +1,4 @@ -import { DiagnosticCategory, flattenDiagnosticMessageText } from 'typescript'; +import { DiagnosticCategory } from 'typescript'; import { parseNumber, parseStringToNumericalArray } from './ts-utils.js'; From 4eed3974581a1faacaa961be69af8260e90af601 Mon Sep 17 00:00:00 2001 From: Mark Lundin Date: Wed, 18 Sep 2024 11:00:33 +0100 Subject: [PATCH 07/13] Finalizes support for enum ref --- package.json | 2 +- src/parsers/script-parser.js | 7 +++++++ src/utils/ts-utils.js | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index dca4f2a..f325b86 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "mocha": "^10.6.0" }, "scripts": { - "test": "mocha test/**/valid/enum.test.js --timeout 10000", + "test": "mocha test/**/*.test.js --timeout 10000", "lint": "eslint ." }, "publishConfig": { diff --git a/src/parsers/script-parser.js b/src/parsers/script-parser.js index 2d7268c..fdd3d84 100644 --- a/src/parsers/script-parser.js +++ b/src/parsers/script-parser.js @@ -187,6 +187,13 @@ const mapAttributesToOutput = (attribute) => { // remove enum if it's empty if (attribute.enum.length === 0) delete attribute.enum; + // If the attribute has no default value then set it + if (attribute.value === undefined) { + if(attribute.type === 'string') attribute.value = ''; + if(attribute.type === 'number') attribute.value = 0; + if(attribute.type === 'boolean') attribute.value = false; + } + // set the default value if (attribute.value !== undefined) attribute.default = attribute.value; diff --git a/src/utils/ts-utils.js b/src/utils/ts-utils.js index 40c3e80..06b4def 100644 --- a/src/utils/ts-utils.js +++ b/src/utils/ts-utils.js @@ -535,7 +535,7 @@ function handleObjectLiteral(node, typeChecker) { export function getLiteralValue(node, typeChecker) { if (!node) return undefined; - if (ts.isLiteralExpression(node)) { + if (ts.isLiteralExpression(node) || ts.isBooleanLiteral(node)) { if (ts.isStringLiteral(node)) { return node.text; } From 839f62aa03ce46c387246afda11d57b9480ac40c Mon Sep 17 00:00:00 2001 From: Mark Lundin Date: Wed, 18 Sep 2024 11:04:13 +0100 Subject: [PATCH 08/13] linting --- src/parsers/script-parser.js | 8 ++++---- src/utils/ts-utils.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/parsers/script-parser.js b/src/parsers/script-parser.js index fdd3d84..0197c0f 100644 --- a/src/parsers/script-parser.js +++ b/src/parsers/script-parser.js @@ -5,7 +5,7 @@ import { AttributeParser } from './attribute-parser.js'; import { ParsingError } from './parsing-error.js'; import { hasTag } from '../utils/attribute-utils.js'; import { zipArrays } from '../utils/generic-utils.js'; -import { flatMapAnyNodes, getJSDocCommentRanges, getLiteralValue, parseArrayLiteral, parseBooleanNode, parseFloatNode, parseStringNode } from '../utils/ts-utils.js'; +import { flatMapAnyNodes, getJSDocCommentRanges, getLiteralValue, parseArrayLiteral, parseFloatNode } from '../utils/ts-utils.js'; /** * @typedef {object} Attribute @@ -189,9 +189,9 @@ const mapAttributesToOutput = (attribute) => { // If the attribute has no default value then set it if (attribute.value === undefined) { - if(attribute.type === 'string') attribute.value = ''; - if(attribute.type === 'number') attribute.value = 0; - if(attribute.type === 'boolean') attribute.value = false; + if (attribute.type === 'string') attribute.value = ''; + if (attribute.type === 'number') attribute.value = 0; + if (attribute.type === 'boolean') attribute.value = false; } // set the default value diff --git a/src/utils/ts-utils.js b/src/utils/ts-utils.js index 06b4def..e5ab304 100644 --- a/src/utils/ts-utils.js +++ b/src/utils/ts-utils.js @@ -517,7 +517,7 @@ function evaluatePrefixUnaryExpression(node, typeChecker) { function handleObjectLiteral(node, typeChecker) { const obj = {}; - node.properties.forEach(prop => { + node.properties.forEach((prop) => { if (ts.isPropertyAssignment(prop)) { const key = prop.name.getText(); const value = getLiteralValue(prop.initializer, typeChecker); From ea7f2d5be79d12a750e1634ae600857afad63ae9 Mon Sep 17 00:00:00 2001 From: Mark Lundin Date: Wed, 18 Sep 2024 11:09:39 +0100 Subject: [PATCH 09/13] Refactor utility functions for TypeScript --- src/utils/ts-utils.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/utils/ts-utils.js b/src/utils/ts-utils.js index e5ab304..e8971a9 100644 --- a/src/utils/ts-utils.js +++ b/src/utils/ts-utils.js @@ -474,7 +474,15 @@ function resolveIdentifier(node, typeChecker) { return undefined; } -function resolvePropertyAccess(node, typeChecker) { +/** + * Resolve the value of a property access expression. Limited to simple cases like + * object literals and variable declarations. + * + * @param {import('typescript').Node} node - The property access expression node + * @param {import('typescript')} typeChecker - The TypeScript type checker + * @returns {any} - The resolved value of the property access + */ +const resolvePropertyAccess = (node, typeChecker) => { const symbol = typeChecker.getSymbolAtLocation(node); if (symbol && symbol.declarations) { for (const declaration of symbol.declarations) { @@ -498,7 +506,13 @@ function resolvePropertyAccess(node, typeChecker) { return undefined; } -function evaluatePrefixUnaryExpression(node, typeChecker) { +/** + * Evaluates unary prefixes like +, -, !, ~, and returns the result. + * @param {import('typescript').Node} node - The AST node to evaluate + * @param {import('typescript').TypeChecker} typeChecker - The TypeScript type checker + * @returns {number | boolean | undefined} - The result of the evaluation + */ +const evaluatePrefixUnaryExpression = (node, typeChecker) => { const operandValue = getLiteralValue(node.operand, typeChecker); if (operandValue !== undefined) { switch (node.operator) { @@ -531,7 +545,15 @@ function handleObjectLiteral(node, typeChecker) { return obj; } -// Modify getLiteralValue to handle ObjectLiteralExpression +/** + * Attempts to extract a literal value from a TypeScript node. This function + * supports various types of literals and expressions, including object literals, + * array literals, identifiers, and unary expressions. + * + * @param {import('typescript').Node} node - The AST node to evaluate + * @param {import('typescript').TypeChecker} typeChecker - The TypeScript type checker + * @returns {any} - The extracted literal value + */ export function getLiteralValue(node, typeChecker) { if (!node) return undefined; From c9327725882b6407cde8649b81b258f83b65aa78 Mon Sep 17 00:00:00 2001 From: Mark Lundin Date: Wed, 18 Sep 2024 11:11:16 +0100 Subject: [PATCH 10/13] linting --- src/utils/ts-utils.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/ts-utils.js b/src/utils/ts-utils.js index e8971a9..93e8aee 100644 --- a/src/utils/ts-utils.js +++ b/src/utils/ts-utils.js @@ -477,7 +477,7 @@ function resolveIdentifier(node, typeChecker) { /** * Resolve the value of a property access expression. Limited to simple cases like * object literals and variable declarations. - * + * * @param {import('typescript').Node} node - The property access expression node * @param {import('typescript')} typeChecker - The TypeScript type checker * @returns {any} - The resolved value of the property access @@ -504,7 +504,7 @@ const resolvePropertyAccess = (node, typeChecker) => { } return undefined; -} +}; /** * Evaluates unary prefixes like +, -, !, ~, and returns the result. @@ -527,7 +527,7 @@ const evaluatePrefixUnaryExpression = (node, typeChecker) => { } } return undefined; -} +}; function handleObjectLiteral(node, typeChecker) { const obj = {}; @@ -549,7 +549,7 @@ function handleObjectLiteral(node, typeChecker) { * Attempts to extract a literal value from a TypeScript node. This function * supports various types of literals and expressions, including object literals, * array literals, identifiers, and unary expressions. - * + * * @param {import('typescript').Node} node - The AST node to evaluate * @param {import('typescript').TypeChecker} typeChecker - The TypeScript type checker * @returns {any} - The extracted literal value From 4d36fdb7925ba8205865f192df5f3267beba68de Mon Sep 17 00:00:00 2001 From: Mark Lundin Date: Wed, 18 Sep 2024 13:02:12 +0100 Subject: [PATCH 11/13] 1.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1db1b46..3eee914 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@playcanvas/attribute-parser", - "version": "1.0.8", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@playcanvas/attribute-parser", - "version": "1.0.8", + "version": "1.1.0", "dependencies": { "@microsoft/tsdoc": "^0.15.0", "@playcanvas/eslint-config": "^1.7.4", diff --git a/package.json b/package.json index f325b86..1789894 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "engines": { "node": ">=18.0.0" }, - "version": "1.0.8", + "version": "1.1.0", "dependencies": { "@microsoft/tsdoc": "^0.15.0", "@playcanvas/eslint-config": "^1.7.4", From f235b9b4c5cf05115133722b13c59473a168f4cf Mon Sep 17 00:00:00 2001 From: Mark Lundin Date: Wed, 18 Sep 2024 16:45:36 +0100 Subject: [PATCH 12/13] Update attribute-parser.js and enum.valid.js --- src/parsers/attribute-parser.js | 18 ++++++++++-------- test/fixtures/enum.valid.js | 6 +++--- test/tests/valid/enum.test.js | 6 +++++- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/parsers/attribute-parser.js b/src/parsers/attribute-parser.js index f97190d..e8d7278 100644 --- a/src/parsers/attribute-parser.js +++ b/src/parsers/attribute-parser.js @@ -4,7 +4,7 @@ import * as ts from 'typescript'; import { ParsingError } from './parsing-error.js'; import { hasTag } from '../utils/attribute-utils.js'; import { parseTag, validateTag } from '../utils/tag-utils.js'; -import { extractTextFromDocNode, getLeadingBlockCommentRanges, getType } from '../utils/ts-utils.js'; +import { extractTextFromDocNode, getLeadingBlockCommentRanges, getLiteralValue, getType } from '../utils/ts-utils.js'; /** * A class to parse JSDoc comments and extract attribute metadata. @@ -260,14 +260,16 @@ export class AttributeParser { const node = property.initializer; + value = getLiteralValue(node, this.typeChecker); + // Enums can only contain primitives (string|number|boolean) - if (ts.isNumericLiteral(node)) { - value = parseFloat(node.getText()); - } else if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) { - value = node.kind === ts.SyntaxKind.TrueKeyword; - } else { - value = node.getText(); - } + // if (ts.isNumericLiteral(node)) { + // value = parseFloat(node.getText()); + // } else if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) { + // value = node.kind === ts.SyntaxKind.TrueKeyword; + // } else { + // value = node.getText(); + // } members.push({ [name]: value }); } diff --git a/test/fixtures/enum.valid.js b/test/fixtures/enum.valid.js index 251107f..7cb11e3 100644 --- a/test/fixtures/enum.valid.js +++ b/test/fixtures/enum.valid.js @@ -4,9 +4,9 @@ import { Script, Vec3 } from 'playcanvas'; * @enum {number} */ const NumberEnum = { - A: 0, - B: 1, - C: 2 + A: 13, + B: 14, + C: 23 }; /** diff --git a/test/tests/valid/enum.test.js b/test/tests/valid/enum.test.js index bcb113d..696574f 100644 --- a/test/tests/valid/enum.test.js +++ b/test/tests/valid/enum.test.js @@ -26,7 +26,7 @@ describe('VALID: Enum attribute', function () { expect(data[0].example.attributes.e.name).to.equal('e'); expect(data[0].example.attributes.e.type).to.equal('number'); expect(data[0].example.attributes.e.array).to.equal(false); - expect(data[0].example.attributes.e.default).to.equal(0); + expect(data[0].example.attributes.e.default).to.equal(13); }); it('f: should be a enum attribute with a default value', function () { @@ -51,6 +51,10 @@ describe('VALID: Enum attribute', function () { expect(data[0].example.attributes.h.name).to.equal('h'); expect(data[0].example.attributes.h.type).to.equal('string'); expect(data[0].example.attributes.h.array).to.equal(false); + expect(data[0].example.attributes.h.enum).to.be.an('array').with.lengthOf(3) + expect(data[0].example.attributes.h.enum[0]).to.deep.equal({ A: 'a' }); + expect(data[0].example.attributes.h.enum[1]).to.deep.equal({ B: 'b' }); + expect(data[0].example.attributes.h.enum[2]).to.deep.equal({ C: 'c' }); expect(data[0].example.attributes.h.default).to.equal(''); }); From 830c651bff0a2e855df669482102244cedaba4dc Mon Sep 17 00:00:00 2001 From: Mark Lundin Date: Wed, 18 Sep 2024 16:55:19 +0100 Subject: [PATCH 13/13] linting --- src/parsers/attribute-parser.js | 16 +--------------- test/tests/valid/enum.test.js | 2 +- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/parsers/attribute-parser.js b/src/parsers/attribute-parser.js index e8d7278..2531f80 100644 --- a/src/parsers/attribute-parser.js +++ b/src/parsers/attribute-parser.js @@ -256,21 +256,7 @@ export class AttributeParser { if (ts.isPropertyAssignment(property)) { const name = property.name && ts.isIdentifier(property.name) && property.name.text; - let value; - - const node = property.initializer; - - value = getLiteralValue(node, this.typeChecker); - - // Enums can only contain primitives (string|number|boolean) - // if (ts.isNumericLiteral(node)) { - // value = parseFloat(node.getText()); - // } else if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) { - // value = node.kind === ts.SyntaxKind.TrueKeyword; - // } else { - // value = node.getText(); - // } - + const value = getLiteralValue(property.initializer, this.typeChecker); members.push({ [name]: value }); } }); diff --git a/test/tests/valid/enum.test.js b/test/tests/valid/enum.test.js index 696574f..656c2f7 100644 --- a/test/tests/valid/enum.test.js +++ b/test/tests/valid/enum.test.js @@ -51,7 +51,7 @@ describe('VALID: Enum attribute', function () { expect(data[0].example.attributes.h.name).to.equal('h'); expect(data[0].example.attributes.h.type).to.equal('string'); expect(data[0].example.attributes.h.array).to.equal(false); - expect(data[0].example.attributes.h.enum).to.be.an('array').with.lengthOf(3) + expect(data[0].example.attributes.h.enum).to.be.an('array').with.lengthOf(3); expect(data[0].example.attributes.h.enum[0]).to.deep.equal({ A: 'a' }); expect(data[0].example.attributes.h.enum[1]).to.deep.equal({ B: 'b' }); expect(data[0].example.attributes.h.enum[2]).to.deep.equal({ C: 'c' });