Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Support for Enum refs #15

Merged
merged 5 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/parsers/attribute-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
15 changes: 11 additions & 4 deletions src/parsers/script-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, parseFloatNode } from '../utils/ts-utils.js';

/**
* @typedef {object} Attribute
Expand Down Expand Up @@ -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]
]);

/**
Expand Down Expand Up @@ -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;

Expand Down
131 changes: 131 additions & 0 deletions src/utils/ts-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,137 @@ 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;
}

/**
* 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) {
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;
};

/**
* 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) {
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;
}

/**
* 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;

if (ts.isLiteralExpression(node) || ts.isBooleanLiteral(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
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/enum.valid.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class Example extends Script {
* @attribute
* @type {NumberEnum}
*/
e;
e = NumberEnum.A;

/**
* @attribute
Expand Down