@playcanvas/attribute-parser 1.9.0 → 1.10.0

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/dist/index.js CHANGED
@@ -417,20 +417,22 @@ var ts = /*#__PURE__*/_mergeNamespaces({
417
417
  default: typescript
418
418
  }, [typescriptExports]);
419
419
 
420
- class ParsingError{constructor(node,message){this.node=node;this.message=message;}toString(){const file=this.node.getSourceFile();const fileName=file.fileName;const start=this.node.getSourceFile().getLineAndCharacterOfPosition(this.node.getStart());return `ParsingError: ${this.message} at ${fileName}:${start.line+1}:${start.character}`}}
420
+ class ParsingError{constructor(node,type,message,fix){this.node=node;this.type=type;this.message=message;this.fix=fix;}toString(){const file=this.node.getSourceFile();const fileName=file.fileName;const start=this.node.getSourceFile().getLineAndCharacterOfPosition(this.node.getStart());return `ParsingError: ${this.message} at ${fileName}:${start.line+1}:${start.character}`}}
421
421
 
422
- const getTagFromJsdoc=(tag,doc)=>{return doc?.tags?.find(doc=>doc.tagName.text===tag)};const getTag=(tag,node)=>{return node?.jsDoc?.find(doc=>getTagFromJsdoc(tag,doc))};const hasTag=(tag,node)=>{return !!getTag(tag,node)};const isInterface=node=>{return hasTag("interface",node)};const isStaticMember=member=>(typescriptExports.getCombinedModifierFlags(member)&typescriptExports.ModifierFlags.Static)!==0;
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,`"${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,`"${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){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 []};
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){if(!requiresAttributeTag||hasTag("attribute",node)){const attribute=this.getNodeAsAttribute(node,errors);if(!attribute)return;attribute.description=node.jsDoc?.[0]?.comment||"";const jsDocTags=node.jsDoc?.[0]?.tags??[];jsDocTags.forEach(tag=>{let commentText=(tag.comment?.split("\n")[0]||"").trim();if(this.tagTypeAnnotations.has(tag.tagName.text)){try{const value=parseTag(commentText);const tagTypeAnnotation=this.tagTypeAnnotations.get(tag.tagName.text);if(typeof value==="string")commentText=`"${commentText}"`;validateTag(commentText,tagTypeAnnotation,this.env);attribute[tag.tagName.text]=value;}catch(error){const file=node.getSourceFile();const{line,character}=file.getLineAndCharacterOfPosition(tag.getStart());const parseError=new ParsingError(tag,`Error (${line}, ${character}): Parsing Tag '@${tag.tagName.text} ${commentText}' - ${error.message}`);errors.push(parseError);}}});return attribute}}getNodeAsAttribute(node,errors=[]){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,`Mixed literal union types (combining different primitive types like string | number) are not supported for attribute: '${name}'. `+'Please use a union of the same primitive type (e.g., \'1 | 2 | 3\' or \'"a" | "b" | "c"\') or refactor your type.'));return}const serializer=!array&&this.typeSerializerMap.get(typeName);if(serializer){try{value=serializer(node.initializer??node,this.typeChecker);}catch(error){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=>({[member.name.text]:member.initializer.text}));}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}}
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){if(!requiresAttributeTag||hasTag("attribute",node)){const attribute=this.getNodeAsAttribute(node,errors);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);errors.push(parseError);}}});return attribute}}getNodeAsAttribute(node,errors=[]){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.'));return}const serializer=!array&&this.typeSerializerMap.get(typeName);if(serializer){try{value=serializer(node.initializer??node,this.typeChecker);}catch(error){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=>({[member.name.text]:member.initializer.text}));}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
 
432
- const createNewExpressionParser=(name,argProcessor,defaultArr=[])=>{return node=>{if(node.kind!==typescriptExports.SyntaxKind.NewExpression){if(typescriptExports.isPropertyAssignment(node.parent)||typescriptExports.isPropertyDeclaration(node.parent)){throw new ParsingError(node,`Property "${node.name.text}" is not a ${name}.`)}return defaultArr}const args=argProcessor(node.arguments);return zipArrays(args,defaultArr)}};const parseNumberArguments=args=>args.map(arg=>parseFloatNode(arg,true));const createNumberArgumentParser=(name,defaultArr=[])=>createNewExpressionParser(name,parseNumberArguments,defaultArr);const createArrayArgumentParser=(name,defaultArr=[])=>createNewExpressionParser(name,parseArrayLiteral,defaultArr);const SUPPORTED_INITIALIZABLE_TYPE_NAMES=new Map([["Curve",createArrayArgumentParser("Curve")],["Vec2",createNumberArgumentParser("Vec2",[0,0])],["Vec3",createNumberArgumentParser("Vec3",[0,0,0])],["Vec4",createNumberArgumentParser("Vec4",[0,0,0,0])],["Color",createNumberArgumentParser("Color",[1,1,1,1])],["number",getLiteralValue],["string",getLiteralValue],["boolean",getLiteralValue]]);const SUPPORTED_PLAYCANVAS_TYPES=new Set(["Curve","Asset","Entity","Vec2","Vec3","Vec4","Color"]);const isSupportedPlayCanvasNode=node=>{return typescriptExports.isClassDeclaration(node)&&SUPPORTED_PLAYCANVAS_TYPES.has(node.name.text)};const EDITOR_ASSET_TYPES=["animation","animstategraph","audio","bundle","binary","container","cubemap","css","font","folder","json","html","gsplat","material","model","render","scene","script","shader","sprite","template","text","texture","textureAtlas","wasm"];const resourceTypesAsUnion=EDITOR_ASSET_TYPES.map(type=>`"${type}"`).join("|");const SUPPORTED_BLOCK_TAGS=new Map([["resource",resourceTypesAsUnion],["color",'"r"|"rg"|"rgb"|"rgba"'],["curves",'["x"] | ["x", "y"] | ["x", "y", "z"] | ["x", "z"] | ["y"] | ["y", "z"] | ["z"]'],["range","number[]"],["placeholder","string"],["precision","number"],["size","number"],["step","number"],["title","string"],["default","any"],["enabledif","any"],["visibleif","any"]]);const mapAttributesToOutput=attribute=>{attribute.type=attribute.typeName.toLowerCase();if(attribute.range){if(attribute.range.length>0){attribute.min=attribute.range[0];if(attribute.range.length>1){attribute.max=attribute.range[1];}}delete attribute.range;}if(typeof attribute.attributes=="object"){attribute.type="json";attribute.schema=Object.keys(attribute.attributes).map(key=>{return attribute.attributes[key]});delete attribute.attributes;}if(attribute.type==="color"){attribute.type="rgba";}if(attribute.resource){attribute.assetType=attribute.resource;delete attribute.resource;}if(attribute.enum.length===0)delete attribute.enum;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.value!==undefined)attribute.default=attribute.default??attribute.value;if(attribute.type==="curve"&&Array.isArray(attribute.value)&&attribute.value.length===0){delete attribute.default;}if(attribute.enabledif?.startsWith("{")&&attribute.enabledif?.endsWith("}")){attribute.enabledif=attribute.enabledif.slice(1,-1);}if(attribute.visibleif?.startsWith("{")&&attribute.visibleif?.endsWith("}")){attribute.visibleif=attribute.visibleif.slice(1,-1);}delete attribute.typeName;delete attribute.value;return attribute};class ScriptParser{constructor(env,maxDepth=2){if(!env){throw new Error("A TypeScript Environment must be provided to the ScriptParser.")}this.env=env;this.program=env.languageService.getProgram();this.typeChecker=this.program.getTypeChecker();this.maxDepth=maxDepth;const playcanvasTypesFile=this.program.getSourceFile("./playcanvas.d.ts");if(!playcanvasTypesFile){throw new Error("The PlayCanvas types file could not be found.")}const allSupportedTypes=flatMapAnyNodes(playcanvasTypesFile,isSupportedPlayCanvasNode).map(node=>this.typeChecker.getTypeAtLocation(node));allSupportedTypes.push(this.typeChecker.getStringType());allSupportedTypes.push(this.typeChecker.getNumberType());allSupportedTypes.push(this.typeChecker.getBooleanType());this.typeSerializerMap=new Map;this.allSupportedTypesSet=new Set(allSupportedTypes);allSupportedTypes.forEach(type=>{const typeName=this.typeChecker.typeToString(type);const serializer=SUPPORTED_INITIALIZABLE_TYPE_NAMES.get(typeName);if(serializer){this.typeSerializerMap.set(typeName,serializer);}});this.attributeParser=new AttributeParser(SUPPORTED_BLOCK_TAGS,env,this.typeSerializerMap);}validateProgram(errors=[]){const compilerDiagnostics=this.program.getSyntacticDiagnostics();if(compilerDiagnostics.length>0){for(const diagnostic of compilerDiagnostics){const message=typescriptExports.flattenDiagnosticMessageText(diagnostic.messageText,"\n");if(diagnostic.file){const location=diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);errors.push({message,file:diagnostic.file.fileName,line:location.line+1,column:location.character+1,length:diagnostic.length});}else {errors.push({message});}}}return errors.length===0}extractAttributes(node,opts={}){const attributes=opts.attributes||{};const errors=opts.errors??[];const requiresAttributeTag=opts.requiresAttributeTag??true;const depth=opts.depth??0;if(depth>10){return attributes}const members=getJSDocTags(node,this.typeChecker);for(const{member,memberName}of members){const attributeMetadata=this.attributeParser.parseAttributeComment(member,errors,requiresAttributeTag);if(attributeMetadata){const{type,typeName}=attributeMetadata;const initializer=member?.initializer;if(typeName==="any"){const error=new ParsingError(member,`The attribute "${memberName}" is not initialized and does not have a @type tag.`);errors.push(error);continue}else if(!this.allSupportedTypesSet.has(type)&&attributeMetadata.enum.length===0){const symbol=type.aliasSymbol||type.symbol;const typeNode=symbol?.valueDeclaration||symbol.declarations?.[0];if(!typeNode){continue}const typeIsJSDocTypeDef=typescriptExports.isJSDocTypedefTag(typeNode);const typeIsInterface=hasTag("interface",typeNode)||typescriptExports.isInterfaceDeclaration(typeNode);const isInitialized=!!initializer&&typescriptExports.isObjectLiteralExpression(initializer);const isJSDocTypeLiteral=type.symbol.name==="__type";if(!isInitialized&&!typeIsInterface&&!typeIsJSDocTypeDef&&!isJSDocTypeLiteral){const error=new ParsingError(member,`Attribute "${memberName}" is an invalid type.`);errors.push(error);continue}const members=typeNode.members??typeNode.properties??Array.from(type.symbol.members).map(arr=>arr[1].declarations[0])??[];let commentAttributes;if(typeIsInterface||isInitialized){commentAttributes=this.extractAttributes(typeNode,{errors,requiresAttributeTag:false,depth:depth+1});}const nestedAttributes=members.reduce((attributes,prop)=>{const attribute=this.attributeParser.getNodeAsAttribute(prop,errors);if(attribute&&this.allSupportedTypesSet.has(attribute.type)){attributes[attribute.name]=mapAttributesToOutput(attribute);}return attributes},{});const mergedAttributes={...nestedAttributes,...commentAttributes};attributeMetadata.attributes=mergedAttributes;}attributes[memberName]=mapAttributesToOutput(attributeMetadata);}}return attributes}}
432
+ const createNewExpressionParser=(name,argProcessor,defaultArr=[])=>{return node=>{if(node.kind!==typescriptExports.SyntaxKind.NewExpression){if(typescriptExports.isPropertyAssignment(node.parent)||typescriptExports.isPropertyDeclaration(node.parent)){throw new ParsingError(node,"Invalid Property",`Property "${node.name.text}" is not a ${name}.`)}return defaultArr}const args=argProcessor(node.arguments);return zipArrays(args,defaultArr)}};const parseNumberArguments=args=>args.map(arg=>parseFloatNode(arg,true));const createNumberArgumentParser=(name,defaultArr=[])=>createNewExpressionParser(name,parseNumberArguments,defaultArr);const createArrayArgumentParser=(name,defaultArr=[])=>createNewExpressionParser(name,parseArrayLiteral,defaultArr);const SUPPORTED_INITIALIZABLE_TYPE_NAMES=new Map([["Curve",createArrayArgumentParser("Curve")],["Vec2",createNumberArgumentParser("Vec2",[0,0])],["Vec3",createNumberArgumentParser("Vec3",[0,0,0])],["Vec4",createNumberArgumentParser("Vec4",[0,0,0,0])],["Color",createNumberArgumentParser("Color",[1,1,1,1])],["number",getLiteralValue],["string",getLiteralValue],["boolean",getLiteralValue]]);const SUPPORTED_PLAYCANVAS_TYPES=new Set(["Curve","Asset","Entity","Vec2","Vec3","Vec4","Color"]);const isSupportedPlayCanvasNode=node=>{return typescriptExports.isClassDeclaration(node)&&SUPPORTED_PLAYCANVAS_TYPES.has(node.name.text)};const EDITOR_ASSET_TYPES=["animation","animstategraph","audio","bundle","binary","container","cubemap","css","font","folder","json","html","gsplat","material","model","render","scene","script","shader","sprite","template","text","texture","textureAtlas","wasm"];const resourceTypesAsUnion=EDITOR_ASSET_TYPES.map(type=>`"${type}"`).join("|");const SUPPORTED_BLOCK_TAGS=new Map([["resource",{type:resourceTypesAsUnion,supportMessage:()=>`@resource is malformed or invalid. The tag should be formatted as "@resource ${resourceTypesAsUnion}".`,fix:"texture"}],["color",{type:'"r"|"rg"|"rgb"|"rgba"',supportMessage:()=>'@color is malformed or invalid. The tag should be formatted as "@color r", "@color rg", "@color rgb", or "@color rgba".',fix:"rgb"}],["curves",{type:'["x"] | ["x", "y"] | ["x", "y", "z"] | ["x", "z"] | ["y"] | ["y", "z"] | ["z"]',supportMessage:()=>'@curves is malformed or invalid. The tag should be formatted as "@curves x", "@curves x y", "@curves x y z", "@curves x z", "@curves y", "@curves y z", or "@curves z".',fix:'["x"]'}],["range",{type:"number[]",supportMessage:()=>'@range is malformed or invalid. The tag should be formatted as "@range [0, 100]".',fix:"[0, 100]"}],["placeholder",{type:"string",supportMessage:()=>'@placeholder is malformed or invalid. The tag should be formatted as "@placeholder "My Placeholder"".',fix:'"My Placeholder"'}],["precision",{type:"number",supportMessage:()=>'@precision is malformed or invalid. The tag should be formatted as "@precision 0.1".',fix:"0.1"}],["size",{type:"number",supportMessage:()=>'@size is malformed or invalid. The tag should be formatted as "@size 100".',fix:"100"}],["step",{type:"number",supportMessage:()=>'@step is malformed or invalid. The tag should be formatted as "@step 1".',fix:"1"}],["title",{type:"string",supportMessage:()=>'@title is malformed or invalid. The tag should be formatted as "@title "My Attribute"".',fix:'"My Attribute"'}],["default",{type:"any"}],["enabledif",{type:"any"}],["visibleif",{type:"any"}]]);const mapAttributesToOutput=attribute=>{attribute.type=attribute.typeName.toLowerCase();if(attribute.range){if(attribute.range.length>0){attribute.min=attribute.range[0];if(attribute.range.length>1){attribute.max=attribute.range[1];}}delete attribute.range;}if(typeof attribute.attributes=="object"){attribute.type="json";attribute.schema=Object.keys(attribute.attributes).map(key=>{return attribute.attributes[key]});delete attribute.attributes;}if(attribute.type==="color"){attribute.type="rgba";}if(attribute.resource){attribute.assetType=attribute.resource;delete attribute.resource;}if(attribute.enum.length===0)delete attribute.enum;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.value!==undefined)attribute.default=attribute.default??attribute.value;if(attribute.type==="curve"&&Array.isArray(attribute.value)&&attribute.value.length===0){delete attribute.default;}if(attribute.enabledif?.startsWith("{")&&attribute.enabledif?.endsWith("}")){attribute.enabledif=attribute.enabledif.slice(1,-1);}if(attribute.visibleif?.startsWith("{")&&attribute.visibleif?.endsWith("}")){attribute.visibleif=attribute.visibleif.slice(1,-1);}delete attribute.typeName;delete attribute.value;return attribute};class ScriptParser{constructor(env,maxDepth=2){if(!env){throw new Error("A TypeScript Environment must be provided to the ScriptParser.")}this.env=env;this.program=env.languageService.getProgram();this.typeChecker=this.program.getTypeChecker();this.maxDepth=maxDepth;const playcanvasTypesFile=this.program.getSourceFile("./playcanvas.d.ts");if(!playcanvasTypesFile){throw new Error("The PlayCanvas types file could not be found.")}const allSupportedTypes=flatMapAnyNodes(playcanvasTypesFile,isSupportedPlayCanvasNode).map(node=>this.typeChecker.getTypeAtLocation(node));allSupportedTypes.push(this.typeChecker.getStringType());allSupportedTypes.push(this.typeChecker.getNumberType());allSupportedTypes.push(this.typeChecker.getBooleanType());this.typeSerializerMap=new Map;this.allSupportedTypesSet=new Set(allSupportedTypes);allSupportedTypes.forEach(type=>{const typeName=this.typeChecker.typeToString(type);const serializer=SUPPORTED_INITIALIZABLE_TYPE_NAMES.get(typeName);if(serializer){this.typeSerializerMap.set(typeName,serializer);}});this.attributeParser=new AttributeParser(SUPPORTED_BLOCK_TAGS,env,this.typeSerializerMap);}validateProgram(errors=[]){const compilerDiagnostics=this.program.getSyntacticDiagnostics();if(compilerDiagnostics.length>0){for(const diagnostic of compilerDiagnostics){const message=typescriptExports.flattenDiagnosticMessageText(diagnostic.messageText,"\n");if(diagnostic.file){const location=diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);errors.push({message,file:diagnostic.file.fileName,line:location.line+1,column:location.character+1,length:diagnostic.length});}else {errors.push({message});}}}return errors.length===0}extractAttributes(node,opts={}){const attributes=opts.attributes||{};const errors=opts.errors??[];const requiresAttributeTag=opts.requiresAttributeTag??true;const depth=opts.depth??0;if(depth>10){return attributes}const members=getJSDocTags(node,this.typeChecker);for(const{member,memberName}of members){const attributeMetadata=this.attributeParser.parseAttributeComment(member,errors,requiresAttributeTag);if(attributeMetadata){const{type,typeName}=attributeMetadata;const initializer=member?.initializer;if(typeName==="any"){const supportedTypes=Array.from(SUPPORTED_INITIALIZABLE_TYPE_NAMES.keys()).join(", ");let errorMessage;if(member.initializer){errorMessage=`This attribute is initialized with an invalid type. An attribute should be one of the following: ${supportedTypes}.`;}else {errorMessage=`This attribute has an invalid @type tag. An attribute should be one of the following: ${supportedTypes}.`;}const error=new ParsingError(member,"Invalid Type",errorMessage);errors.push(error);continue}else if(!this.allSupportedTypesSet.has(type)&&attributeMetadata.enum.length===0){const symbol=type.aliasSymbol||type.symbol;const typeNode=symbol?.valueDeclaration||symbol.declarations?.[0];if(!typeNode){continue}const typeIsJSDocTypeDef=typescriptExports.isJSDocTypedefTag(typeNode);const typeIsInterface=hasTag("interface",typeNode)||typescriptExports.isInterfaceDeclaration(typeNode);const isInitialized=!!initializer&&typescriptExports.isObjectLiteralExpression(initializer);const isJSDocTypeLiteral=type.symbol.name==="__type";if(!isInitialized&&!typeIsInterface&&!typeIsJSDocTypeDef&&!isJSDocTypeLiteral){let error;const typeTag=getTag("type",member);const supportedTypes=Array.from(SUPPORTED_INITIALIZABLE_TYPE_NAMES.keys()).join(", ");if(typeTag){error=new ParsingError(typeTag.typeExpression,"Invalid Type",`"${typeTag.typeExpression.getText()}" is not a valid attribute type. An attribute should be one of the following: ${supportedTypes}`);}else if(initializer){error=new ParsingError(initializer,"Invalid Type",`"${initializer.getText()}" is not a valid attribute type. An attribute should be one of the following: ${supportedTypes}`);}else {error=new ParsingError(member,"Invalid Type",`Attribute is an invalid type. An attribute should be one of the following: ${supportedTypes}`);}errors.push(error);continue}const members=typeNode.members??typeNode.properties??Array.from(type.symbol.members).map(arr=>arr[1].declarations[0])??[];let commentAttributes;if(typeIsInterface||isInitialized){commentAttributes=this.extractAttributes(typeNode,{errors,requiresAttributeTag:false,depth:depth+1});}const nestedAttributes=members.reduce((attributes,prop)=>{const attribute=this.attributeParser.getNodeAsAttribute(prop,errors);if(attribute&&this.allSupportedTypesSet.has(attribute.type)){attributes[attribute.name]=mapAttributesToOutput(attribute);}return attributes},{});const mergedAttributes={...nestedAttributes,...commentAttributes};attributeMetadata.attributes=mergedAttributes;}attributes[memberName]=mapAttributesToOutput(attributeMetadata);}}return attributes}}
433
433
 
