@playcanvas/attribute-parser 1.10.1 → 1.10.2
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/README.md +9 -0
- package/dist/index.js +2 -2
- package/package.json +16 -15
package/README.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# PlayCanvas Attribute Parser
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@playcanvas/attribute-parser)
|
|
4
|
+
[](https://npmtrends.com/@playcanvas/attribute-parser)
|
|
5
|
+
[](https://github.com/playcanvas/attribute-parser/blob/main/LICENSE)
|
|
6
|
+
[](https://discord.gg/RSaMRzg)
|
|
7
|
+
[](https://www.reddit.com/r/PlayCanvas)
|
|
8
|
+
[](https://x.com/intent/follow?screen_name=playcanvas)
|
|
9
|
+
|
|
10
|
+
| [User Manual](https://developer.playcanvas.com) | [API Reference](https://api.playcanvas.com) | [Blog](https://blog.playcanvas.com) | [Forum](https://forum.playcanvas.com) |
|
|
11
|
+
|
|
3
12
|
This is the official JSDoc attribute parser used in the PlayCanvas Editor.
|
|
4
13
|
|
|
5
14
|
It collects metadata from user scripts by parsing `@attribute` JSDoc annotations. These attributes enable the PlayCanvas Editor to expose UI controls and contextual information for your script properties.
|
package/dist/index.js
CHANGED
|
@@ -421,11 +421,11 @@ class ParsingError{constructor(node,type,message,fix,scriptName,attributeName){t
|
|
|
421
421
|
|
|
422
422
|
const getTag=(tag,node)=>{for(const jsDoc of node?.jsDoc||[]){for(const tagNode of jsDoc.tags||[]){if(tagNode.tagName.text===tag){return tagNode}}}return null};const hasTag=(tag,node)=>{return !!getTag(tag,node)};const isStaticMember=member=>(typescriptExports.getCombinedModifierFlags(member)&typescriptExports.ModifierFlags.Static)!==0;
|
|
423
423
|
|
|
424
|
-
function createDefaultMapFromCDN(options,prefix,ts){const fsMap=new Map;const files=knownLibFilesForCompilerOptions(options,ts);async function uncached(){const contentPromises=files.map(lib=>{return fetch(prefix+lib).then(resp=>{if(!resp.ok){throw new Error(`Failed to fetch ${prefix+lib}: ${resp.statusText}`)}return resp.text()})});const contents=await Promise.all(contentPromises);contents.forEach((text,index)=>fsMap.set(`/${files[index]}`,text));return fsMap}return uncached()}function resolveAliasedSymbol(typeChecker,symbol){while(symbol.flags&typescriptExports.SymbolFlags.Alias){symbol=typeChecker.getAliasedSymbol(symbol);}return symbol}function getExportedNodes(program,sourceFile){if(!program||!sourceFile){return []}const typeChecker=program.getTypeChecker();const moduleSymbol=typeChecker.getSymbolAtLocation(sourceFile);if(!moduleSymbol){return []}const exportedSymbols=typeChecker.getExportsOfModule(moduleSymbol);const exportedNodes=new Map;exportedSymbols.forEach(symbol=>{const resolvedSymbol=resolveAliasedSymbol(typeChecker,symbol);if(resolvedSymbol.declarations){const node=resolvedSymbol.declarations[0];const name=symbol.name==="default"?node.name.getText():symbol.name;exportedNodes.set(name,node);}});return exportedNodes}function isAliasedClassDeclaration(node,typeChecker){if(typescriptExports.isClassDeclaration(node)){return true}if(node?.name&&typescriptExports.isIdentifier(node.name)){const symbol=typeChecker.getSymbolAtLocation(node.name);if(symbol){const resolvedSymbol=resolveAliasedSymbol(typeChecker,symbol);if(resolvedSymbol.declarations){return resolvedSymbol.declarations.some(typescriptExports.isClassDeclaration)}}}return false}function inheritsFrom(node,typeChecker,baseClassSymbol){if(node.heritageClauses){for(const clause of node.heritageClauses){for(const type of clause.types){const symbol=typeChecker.getTypeAtLocation(type.expression).getSymbol();if(symbol===baseClassSymbol){return true}if(symbol?.declarations.length>0){const declaration=symbol.declarations[0];if(inheritsFrom(declaration,typeChecker,baseClassSymbol)){return true}}}}}return false}function flatMapAnyNodes(node,filter=()=>true){const results=[];function visit(node){if(filter(node)){results.push(node);}typescriptExports.forEachChild(node,visit);}visit(node);return results}function getSuperClasses(node,typeChecker){const superClasses=[node];let currentClass=node;while(currentClass){const heritageClauses=currentClass.heritageClauses;if(!heritageClauses){break}const extendsClause=heritageClauses.find(clause=>clause.token===typescriptExports.SyntaxKind.ExtendsKeyword);if(!extendsClause||extendsClause.types.length===0){break}const superClassType=typeChecker.getTypeAtLocation(extendsClause.types[0]);const superClassSymbol=superClassType.getSymbol();if(!superClassSymbol){break}const superClassDeclaration=superClassSymbol.declarations.find(typescriptExports.isClassDeclaration);if(!superClassDeclaration){break}superClasses.push(superClassDeclaration);currentClass=superClassDeclaration;}return superClasses}function getJSDocTags(node,typeChecker){const tags=[];if(typescriptExports.isClassDeclaration(node)||typescriptExports.isInterfaceDeclaration(node)){const heritageChain=getSuperClasses(node,typeChecker).filter(classNode=>!classNode.getSourceFile().isDeclarationFile);heritageChain.forEach(classNode=>{classNode.members.forEach(member=>{const isSetter=typescriptExports.isSetAccessor(member);if(typescriptExports.isPropertyDeclaration(member)||isSetter||typescriptExports.isPropertySignature(member)){const memberName=member.name&&typescriptExports.isIdentifier(member.name)?member.name.text:"unnamed";let hasJsDoc=member.jsDoc?.length>0;if(isSetter&&!hasJsDoc){const symbol=typeChecker.getSymbolAtLocation(member.name);member=symbol.declarations.find(accessor=>accessor.jsDoc?.length>0);hasJsDoc=!!member;}if(hasJsDoc){tags.push({memberName,member});}}});});}else if(typescriptExports.isObjectLiteralExpression(node)){node.properties.forEach(property=>{if(typescriptExports.isPropertyAssignment(property)){const memberName=property.name&&(typescriptExports.isIdentifier(property.name)||typescriptExports.isStringLiteral(property.name))?property.name.text:"unnamed";if(property.jsDoc?.length>0){tags.push({memberName,member:property});}}});}return tags}function getPrimitiveEnumType(type,typeChecker){if(!type.symbol?.declarations?.some(decl=>typescriptExports.isEnumDeclaration(decl)))return null;const enumMembers=type.symbol.declarations[0].members;const firstMemberValue=typeChecker.getConstantValue(enumMembers[0]);const validEnumType=["number","string","boolean"];const typeOf=typeof firstMemberValue;return validEnumType.includes(typeOf)?typeOf:null}function getType(node,typeChecker){if(node?.name?.kind===typescriptExports.SyntaxKind.Identifier){const type=typeChecker.getTypeAtLocation(node);const array=typeChecker.isArrayType(type);const actualType=array?typeChecker.getElementTypeOfArrayType(type):type;const baseType=typeChecker.getBaseTypeOfLiteralType(actualType);let isMixedUnion=false;if(Array.isArray(baseType.types)){const primitiveKinds=new Set(baseType.types.map(t=>typeChecker.getBaseTypeOfLiteralType(t)));primitiveKinds.delete(null);isMixedUnion=primitiveKinds.size>1;}const name=getPrimitiveEnumType(baseType,typeChecker)??typeChecker.typeToString(baseType);return {type:baseType,name,array,isMixedUnion}}return null}function parseNumber(input){let cleanedInput=input.replace(/_/g,"").toLowerCase();let unaryOperator=null;if(cleanedInput.startsWith("+")||cleanedInput.startsWith("-")||cleanedInput.startsWith("~")){unaryOperator=cleanedInput[0];cleanedInput=cleanedInput.slice(1).trim();}let result;if(cleanedInput==="NaN"||cleanedInput===""){result=NaN;}else if(cleanedInput.startsWith("0b")){result=parseInt(cleanedInput.slice(2),2);}else if(cleanedInput.startsWith("0o")){result=parseInt(cleanedInput.slice(2),8);}else if(cleanedInput.startsWith("0x")){result=parseInt(cleanedInput.slice(2),16);}else if(cleanedInput.includes("e")){result=parseFloat(cleanedInput);}else if(!isNaN(Number(cleanedInput))){result=Number(cleanedInput);}else {result=NaN;}if(unaryOperator==="+"){result=+result;}else if(unaryOperator==="-"){result=-result;}else if(unaryOperator==="~"){result=~result;}return result}function parseStringToNumericalArray(input){const sanitizedInput=input.replace(/\s+/g,"");if(!sanitizedInput.startsWith("[")||!sanitizedInput.endsWith("]")){throw new Error("Invalid input string")}const arrayString=sanitizedInput.slice(1,-1);const elements=arrayString.split(",");const parsedArray=elements.map(element=>{return parseNumber(element.trim())});if(parsedArray.some(isNaN)){throw new Error("Parsed result contains invalid numbers")}return parsedArray}const isNumericLiteral=node=>{const isNumericLiteral=typescriptExports.isNumericLiteral(node);const isUnaryExpression=typescriptExports.isUnaryExpression(node)&&(node.operator===typescriptExports.SyntaxKind.MinusToken||node.operator===typescriptExports.SyntaxKind.PlusToken)&&node.operand.kind===typescriptExports.SyntaxKind.NumericLiteral;return isNumericLiteral||isUnaryExpression};const parseFloatNode=(node,isArgument=false)=>{if(!isNumericLiteral(node)){if(typescriptExports.isPropertyAssignment(node.parent)||typescriptExports.isPropertyDeclaration(node.parent)||isArgument){throw new ParsingError(node,"Not a Literal",`"${node.parent.getText()}" must be defined with a numeric literal.`)}return 0}return parseNumber(node.getText())};function resolveIdentifier(node,typeChecker){const symbol=typeChecker.getSymbolAtLocation(node);if(symbol&&symbol.declarations){for(const declaration of symbol.declarations){if(typescriptExports.isVariableDeclaration(declaration)&&declaration.initializer){return getLiteralValue(declaration.initializer,typeChecker)}}}return undefined}const resolvePropertyAccess=(node,typeChecker)=>{const symbol=typeChecker.getSymbolAtLocation(node);if(symbol&&symbol.declarations){for(const declaration of symbol.declarations){if(typescriptExports.isPropertyAssignment(declaration)&&declaration.initializer){return getLiteralValue(declaration.initializer,typeChecker)}if(typescriptExports.isVariableDeclaration(declaration)&&declaration.initializer){return getLiteralValue(declaration.initializer,typeChecker)}if(typescriptExports.isEnumMember(declaration)){return declaration.initializer?getLiteralValue(declaration.initializer,typeChecker):declaration.name.getText()}}}const objValue=getLiteralValue(node.expression,typeChecker);if(objValue&&typeof objValue==="object"){const propName=node.name.text;return objValue[propName]}return undefined};const evaluatePrefixUnaryExpression=(node,typeChecker)=>{const operandValue=getLiteralValue(node.operand,typeChecker);if(operandValue!==undefined){switch(node.operator){case typescriptExports.SyntaxKind.PlusToken:return +operandValue;case typescriptExports.SyntaxKind.MinusToken:return -operandValue;case typescriptExports.SyntaxKind.ExclamationToken:return !operandValue;case typescriptExports.SyntaxKind.TildeToken:return ~operandValue}}return undefined};function handleObjectLiteral(node,typeChecker){const obj={};node.properties.forEach(prop=>{if(typescriptExports.isPropertyAssignment(prop)){const key=prop.name.getText();const value=getLiteralValue(prop.initializer,typeChecker);obj[key]=value;}else if(typescriptExports.isShorthandPropertyAssignment(prop)){const key=prop.name.getText();const value=resolveIdentifier(prop.name,typeChecker);obj[key]=value;}});return obj}function getLiteralValue(node,typeChecker){if(!node)return undefined;if(typescriptExports.isLiteralExpression(node)||typescriptExports.isBooleanLiteral(node)){if(typescriptExports.isStringLiteral(node)){return node.text}if(typescriptExports.isNumericLiteral(node)){return Number(node.text)}if(node.kind===typescriptExports.SyntaxKind.TrueKeyword){return true}if(node.kind===typescriptExports.SyntaxKind.FalseKeyword){return false}}switch(node.kind){case typescriptExports.SyntaxKind.NullKeyword:return null;case typescriptExports.SyntaxKind.ArrayLiteralExpression:return node.elements.map(element=>getLiteralValue(element,typeChecker));case typescriptExports.SyntaxKind.ObjectLiteralExpression:return handleObjectLiteral(node,typeChecker);case typescriptExports.SyntaxKind.Identifier:return resolveIdentifier(node,typeChecker);case typescriptExports.SyntaxKind.PropertyAccessExpression:return resolvePropertyAccess(node,typeChecker);case typescriptExports.SyntaxKind.ParenthesizedExpression:return getLiteralValue(node.expression,typeChecker);case typescriptExports.SyntaxKind.PrefixUnaryExpression:return evaluatePrefixUnaryExpression(node,typeChecker);default:return undefined}}const parseArrayLiteralNode=(node,isArgument=false)=>{if(node.kind!==typescriptExports.SyntaxKind.ArrayLiteralExpression){if(typescriptExports.isPropertyAssignment(node.parent)||typescriptExports.isPropertyDeclaration(node.parent)||isArgument){throw new ParsingError(node,"Not a Literal",`"${node.parent.getText()}" must be defined with an array literal.`)}return []}return JSON.parse(node.getText())};const parseArrayLiteral=args=>{if(args.length>0){return parseArrayLiteralNode(args[0],true)}return []};
|
|
424
|
+
function createDefaultMapFromCDN(options,prefix,ts){const fsMap=new Map;const files=knownLibFilesForCompilerOptions(options,ts);async function uncached(){const contentPromises=files.map(lib=>{return fetch(prefix+lib).then(resp=>{if(!resp.ok){throw new Error(`Failed to fetch ${prefix+lib}: ${resp.statusText}`)}return resp.text()})});const contents=await Promise.all(contentPromises);contents.forEach((text,index)=>fsMap.set(`/${files[index]}`,text));return fsMap}return uncached()}function resolveAliasedSymbol(typeChecker,symbol){while(symbol.flags&typescriptExports.SymbolFlags.Alias){symbol=typeChecker.getAliasedSymbol(symbol);}return symbol}function getExportedNodes(program,sourceFile){if(!program||!sourceFile){return []}const typeChecker=program.getTypeChecker();const moduleSymbol=typeChecker.getSymbolAtLocation(sourceFile);if(!moduleSymbol){return []}const exportedSymbols=typeChecker.getExportsOfModule(moduleSymbol);const exportedNodes=new Map;exportedSymbols.forEach(symbol=>{const resolvedSymbol=resolveAliasedSymbol(typeChecker,symbol);if(resolvedSymbol.declarations){const node=resolvedSymbol.declarations[0];const name=symbol.name==="default"?node.name.getText():symbol.name;exportedNodes.set(name,node);}});return exportedNodes}function isAliasedClassDeclaration(node,typeChecker){if(typescriptExports.isClassDeclaration(node)){return true}if(node?.name&&typescriptExports.isIdentifier(node.name)){const symbol=typeChecker.getSymbolAtLocation(node.name);if(symbol){const resolvedSymbol=resolveAliasedSymbol(typeChecker,symbol);if(resolvedSymbol.declarations){return resolvedSymbol.declarations.some(typescriptExports.isClassDeclaration)}}}return false}function inheritsFrom(node,typeChecker,baseClassSymbol){if(node.heritageClauses){for(const clause of node.heritageClauses){for(const type of clause.types){const symbol=typeChecker.getTypeAtLocation(type.expression).getSymbol();if(symbol===baseClassSymbol){return true}if(symbol?.declarations.length>0){const declaration=symbol.declarations[0];if(inheritsFrom(declaration,typeChecker,baseClassSymbol)){return true}}}}}return false}function flatMapAnyNodes(node,filter=()=>true){const results=[];function visit(node){if(filter(node)){results.push(node);}typescriptExports.forEachChild(node,visit);}visit(node);return results}function getSuperClasses(node,typeChecker){const superClasses=[node];let currentClass=node;while(currentClass){const heritageClauses=currentClass.heritageClauses;if(!heritageClauses){break}const extendsClause=heritageClauses.find(clause=>clause.token===typescriptExports.SyntaxKind.ExtendsKeyword);if(!extendsClause||extendsClause.types.length===0){break}const superClassType=typeChecker.getTypeAtLocation(extendsClause.types[0]);const superClassSymbol=superClassType.getSymbol();if(!superClassSymbol){break}const superClassDeclaration=superClassSymbol.declarations.find(typescriptExports.isClassDeclaration);if(!superClassDeclaration){break}superClasses.push(superClassDeclaration);currentClass=superClassDeclaration;}return superClasses}function getJSDocTags(node,typeChecker){const tags=[];if(typescriptExports.isClassDeclaration(node)||typescriptExports.isInterfaceDeclaration(node)){const heritageChain=getSuperClasses(node,typeChecker).filter(classNode=>!classNode.getSourceFile().isDeclarationFile);heritageChain.forEach(classNode=>{classNode.members.forEach(member=>{const isSetter=typescriptExports.isSetAccessor(member);if(typescriptExports.isPropertyDeclaration(member)||isSetter||typescriptExports.isPropertySignature(member)){const memberName=member.name&&typescriptExports.isIdentifier(member.name)?member.name.text:"unnamed";let hasJsDoc=member.jsDoc?.length>0;if(isSetter&&!hasJsDoc){const symbol=typeChecker.getSymbolAtLocation(member.name);member=symbol.declarations.find(accessor=>accessor.jsDoc?.length>0);hasJsDoc=!!member;}if(hasJsDoc){tags.push({memberName,member});}}});});}else if(typescriptExports.isObjectLiteralExpression(node)){node.properties.forEach(property=>{if(typescriptExports.isPropertyAssignment(property)){const memberName=property.name&&(typescriptExports.isIdentifier(property.name)||typescriptExports.isStringLiteral(property.name))?property.name.text:"unnamed";if(property.jsDoc?.length>0){tags.push({memberName,member:property});}}});}return tags}function getPrimitiveEnumType(type,typeChecker){const isEnumType=type.symbol?.declarations?.some(decl=>typescriptExports.isEnumDeclaration(decl));let isEnumMember=false;if(!isEnumType){isEnumMember=type.symbol?.declarations?.some(decl=>typescriptExports.isEnumMember(decl));if(!isEnumMember)return null}let enumMembers;if(isEnumType){enumMembers=type.symbol.declarations[0].members;}else if(isEnumMember){const enumMember=type.symbol.declarations[0];const parentEnum=enumMember.parent;if(typescriptExports.isEnumDeclaration(parentEnum)){enumMembers=parentEnum.members;}else {return null}}const firstMember=enumMembers[0];const firstMemberValue=firstMember.initializer?getLiteralValue(firstMember.initializer,typeChecker):getLiteralValue(firstMember,typeChecker);const validEnumTypes=["number","string","boolean"];const typeOf=typeof firstMemberValue;return validEnumTypes.includes(typeOf)?typeOf:null}function getType(node,typeChecker){if(node?.name?.kind===typescriptExports.SyntaxKind.Identifier){const type=typeChecker.getTypeAtLocation(node);const array=typeChecker.isArrayType(type);const actualType=array?typeChecker.getElementTypeOfArrayType(type):type;const baseType=typeChecker.getBaseTypeOfLiteralType(actualType);let isMixedUnion=false;if(Array.isArray(baseType.types)){const primitiveKinds=new Set(baseType.types.map(t=>typeChecker.getBaseTypeOfLiteralType(t)));primitiveKinds.delete(null);isMixedUnion=primitiveKinds.size>1;}const name=getPrimitiveEnumType(baseType,typeChecker)??typeChecker.typeToString(baseType);return {type:baseType,name,array,isMixedUnion}}return null}function parseNumber(input){let cleanedInput=input.replace(/_/g,"").toLowerCase();let unaryOperator=null;if(cleanedInput.startsWith("+")||cleanedInput.startsWith("-")||cleanedInput.startsWith("~")){unaryOperator=cleanedInput[0];cleanedInput=cleanedInput.slice(1).trim();}let result;if(cleanedInput==="NaN"||cleanedInput===""){result=NaN;}else if(cleanedInput.startsWith("0b")){result=parseInt(cleanedInput.slice(2),2);}else if(cleanedInput.startsWith("0o")){result=parseInt(cleanedInput.slice(2),8);}else if(cleanedInput.startsWith("0x")){result=parseInt(cleanedInput.slice(2),16);}else if(cleanedInput.includes("e")){result=parseFloat(cleanedInput);}else if(!isNaN(Number(cleanedInput))){result=Number(cleanedInput);}else {result=NaN;}if(unaryOperator==="+"){result=+result;}else if(unaryOperator==="-"){result=-result;}else if(unaryOperator==="~"){result=~result;}return result}function parseStringToNumericalArray(input){const sanitizedInput=input.replace(/\s+/g,"");if(!sanitizedInput.startsWith("[")||!sanitizedInput.endsWith("]")){throw new Error("Invalid input string")}const arrayString=sanitizedInput.slice(1,-1);const elements=arrayString.split(",");const parsedArray=elements.map(element=>{return parseNumber(element.trim())});if(parsedArray.some(isNaN)){throw new Error("Parsed result contains invalid numbers")}return parsedArray}const isNumericLiteral=node=>{const isNumericLiteral=typescriptExports.isNumericLiteral(node);const isUnaryExpression=typescriptExports.isUnaryExpression(node)&&(node.operator===typescriptExports.SyntaxKind.MinusToken||node.operator===typescriptExports.SyntaxKind.PlusToken)&&node.operand.kind===typescriptExports.SyntaxKind.NumericLiteral;return isNumericLiteral||isUnaryExpression};const parseFloatNode=(node,isArgument=false)=>{if(!isNumericLiteral(node)){if(typescriptExports.isPropertyAssignment(node.parent)||typescriptExports.isPropertyDeclaration(node.parent)||isArgument){throw new ParsingError(node,"Not a Literal",`"${node.parent.getText()}" must be defined with a numeric literal.`)}return 0}return parseNumber(node.getText())};function resolveIdentifier(node,typeChecker){const symbol=typeChecker.getSymbolAtLocation(node);if(symbol&&symbol.declarations){for(const declaration of symbol.declarations){if(typescriptExports.isVariableDeclaration(declaration)&&declaration.initializer){return getLiteralValue(declaration.initializer,typeChecker)}}}return undefined}const resolvePropertyAccess=(node,typeChecker)=>{const symbol=typeChecker.getSymbolAtLocation(node);if(symbol&&symbol.declarations){for(const declaration of symbol.declarations){if(typescriptExports.isPropertyAssignment(declaration)&&declaration.initializer){return getLiteralValue(declaration.initializer,typeChecker)}if(typescriptExports.isVariableDeclaration(declaration)&&declaration.initializer){return getLiteralValue(declaration.initializer,typeChecker)}if(typescriptExports.isEnumMember(declaration)){return declaration.initializer?getLiteralValue(declaration.initializer,typeChecker):declaration.name.getText()}}}const result=getLiteralValue(node,typeChecker);if(result!==undefined){return result}const objValue=getLiteralValue(node.expression,typeChecker);if(objValue&&typeof objValue==="object"){const propName=node.name.text;return objValue[propName]}return undefined};const evaluatePrefixUnaryExpression=(node,typeChecker)=>{const operandValue=getLiteralValue(node.operand,typeChecker);if(operandValue!==undefined){switch(node.operator){case typescriptExports.SyntaxKind.PlusToken:return +operandValue;case typescriptExports.SyntaxKind.MinusToken:return -operandValue;case typescriptExports.SyntaxKind.ExclamationToken:return !operandValue;case typescriptExports.SyntaxKind.TildeToken:return ~operandValue}}return undefined};function handleObjectLiteral(node,typeChecker){const obj={};node.properties.forEach(prop=>{if(typescriptExports.isPropertyAssignment(prop)){const key=prop.name.getText();const value=getLiteralValue(prop.initializer,typeChecker);obj[key]=value;}else if(typescriptExports.isShorthandPropertyAssignment(prop)){const key=prop.name.getText();const value=resolveIdentifier(prop.name,typeChecker);obj[key]=value;}});return obj}function getLiteralValue(node,typeChecker){if(!node)return undefined;if(typeChecker){const type=typeChecker.getTypeAtLocation(node);if(type&&type.isLiteral()){return type.value}}if(typescriptExports.isLiteralExpression(node)||typescriptExports.isBooleanLiteral(node)){if(typescriptExports.isStringLiteral(node)){return node.text}if(typescriptExports.isNumericLiteral(node)){return Number(node.text)}if(node.kind===typescriptExports.SyntaxKind.TrueKeyword){return true}if(node.kind===typescriptExports.SyntaxKind.FalseKeyword){return false}}switch(node.kind){case typescriptExports.SyntaxKind.NullKeyword:return null;case typescriptExports.SyntaxKind.ArrayLiteralExpression:return node.elements.map(element=>getLiteralValue(element,typeChecker));case typescriptExports.SyntaxKind.ObjectLiteralExpression:return handleObjectLiteral(node,typeChecker);case typescriptExports.SyntaxKind.Identifier:return resolveIdentifier(node,typeChecker);case typescriptExports.SyntaxKind.PropertyAccessExpression:return resolvePropertyAccess(node,typeChecker);case typescriptExports.SyntaxKind.ParenthesizedExpression:return getLiteralValue(node.expression,typeChecker);case typescriptExports.SyntaxKind.PrefixUnaryExpression:return evaluatePrefixUnaryExpression(node,typeChecker);default:return undefined}}const parseArrayLiteralNode=(node,isArgument=false)=>{if(node.kind!==typescriptExports.SyntaxKind.ArrayLiteralExpression){if(typescriptExports.isPropertyAssignment(node.parent)||typescriptExports.isPropertyDeclaration(node.parent)||isArgument){throw new ParsingError(node,"Not a Literal",`"${node.parent.getText()}" must be defined with an array literal.`)}return []}return JSON.parse(node.getText())};const parseArrayLiteral=args=>{if(args.length>0){return parseArrayLiteralNode(args[0],true)}return []};
|
|
425
425
|
|
|
426
426
|
function parseTag(input=""){input=input.replace(/['`]/g,'"').trim();if(input==="true")return true;if(input==="false")return false;const num=parseNumber(input);if(!isNaN(num))return num;try{const arr=parseStringToNumericalArray(input);if(Array.isArray(arr))return arr}catch{}try{const arr=JSON.parse(input);if(Array.isArray(arr))return arr}catch{}return input}function validateTag(value,typeAnnotation,env){const virtualFileName="/___virtual__.ts";const sourceText=`let a: ${typeAnnotation} = ${value};`;env.createFile(virtualFileName,sourceText);const languageService=env.languageService;const errors=languageService.getSemanticDiagnostics(virtualFileName);const typeErrors=errors.filter(error=>error.code===2322&&error.category===typescriptExports.DiagnosticCategory.Error);if(typeErrors.length>0){const errorMessage=typeErrors[0].messageText.toString();throw new Error(`Type Validation Error: ${errorMessage}`)}return true}
|
|
427
427
|
|
|
428
|
-
class AttributeParser{constructor(tags=new Map,env,typeSerializerMap){this.env=env;this.tagTypeAnnotations=tags;this.typeSerializerMap=typeSerializerMap;this.program=env.languageService.getProgram();this.typeChecker=this.program.getTypeChecker();}parseAttributeComment(node,errors=[],requiresAttributeTag=true,scriptName,attributeName){if(!requiresAttributeTag||hasTag("attribute",node)){const attribute=this.getNodeAsAttribute(node,errors,scriptName);if(!attribute)return;attribute.description=node.jsDoc?.[0]?.comment||"";const jsDocTags=node.jsDoc?.[0]?.tags??[];jsDocTags.forEach(tag=>{const tagName=tag.tagName.text;let commentText=(tag.comment?.split("\n")[0]||"").trim();if(this.tagTypeAnnotations.has(tagName)){const{type,supportMessage,fix}=this.tagTypeAnnotations.get(tagName);try{const value=parseTag(commentText);if(typeof value==="string")commentText=`"${commentText}"`;validateTag(commentText,type,this.env);attribute[tagName]=value;}catch(error){let startPos=tag.getStart();let endPos=tag.getEnd();const fullText=tag.getText();const commentText=tag.comment?.split("\n")[0]?.trim()||"";const commentStart=fullText.indexOf(commentText);if(commentStart!==-1){startPos=tag.getStart()+commentStart;endPos=startPos+commentText.length;}const sourceFile=tag.getSourceFile();const startLineChar=sourceFile.getLineAndCharacterOfPosition(startPos);const endLineChar=sourceFile.getLineAndCharacterOfPosition(endPos);const edit=fix?{text:fix,title:`Fix @${tagName} tag`,range:{startLineNumber:startLineChar.line+1,startColumn:startLineChar.character+1,endLineNumber:endLineChar.line+1,endColumn:endLineChar.character+1}}:null;const errorMessage=supportMessage?.()??"The tag is invalid.";const parseError=new ParsingError(tag,`Invalid Tag '@${tagName}'`,errorMessage,edit,scriptName,attributeName);errors.push(parseError);}}});return attribute}}getNodeAsAttribute(node,errors=[],scriptName){const name=node.name&&typescriptExports.isIdentifier(node.name)&&node.name.text;const{type,name:typeName,array,isMixedUnion}=getType(node,this.typeChecker);const enums=this.getEnumMembers(node,errors);let value=null;if(isMixedUnion&&enums.length===0){errors.push(new ParsingError(node,"Invalid Type","Mixed literal union types (combining different primitive types like string | number) are not supported. "+'Use a union of the same primitive type (e.g., \'1 | 2 | 3\' or \'"a" | "b" | "c"\') or refactor your type.',undefined,scriptName,name));return}const serializer=!array&&this.typeSerializerMap.get(typeName);if(serializer){try{value=serializer(node.initializer??node,this.typeChecker);}catch(error){if(error&&typeof error==="object"){error.scriptName=scriptName;error.attributeName=name;}errors.push(error);return}}return {type,name,typeName,array,value,enum:enums}}getEnumMembers(node,errors){let typeNode=null;let members=[];if(node.type){typeNode=node;}else {const jsDocs=typescriptExports.getJSDocTags(node);const typeTag=jsDocs.find(doc=>doc.tagName.text==="type");if(typeTag&&typescriptExports.isJSDocTypeTag(typeTag)){typeNode=typeTag.typeExpression;}}const type=typeNode&&(typeNode.type.elementType??typeNode.type);if(typeNode&&typescriptExports.isTypeReferenceNode(type)){let symbol=this.typeChecker.getSymbolAtLocation(type.typeName);if(symbol&&symbol.flags&typescriptExports.SymbolFlags.Alias){symbol=this.typeChecker.getAliasedSymbol(symbol);}if(symbol){const declarations=symbol.getDeclarations();if(declarations&&declarations.length>0){declarations.forEach(declaration=>{if(typescriptExports.isEnumDeclaration(declaration)){members=declaration.members.map(member=>
|
|
428
|
+
class AttributeParser{constructor(tags=new Map,env,typeSerializerMap){this.env=env;this.tagTypeAnnotations=tags;this.typeSerializerMap=typeSerializerMap;this.program=env.languageService.getProgram();this.typeChecker=this.program.getTypeChecker();}parseAttributeComment(node,errors=[],requiresAttributeTag=true,scriptName,attributeName){if(!requiresAttributeTag||hasTag("attribute",node)){const attribute=this.getNodeAsAttribute(node,errors,scriptName);if(!attribute)return;attribute.description=node.jsDoc?.[0]?.comment||"";const jsDocTags=node.jsDoc?.[0]?.tags??[];jsDocTags.forEach(tag=>{const tagName=tag.tagName.text;let commentText=(tag.comment?.split("\n")[0]||"").trim();if(this.tagTypeAnnotations.has(tagName)){const{type,supportMessage,fix}=this.tagTypeAnnotations.get(tagName);try{const value=parseTag(commentText);if(typeof value==="string")commentText=`"${commentText}"`;validateTag(commentText,type,this.env);attribute[tagName]=value;}catch(error){let startPos=tag.getStart();let endPos=tag.getEnd();const fullText=tag.getText();const commentText=tag.comment?.split("\n")[0]?.trim()||"";const commentStart=fullText.indexOf(commentText);if(commentStart!==-1){startPos=tag.getStart()+commentStart;endPos=startPos+commentText.length;}const sourceFile=tag.getSourceFile();const startLineChar=sourceFile.getLineAndCharacterOfPosition(startPos);const endLineChar=sourceFile.getLineAndCharacterOfPosition(endPos);const edit=fix?{text:fix,title:`Fix @${tagName} tag`,range:{startLineNumber:startLineChar.line+1,startColumn:startLineChar.character+1,endLineNumber:endLineChar.line+1,endColumn:endLineChar.character+1}}:null;const errorMessage=supportMessage?.()??"The tag is invalid.";const parseError=new ParsingError(tag,`Invalid Tag '@${tagName}'`,errorMessage,edit,scriptName,attributeName);errors.push(parseError);}}});return attribute}}getNodeAsAttribute(node,errors=[],scriptName){const name=node.name&&typescriptExports.isIdentifier(node.name)&&node.name.text;const{type,name:typeName,array,isMixedUnion}=getType(node,this.typeChecker);const enums=this.getEnumMembers(node,errors);let value=null;if(isMixedUnion&&enums.length===0){errors.push(new ParsingError(node,"Invalid Type","Mixed literal union types (combining different primitive types like string | number) are not supported. "+'Use a union of the same primitive type (e.g., \'1 | 2 | 3\' or \'"a" | "b" | "c"\') or refactor your type.',undefined,scriptName,name));return}const serializer=!array&&this.typeSerializerMap.get(typeName);if(serializer){try{value=serializer(node.initializer??node,this.typeChecker);}catch(error){if(error&&typeof error==="object"){error.scriptName=scriptName;error.attributeName=name;}errors.push(error);return}}return {type,name,typeName,array,value,enum:enums}}getEnumMembers(node,errors){let typeNode=null;let members=[];if(node.type){typeNode=node;}else {const jsDocs=typescriptExports.getJSDocTags(node);const typeTag=jsDocs.find(doc=>doc.tagName.text==="type");if(typeTag&&typescriptExports.isJSDocTypeTag(typeTag)){typeNode=typeTag.typeExpression;}}const type=typeNode&&(typeNode.type.elementType??typeNode.type);if(typeNode&&typescriptExports.isTypeReferenceNode(type)){let symbol=this.typeChecker.getSymbolAtLocation(type.typeName);if(symbol&&symbol.flags&typescriptExports.SymbolFlags.Alias){symbol=this.typeChecker.getAliasedSymbol(symbol);}if(symbol){const declarations=symbol.getDeclarations();if(declarations&&declarations.length>0){declarations.forEach(declaration=>{if(typescriptExports.isEnumDeclaration(declaration)){members=declaration.members.map(member=>{const name=member.name.text;let value;if(member.initializer){value=getLiteralValue(member.initializer,this.typeChecker);}return {[name]:value}});}const jsDocs=typescriptExports.getJSDocTags(declaration);const enumTag=jsDocs.find(doc=>doc.tagName.text==="enum");if(enumTag&&typescriptExports.isJSDocEnumTag(enumTag)){members=members.concat(this.getMembers(declaration.initializer,errors));}});}}}return members}getMembers(node){const members=[];if(typescriptExports.isObjectLiteralExpression(node)){node.properties.forEach(property=>{if(typescriptExports.isPropertyAssignment(property)){const isValidName=typescriptExports.isIdentifier(property.name)||typescriptExports.isStringLiteral(property.name);const name=isValidName&&property.name.text;const value=getLiteralValue(property.initializer,this.typeChecker);members.push({[name]:value});}});}return members}}
|
|
429
429
|
|
|
430
430
|
function zipArrays(a,b){const length=Math.max(a.length,b.length);const result=new Array(length);for(let i=0;i<length;i++){result[i]=i<a.length?a[i]:b[i];}return result}
|
|
431
431
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcanvas/attribute-parser",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.2",
|
|
4
4
|
"author": "PlayCanvas <support@playcanvas.com>",
|
|
5
5
|
"homepage": "https://github.com/playcanvas/attribute-parser#readme",
|
|
6
6
|
"description": "The PlayCanvas Attribute Parser",
|
|
@@ -34,12 +34,6 @@
|
|
|
34
34
|
"README.md",
|
|
35
35
|
"LICENSE.md"
|
|
36
36
|
],
|
|
37
|
-
"dependencies": {
|
|
38
|
-
"@playcanvas/eslint-config": "^2.0.0",
|
|
39
|
-
"@typescript/vfs": "^1.6.0",
|
|
40
|
-
"eslint": "^9.0.0",
|
|
41
|
-
"typescript": "^5.5.3"
|
|
42
|
-
},
|
|
43
37
|
"devDependencies": {
|
|
44
38
|
"@rollup/plugin-commonjs": "28.0.6",
|
|
45
39
|
"@rollup/plugin-node-resolve": "16.0.1",
|
|
@@ -48,18 +42,25 @@
|
|
|
48
42
|
"chai": "5.2.1",
|
|
49
43
|
"globals": "16.3.0",
|
|
50
44
|
"mocha": "11.7.1",
|
|
51
|
-
"
|
|
52
|
-
"publint": "0.3.12"
|
|
45
|
+
"playcanvas": "^2.12.3",
|
|
46
|
+
"publint": "0.3.12",
|
|
47
|
+
"rollup": "4.46.2"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@playcanvas/eslint-config": "^2.0.0",
|
|
51
|
+
"@typescript/vfs": "^1.6.0",
|
|
52
|
+
"eslint": "^9.0.0",
|
|
53
|
+
"typescript": "^5.5.3"
|
|
53
54
|
},
|
|
54
55
|
"scripts": {
|
|
55
|
-
"
|
|
56
|
-
"build": "rollup -c",
|
|
56
|
+
"build": "rollup -c && npm run build:types",
|
|
57
57
|
"build:types": "tsc -p tsconfig.build.json",
|
|
58
|
-
"
|
|
59
|
-
"lint": "eslint src rollup.config.mjs",
|
|
60
|
-
"
|
|
58
|
+
"lint": "eslint src rollup.config.mjs eslint.config.mjs",
|
|
59
|
+
"lint:fix": "eslint src rollup.config.mjs eslint.config.mjs --fix",
|
|
60
|
+
"publint": "publint",
|
|
61
|
+
"test": "mocha test/**/*.test.js --timeout 10000",
|
|
61
62
|
"type:check": "tsc --noEmit",
|
|
62
|
-
"
|
|
63
|
+
"watch": "rollup -c -w"
|
|
63
64
|
},
|
|
64
65
|
"engines": {
|
|
65
66
|
"node": ">=18.0.0"
|