434
- const toLowerCamelCase=str=>str[0].toLowerCase()+str.substring(1);const COMPILER_OPTIONS={noLib:true,strict:false,skipLibCheck:true,target:typescriptExports.ScriptTarget.ES2023,module:typescriptExports.ModuleKind.CommonJS,checkJs:true,allowJs:true,baseUrl:"./",paths:{"playcanvas":["/playcanvas.js"]}};class JSDocParser{async init(libPath){if(this._env){return this}let fsMap;if(!libPath){fsMap=await createDefaultMapFromNodeModules();}else if(libPath.endsWith(".d.ts")){const types=await fetch(libPath).then(r=>r.text());fsMap=new Map([["/lib.d.ts",types]]);}else {fsMap=await createDefaultMapFromCDN(COMPILER_OPTIONS,libPath,ts);}const system=createSystem(fsMap);this._env=createVirtualTypeScriptEnvironment(system,Array.from(fsMap.keys()),ts,COMPILER_OPTIONS);return this}updateProgram(newFiles=[],removedFiles=[]){const errors=[];newFiles.forEach(([filePath,fileContent])=>{if(this._env.getSourceFile(filePath)){this._env.updateFile(filePath,fileContent);}else {this._env.createFile(filePath,fileContent);}});removedFiles.forEach(filePath=>{this._env.deleteFile(filePath);});this.parser=new ScriptParser(this._env);this.program=this._env.languageService.getProgram();this.parser.validateProgram(errors);this._errors=errors;return errors}getAllEsmScripts(fileName){const typeChecker=this.program.getTypeChecker();const pcTypes=this.program.getSourceFile("/playcanvas.d.ts");if(!pcTypes){throw new Error("PlayCanvas Types must be supplied")}const esmScripts=new Map;const sourceFile=this.program.getSourceFile(fileName);if(sourceFile===undefined){return esmScripts}if(sourceFile===null){throw new Error(`Source file ${fileName} not found`)}const nodes=getExportedNodes(this.program,sourceFile);const esmScriptClass=pcTypes.statements.find(node=>node.kind===typescriptExports.SyntaxKind.ClassDeclaration&&node.name.text==="Script")?.symbol;const isScriptNameMember=member=>typescriptExports.isPropertyDeclaration(member)&&typescriptExports.isIdentifier(member.name)&&member.name.text==="scriptName"&&isStaticMember(member)&&typescriptExports.isStringLiteral(member.initializer);nodes.forEach((node,name)=>{if(isAliasedClassDeclaration(node,typeChecker)&&inheritsFrom(node,typeChecker,esmScriptClass)){const scriptNameMember=node.members.find(isScriptNameMember);let finalName;if(scriptNameMember){finalName=scriptNameMember.initializer.text;}else {finalName=toLowerCamelCase(name);}esmScripts.set(finalName,node);}});return esmScripts}parseAttributes(fileName){const results={};const errors=[...this._errors];if(errors.length>0){return [results,errors]}const nodes=this.getAllEsmScripts(fileName);nodes.forEach((node,name)=>{const opts=results[name]={attributes:{},errors:[]};this.parser.extractAttributes(node,opts);});return [results,errors]}getAttributes(fileName){const results={};const errors=[...this._errors];if(errors.length>0){return [results,errors]}const typeChecker=this.program.getTypeChecker();const pcTypes=this.program.getSourceFile("/playcanvas.d.ts");const esmScriptClass=pcTypes.statements.find(node=>node.kind===typescriptExports.SyntaxKind.ClassDeclaration&&node.name.text==="Script")?.symbol;const sourceFile=this.program.getSourceFile(fileName);if(!sourceFile){throw new Error(`Source file ${fileName} not found`)}const nodes=flatMapAnyNodes(sourceFile,node=>{if(!typescriptExports.isClassDeclaration(node)){return false}return inheritsFrom(node,typeChecker,esmScriptClass)||isInterface(node)});for(const node of nodes){const name=toLowerCamelCase(node.name.text);const members=[];for(const member of node.members){if(member.kind!==typescriptExports.SyntaxKind.PropertyDeclaration||isStaticMember(member)){continue}const tags=member.jsDoc?member.jsDoc.map(jsdoc=>jsdoc.tags?.map(tag=>tag.tagName.getText())??[]).flat():[];const name=member.name.getText();const type=getType(member,typeChecker);if(!type){continue}const namePos=typescriptExports.getLineAndCharacterOfPosition(member.getSourceFile(),member.name.getStart());const jsdocNode=member.jsDoc&&member.jsDoc[member.jsDoc.length-1];const jsdocPos=jsdocNode?typescriptExports.getLineAndCharacterOfPosition(member.getSourceFile(),jsdocNode.getStart()):null;const data={name,type:type.name+(type.array?"[]":""),isAttribute:tags.includes("attribute"),start:jsdocNode?{line:jsdocPos.line+1,column:jsdocPos.character+1}:{line:namePos.line+1,column:namePos.character+1},end:{line:namePos.line+1,column:namePos.character+name.length+1}};members.push(data);}results[name]=members;}return [results,errors]}constructor(){this._errors=[];}}
434
+ const toLowerCamelCase=str=>str[0].toLowerCase()+str.substring(1);const SUPPORTED_ATTRIBUTE_TYPES=new Set(["Curve","Vec2","Vec3","Vec4","Color","number","string","boolean"]);const COMPILER_OPTIONS={noLib:true,strict:false,skipLibCheck:true,target:typescriptExports.ScriptTarget.ES2023,module:typescriptExports.ModuleKind.CommonJS,checkJs:true,allowJs:true,baseUrl:"./",paths:{"playcanvas":["/playcanvas.js"]}};class JSDocParser{async init(libPath){if(this._env){return this}let fsMap;if(!libPath){fsMap=await createDefaultMapFromNodeModules();}else if(libPath.endsWith(".d.ts")){const types=await fetch(libPath).then(r=>r.text());fsMap=new Map([["/lib.d.ts",types]]);}else {fsMap=await createDefaultMapFromCDN(COMPILER_OPTIONS,libPath,ts);}const system=createSystem(fsMap);this._env=createVirtualTypeScriptEnvironment(system,Array.from(fsMap.keys()),ts,COMPILER_OPTIONS);return this}updateProgram(newFiles=[],removedFiles=[]){const errors=[];newFiles.forEach(([filePath,fileContent])=>{if(this._env.getSourceFile(filePath)){this._env.updateFile(filePath,fileContent);}else {this._env.createFile(filePath,fileContent);}});removedFiles.forEach(filePath=>{this._env.deleteFile(filePath);});this.parser=new ScriptParser(this._env);this.program=this._env.languageService.getProgram();this.parser.validateProgram(errors);this._errors=errors;return errors}getAllEsmScripts(fileName,errors=[]){const typeChecker=this.program.getTypeChecker();const pcTypes=this.program.getSourceFile("/playcanvas.d.ts");if(!pcTypes){throw new Error("PlayCanvas Types must be supplied")}const esmScripts=new Map;const sourceFile=this.program.getSourceFile(fileName);if(sourceFile===undefined){return esmScripts}if(sourceFile===null){throw new Error(`Source file ${fileName} not found`)}const nodes=getExportedNodes(this.program,sourceFile);const esmScriptClass=pcTypes.statements.find(node=>node.kind===typescriptExports.SyntaxKind.ClassDeclaration&&node.name.text==="Script")?.symbol;const isScriptNameMember=member=>typescriptExports.isPropertyDeclaration(member)&&typescriptExports.isIdentifier(member.name)&&member.name.text==="scriptName"&&isStaticMember(member)&&typescriptExports.isStringLiteral(member.initializer);nodes.forEach((node,name)=>{if(isAliasedClassDeclaration(node,typeChecker)&&inheritsFrom(node,typeChecker,esmScriptClass)){const scriptNameMember=node.members.find(isScriptNameMember);let finalName;if(scriptNameMember){finalName=scriptNameMember.initializer.text;}else {finalName=toLowerCamelCase(name);const insertOffset=node.members.pos;const insertPos=node.getSourceFile().getLineAndCharacterOfPosition(insertOffset);errors.push(new ParsingError(node,"Missing Script Name","Scripts should have a static scriptName member that identifies the script.",{title:"Add scriptName",text:`
435
+ static scriptName = '${finalName}';
436
+ `,range:{startLineNumber:insertPos.line+1,startColumn:insertPos.character+1,endLineNumber:insertPos.line+1,endColumn:insertPos.character+1}}));}esmScripts.set(finalName,node);}});return esmScripts}parseAttributes(fileName){const results={};const errors=[...this._errors];if(errors.length>0){return [results,errors]}const nodes=this.getAllEsmScripts(fileName,errors);nodes.forEach((node,name)=>{const opts=results[name]={attributes:{},errors:[]};this.parser.extractAttributes(node,opts);});return [results,errors]}getAttributes(fileName){const results={};const errors=[...this._errors];if(errors.length>0){return [results,errors]}const typeChecker=this.program.getTypeChecker();const esmScripts=this.getAllEsmScripts(fileName,errors);const[attributes]=this.parseAttributes(fileName);const scripts=[];for(const[scriptName,node]of esmScripts){const members=[];for(const member of node.members){if(member.kind===typescriptExports.SyntaxKind.PropertyDeclaration&&!isStaticMember(member)){members.push(member);}}scripts.push({scriptName,members});errors.push(...attributes[scriptName]?.errors??[]);}for(const{scriptName,members}of scripts){const localMembers=[];for(const member of members){const name=member.name.getText();const attribute=attributes[scriptName].attributes[name];const isAttribute=!!attribute;const type=getType(member,typeChecker);if(!SUPPORTED_ATTRIBUTE_TYPES.has(type.name)){continue}const namePos=typescriptExports.getLineAndCharacterOfPosition(member.getSourceFile(),member.name.getStart());const jsdocNode=member.jsDoc&&member.jsDoc[member.jsDoc.length-1];const jsdocPos=jsdocNode?typescriptExports.getLineAndCharacterOfPosition(member.getSourceFile(),jsdocNode.getStart()):null;localMembers.push({name,isAttribute,type:type.name+(type.array?"[]":""),start:jsdocNode?{line:jsdocPos.line+1,column:jsdocPos.character+1}:{line:namePos.line+1,column:namePos.character+1},end:{line:namePos.line+1,column:namePos.character+name.length+1}});}results[scriptName]=localMembers;}return [results,errors]}constructor(){this._errors=[];}}
435
437
 
436
438
  export { JSDocParser };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcanvas/attribute-parser",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "author": "PlayCanvas <support@playcanvas.com>",
5
5
  "homepage": "https://github.com/playcanvas/attribute-parser#readme",
6
6
  "description": "The PlayCanvas Attribute Parser",
package/types/index.d.ts CHANGED
@@ -34,9 +34,10 @@ export class JSDocParser {
34
34
  /**
35
35
  * Returns all the valid ESM Scripts within a file
36
36
  * @param {string} fileName - The file name in the program to check
37
+ * @param {ParsingError[]} errors - An array to store any parsing errors
37
38
  * @returns {Map<string, import('typescript').Node>} - A map of valid ESM Script <names, nodes> within the file
38
39
  */
39
- getAllEsmScripts(fileName: string): Map<string, import("typescript").Node>;
40
+ getAllEsmScripts(fileName: string, errors?: ParsingError[]): Map<string, import("typescript").Node>;
40
41
  /**
41
42
  * Analyzes the specified TypeScript file and extracts metadata from JSDoc comments.
42
43
  *
@@ -53,3 +54,4 @@ export class JSDocParser {
53
54
  }
54
55
  import { ScriptParser } from './parsers/script-parser.js';
55
56
  import * as ts from 'typescript';
57
+ import { ParsingError } from './parsers/parsing-error.js';
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @typedef {Object} TagDefinition
3
+ * @property {string} type - The type of the tag
4
+ * @property {() => string} supportMessage - A function to generate a support message for the tag
5
+ * @property {string} fix - A function to fix the tag
6
+ */
1
7
  /**
2
8
  * A class to parse JSDoc comments and extract attribute metadata.
3
9
  */
@@ -5,13 +11,13 @@ export class AttributeParser {
5
11
  /**
6
12
  * Creates a new instance of the ScriptParser class.
7
13
  *
8
- * @param {Map<string, string>} tags - An set of tag definitions and their validators to add to the parser
14
+ * @param {Map<string, TagDefinition>} tags - An set of tag definitions and their validators to add to the parser
9
15
  * @param {object} env - The TypeScript environment to use
10
16
  * @param {Map<string, Function>} typeSerializerMap - A map of custom serializers
11
17
  */
12
- constructor(tags: Map<string, string>, env: object, typeSerializerMap: Map<string, Function>);
18
+ constructor(tags: Map<string, TagDefinition>, env: object, typeSerializerMap: Map<string, Function>);
13
19
  env: any;
14
- tagTypeAnnotations: Map<string, string>;
20
+ tagTypeAnnotations: Map<string, TagDefinition>;
15
21
  typeSerializerMap: Map<string, Function>;
16
22
  program: any;
17
23
  typeChecker: any;
@@ -51,5 +57,19 @@ export class AttributeParser {
51
57
  */
52
58
  getMembers(node: ts.Node): any[];
53
59
  }
60
+ export type TagDefinition = {
61
+ /**
62
+ * - The type of the tag
63
+ */
64
+ type: string;
65
+ /**
66
+ * - A function to generate a support message for the tag
67
+ */
68
+ supportMessage: () => string;
69
+ /**
70
+ * - A function to fix the tag
71
+ */
72
+ fix: string;
73
+ };
54
74
  import * as ts from 'typescript';
55
75
  import { ParsingError } from './parsing-error.js';
@@ -1,6 +1,40 @@
1
+ /**
2
+ * @typedef {Object} Fix
3
+ * @property {string} text - The text to insert at the fix location
4
+ * @property {string} title - The title of the fix
5
+ * @property {{ startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number }} range - The range of the fix
6
+ */
7
+ /**
8
+ * A class representing a parsing error.
9
+ * @param {import('typescript').Node} node - The AST node which caused the error
10
+ * @param {string} type - The type of the error
11
+ * @param {string} message - The description of the error
12
+ * @param {Fix} [fix] - The fix for the error
13
+ */
1
14
  export class ParsingError {
2
- constructor(node: any, message: any);
15
+ constructor(node: any, type: any, message: any, fix: any);
3
16
  node: any;
17
+ type: any;
4
18
  message: any;
19
+ fix: any;
5
20
  toString(): string;
6
21
  }
22
+ export type Fix = {
23
+ /**
24
+ * - The text to insert at the fix location
25
+ */
26
+ text: string;
27
+ /**
28
+ * - The title of the fix
29
+ */
30
+ title: string;
31
+ /**
32
+ * - The range of the fix
33
+ */
34
+ range: {
35
+ startLineNumber: number;
36
+ startColumn: number;
37
+ endLineNumber: number;
38
+ endColumn: number;
39
+ };
40
+ };
@@ -1,5 +1,4 @@
1
- export function getTagFromJsdoc(tag: string, doc: import("typescript").Node): import("typescript").Node;
2
- export function getTag(tag: string, node: import("typescript").Node): import("typescript").Node;
1
+ export function getTag(tag: string, node: import("typescript").Node): import("typescript").JSDocTag | null;
3
2
  export function hasTag(tag: string, node: import("typescript").Node): boolean;
4
3
  export function isInterface(node: import("typescript").Node): boolean;
5
4
  export function isStaticMember(member: import("typescript").Node): boolean;