@playcanvas/attribute-parser 1.11.0 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -446,13 +446,13 @@ const getTag=(tag,node)=>{for(const jsDoc of node.jsDoc||[]){for(const tagNode o
446
446
 
447
447
  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}
448
448
 
449
- function createDefaultMapFromCDN(options,prefix,tsLib){const fsMap=new Map;const files=knownLibFilesForCompilerOptions(options,tsLib);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){const exportedNodes=new Map;if(!program||!sourceFile){return exportedNodes}const typeChecker=program.getTypeChecker();const moduleSymbol=typeChecker.getSymbolAtLocation(sourceFile);if(!moduleSymbol){return exportedNodes}const exportedSymbols=typeChecker.getExportsOfModule(moduleSymbol);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}const name=node.name;if(name&&typescriptExports.isIdentifier(name)){const symbol=typeChecker.getSymbolAtLocation(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=>{const classMembers=classNode.members;classMembers.forEach(classMember=>{const isSetter=typescriptExports.isSetAccessor(classMember);if(typescriptExports.isPropertyDeclaration(classMember)||isSetter||typescriptExports.isPropertySignature(classMember)){const memberName=classMember.name&&typescriptExports.isIdentifier(classMember.name)?classMember.name.text:"unnamed";let member=classMember;let hasJsDoc=member.jsDoc?.length>0;if(isSetter&&!hasJsDoc){const symbol=typeChecker.getSymbolAtLocation(classMember.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 checker=typeChecker;const actualType=array?checker.getElementTypeOfArrayType(type):type;const baseType=typeChecker.getBaseTypeOfLiteralType(actualType);let isMixedUnion=false;const unionTypes=baseType.types;if(Array.isArray(unionTypes)){const primitiveKinds=new Set(unionTypes.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.isPrefixUnaryExpression(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}}const isBooleanLiteral=node.kind===typescriptExports.SyntaxKind.TrueKeyword||node.kind===typescriptExports.SyntaxKind.FalseKeyword;if(typescriptExports.isLiteralExpression(node)||isBooleanLiteral){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 []};
449
+ function createDefaultMapFromCDN(options,prefix,tsLib){const fsMap=new Map;const files=knownLibFilesForCompilerOptions(options,tsLib);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){const exportedNodes=new Map;if(!program||!sourceFile){return exportedNodes}const typeChecker=program.getTypeChecker();const moduleSymbol=typeChecker.getSymbolAtLocation(sourceFile);if(!moduleSymbol){return exportedNodes}const exportedSymbols=typeChecker.getExportsOfModule(moduleSymbol);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}const name=node.name;if(name&&typescriptExports.isIdentifier(name)){const symbol=typeChecker.getSymbolAtLocation(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=>{const classMembers=classNode.members;classMembers.forEach(classMember=>{const isSetter=typescriptExports.isSetAccessor(classMember);if(typescriptExports.isPropertyDeclaration(classMember)||isSetter||typescriptExports.isPropertySignature(classMember)){const memberName=classMember.name&&typescriptExports.isIdentifier(classMember.name)?classMember.name.text:"unnamed";let member=classMember;let hasJsDoc=member.jsDoc?.length>0;if(isSetter&&!hasJsDoc){const symbol=typeChecker.getSymbolAtLocation(classMember.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 checker=typeChecker;const actualType=array?checker.getElementTypeOfArrayType(type):type;const baseType=typeChecker.getBaseTypeOfLiteralType(actualType);let isMixedUnion=false;const unionTypes=baseType.types;if(Array.isArray(unionTypes)){const primitiveKinds=new Set(unionTypes.map(t=>typeChecker.getBaseTypeOfLiteralType(t)));primitiveKinds.delete(null);isMixedUnion=primitiveKinds.size>1;}const target=baseType.target;const generic=target&&target!==baseType&&target.objectFlags&typescriptExports.ObjectFlags.Class?target:null;const name=getPrimitiveEnumType(baseType,typeChecker)??generic?.symbol.name??typeChecker.typeToString(baseType);return {type:generic??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.isPrefixUnaryExpression(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}}const isBooleanLiteral=node.kind===typescriptExports.SyntaxKind.TrueKeyword||node.kind===typescriptExports.SyntaxKind.FalseKeyword;if(typescriptExports.isLiteralExpression(node)||isBooleanLiteral){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 []};
450
450
 
451
451
  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}
452
452
 
453
453
  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=typescriptExports.getTextOfJSDocComment(node.jsDoc?.[0]?.comment)??"";const jsDocTags=node.jsDoc?.[0]?.tags??[];jsDocTags.forEach(tag=>{const tagName=tag.tagName.text;let commentText=(typescriptExports.getTextOfJSDocComment(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{let startPos=tag.getStart();let endPos=tag.getEnd();const fullText=tag.getText();const commentText=(typescriptExports.getTextOfJSDocComment(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 nameNode=node.name;const name=nameNode&&typescriptExports.isIdentifier(nameNode)&&nameNode.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}}const attribute={type,name,typeName,array,value,enum:enums};return attribute}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));}});}}}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}}
454
454
 
455
- 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;const scriptName=opts.scriptName??undefined;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,scriptName,memberName);if(attributeMetadata){const type=attributeMetadata.type;const{typeName}=attributeMetadata;const initializer=member.initializer;if(typeName==="any"){const supportedTypes=Array.from(SUPPORTED_INITIALIZABLE_TYPE_NAMES.keys()).join(", ");let errorMessage;if(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,node.name.getText(),memberName,undefined);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}`,undefined,node.name.getText(),memberName);}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}`,undefined,node.name.getText(),memberName);}else {error=new ParsingError(member,"Invalid Type",`Attribute is an invalid type. An attribute should be one of the following: ${supportedTypes}`,undefined,node.name.getText(),memberName);}errors.push(error);continue}const members=typeNode.members??typeNode.properties??Array.from(type.symbol.members).map(entry=>entry[1].declarations[0])??[];let commentAttributes;if(typeIsInterface||isInitialized){commentAttributes=this.extractAttributes(typeNode,{errors,requiresAttributeTag:false,depth:depth+1,scriptName});}const nestedAttributes=members.reduce((attributes,prop)=>{const attribute=this.attributeParser.getNodeAsAttribute(prop,errors,scriptName);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}}
455
+ 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;const scriptName=opts.scriptName??undefined;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,scriptName,memberName);if(attributeMetadata){const type=attributeMetadata.type;const{typeName}=attributeMetadata;const initializer=member.initializer;if(typeName==="any"){const supportedTypes=Array.from(SUPPORTED_INITIALIZABLE_TYPE_NAMES.keys()).join(", ");let errorMessage;if(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,node.name.getText(),memberName,undefined);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];const typeIsJSDocTypeDef=!!typeNode&&typescriptExports.isJSDocTypedefTag(typeNode);const typeIsInterface=!!typeNode&&(hasTag("interface",typeNode)||typescriptExports.isInterfaceDeclaration(typeNode));const isInitialized=!!initializer&&typescriptExports.isObjectLiteralExpression(initializer)&&initializer.properties.length>0;const isJSDocTypeLiteral=!!typeNode&&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}`,undefined,node.name.getText(),memberName);}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}`,undefined,node.name.getText(),memberName);}else {error=new ParsingError(member,"Invalid Type",`Attribute is an invalid type. An attribute should be one of the following: ${supportedTypes}`,undefined,node.name.getText(),memberName);}errors.push(error);continue}const members=typeNode.members??typeNode.properties??Array.from(type.symbol.members).map(entry=>entry[1].declarations[0])??[];let commentAttributes;if(typeIsInterface||isInitialized){commentAttributes=this.extractAttributes(typeNode,{errors,requiresAttributeTag:false,depth:depth+1,scriptName});}const nestedAttributes=members.reduce((attributes,prop)=>{const attribute=this.attributeParser.getNodeAsAttribute(prop,errors,scriptName);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}}
456
456
 
457
457
  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=>typescriptExports.isClassDeclaration(node)&&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:`
458
458
  static scriptName = '${finalName}';
@@ -8,13 +8,13 @@ const getTag=(tag,node)=>{for(const jsDoc of node.jsDoc||[]){for(const tagNode o
8
8
 
9
9
  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}
10
10
 
11
- function createDefaultMapFromCDN(options,prefix,tsLib){const fsMap=new Map;const files=knownLibFilesForCompilerOptions(options,tsLib);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&ts.SymbolFlags.Alias){symbol=typeChecker.getAliasedSymbol(symbol);}return symbol}function getExportedNodes(program,sourceFile){const exportedNodes=new Map;if(!program||!sourceFile){return exportedNodes}const typeChecker=program.getTypeChecker();const moduleSymbol=typeChecker.getSymbolAtLocation(sourceFile);if(!moduleSymbol){return exportedNodes}const exportedSymbols=typeChecker.getExportsOfModule(moduleSymbol);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(ts.isClassDeclaration(node)){return true}const name=node.name;if(name&&ts.isIdentifier(name)){const symbol=typeChecker.getSymbolAtLocation(name);if(symbol){const resolvedSymbol=resolveAliasedSymbol(typeChecker,symbol);if(resolvedSymbol.declarations){return resolvedSymbol.declarations.some(ts.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);}ts.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===ts.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(ts.isClassDeclaration);if(!superClassDeclaration){break}superClasses.push(superClassDeclaration);currentClass=superClassDeclaration;}return superClasses}function getJSDocTags(node,typeChecker){const tags=[];if(ts.isClassDeclaration(node)||ts.isInterfaceDeclaration(node)){const heritageChain=getSuperClasses(node,typeChecker).filter(classNode=>!classNode.getSourceFile().isDeclarationFile);heritageChain.forEach(classNode=>{const classMembers=classNode.members;classMembers.forEach(classMember=>{const isSetter=ts.isSetAccessor(classMember);if(ts.isPropertyDeclaration(classMember)||isSetter||ts.isPropertySignature(classMember)){const memberName=classMember.name&&ts.isIdentifier(classMember.name)?classMember.name.text:"unnamed";let member=classMember;let hasJsDoc=member.jsDoc?.length>0;if(isSetter&&!hasJsDoc){const symbol=typeChecker.getSymbolAtLocation(classMember.name);member=symbol.declarations.find(accessor=>accessor.jsDoc?.length>0);hasJsDoc=!!member;}if(hasJsDoc){tags.push({memberName,member});}}});});}else if(ts.isObjectLiteralExpression(node)){node.properties.forEach(property=>{if(ts.isPropertyAssignment(property)){const memberName=property.name&&(ts.isIdentifier(property.name)||ts.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=>ts.isEnumDeclaration(decl));let isEnumMember=false;if(!isEnumType){isEnumMember=type.symbol?.declarations?.some(decl=>ts.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(ts.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===ts.SyntaxKind.Identifier){const type=typeChecker.getTypeAtLocation(node);const array=typeChecker.isArrayType(type);const checker=typeChecker;const actualType=array?checker.getElementTypeOfArrayType(type):type;const baseType=typeChecker.getBaseTypeOfLiteralType(actualType);let isMixedUnion=false;const unionTypes=baseType.types;if(Array.isArray(unionTypes)){const primitiveKinds=new Set(unionTypes.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=ts.isNumericLiteral(node);const isUnaryExpression=ts.isPrefixUnaryExpression(node)&&(node.operator===ts.SyntaxKind.MinusToken||node.operator===ts.SyntaxKind.PlusToken)&&node.operand.kind===ts.SyntaxKind.NumericLiteral;return isNumericLiteral||isUnaryExpression};const parseFloatNode=(node,isArgument=false)=>{if(!isNumericLiteral(node)){if(ts.isPropertyAssignment(node.parent)||ts.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(ts.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(ts.isPropertyAssignment(declaration)&&declaration.initializer){return getLiteralValue(declaration.initializer,typeChecker)}if(ts.isVariableDeclaration(declaration)&&declaration.initializer){return getLiteralValue(declaration.initializer,typeChecker)}if(ts.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 ts.SyntaxKind.PlusToken:return +operandValue;case ts.SyntaxKind.MinusToken:return -operandValue;case ts.SyntaxKind.ExclamationToken:return !operandValue;case ts.SyntaxKind.TildeToken:return ~operandValue}}return undefined};function handleObjectLiteral(node,typeChecker){const obj={};node.properties.forEach(prop=>{if(ts.isPropertyAssignment(prop)){const key=prop.name.getText();const value=getLiteralValue(prop.initializer,typeChecker);obj[key]=value;}else if(ts.isShorthandPropertyAssignment(prop)){const key=prop.name.getText();const value=resolveIdentifier(prop.name,typeChecker);obj[key]=value;}});return obj}function getLiteralValue(node,typeChecker){if(!node)return undefined;if(typeChecker){const type=typeChecker.getTypeAtLocation(node);if(type&&type.isLiteral()){return type.value}}const isBooleanLiteral=node.kind===ts.SyntaxKind.TrueKeyword||node.kind===ts.SyntaxKind.FalseKeyword;if(ts.isLiteralExpression(node)||isBooleanLiteral){if(ts.isStringLiteral(node)){return node.text}if(ts.isNumericLiteral(node)){return Number(node.text)}if(node.kind===ts.SyntaxKind.TrueKeyword){return true}if(node.kind===ts.SyntaxKind.FalseKeyword){return false}}switch(node.kind){case ts.SyntaxKind.NullKeyword:return null;case ts.SyntaxKind.ArrayLiteralExpression:return node.elements.map(element=>getLiteralValue(element,typeChecker));case ts.SyntaxKind.ObjectLiteralExpression:return handleObjectLiteral(node,typeChecker);case ts.SyntaxKind.Identifier:return resolveIdentifier(node,typeChecker);case ts.SyntaxKind.PropertyAccessExpression:return resolvePropertyAccess(node,typeChecker);case ts.SyntaxKind.ParenthesizedExpression:return getLiteralValue(node.expression,typeChecker);case ts.SyntaxKind.PrefixUnaryExpression:return evaluatePrefixUnaryExpression(node,typeChecker);default:return undefined}}const parseArrayLiteralNode=(node,isArgument=false)=>{if(node.kind!==ts.SyntaxKind.ArrayLiteralExpression){if(ts.isPropertyAssignment(node.parent)||ts.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 []};
11
+ function createDefaultMapFromCDN(options,prefix,tsLib){const fsMap=new Map;const files=knownLibFilesForCompilerOptions(options,tsLib);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&ts.SymbolFlags.Alias){symbol=typeChecker.getAliasedSymbol(symbol);}return symbol}function getExportedNodes(program,sourceFile){const exportedNodes=new Map;if(!program||!sourceFile){return exportedNodes}const typeChecker=program.getTypeChecker();const moduleSymbol=typeChecker.getSymbolAtLocation(sourceFile);if(!moduleSymbol){return exportedNodes}const exportedSymbols=typeChecker.getExportsOfModule(moduleSymbol);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(ts.isClassDeclaration(node)){return true}const name=node.name;if(name&&ts.isIdentifier(name)){const symbol=typeChecker.getSymbolAtLocation(name);if(symbol){const resolvedSymbol=resolveAliasedSymbol(typeChecker,symbol);if(resolvedSymbol.declarations){return resolvedSymbol.declarations.some(ts.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);}ts.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===ts.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(ts.isClassDeclaration);if(!superClassDeclaration){break}superClasses.push(superClassDeclaration);currentClass=superClassDeclaration;}return superClasses}function getJSDocTags(node,typeChecker){const tags=[];if(ts.isClassDeclaration(node)||ts.isInterfaceDeclaration(node)){const heritageChain=getSuperClasses(node,typeChecker).filter(classNode=>!classNode.getSourceFile().isDeclarationFile);heritageChain.forEach(classNode=>{const classMembers=classNode.members;classMembers.forEach(classMember=>{const isSetter=ts.isSetAccessor(classMember);if(ts.isPropertyDeclaration(classMember)||isSetter||ts.isPropertySignature(classMember)){const memberName=classMember.name&&ts.isIdentifier(classMember.name)?classMember.name.text:"unnamed";let member=classMember;let hasJsDoc=member.jsDoc?.length>0;if(isSetter&&!hasJsDoc){const symbol=typeChecker.getSymbolAtLocation(classMember.name);member=symbol.declarations.find(accessor=>accessor.jsDoc?.length>0);hasJsDoc=!!member;}if(hasJsDoc){tags.push({memberName,member});}}});});}else if(ts.isObjectLiteralExpression(node)){node.properties.forEach(property=>{if(ts.isPropertyAssignment(property)){const memberName=property.name&&(ts.isIdentifier(property.name)||ts.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=>ts.isEnumDeclaration(decl));let isEnumMember=false;if(!isEnumType){isEnumMember=type.symbol?.declarations?.some(decl=>ts.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(ts.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===ts.SyntaxKind.Identifier){const type=typeChecker.getTypeAtLocation(node);const array=typeChecker.isArrayType(type);const checker=typeChecker;const actualType=array?checker.getElementTypeOfArrayType(type):type;const baseType=typeChecker.getBaseTypeOfLiteralType(actualType);let isMixedUnion=false;const unionTypes=baseType.types;if(Array.isArray(unionTypes)){const primitiveKinds=new Set(unionTypes.map(t=>typeChecker.getBaseTypeOfLiteralType(t)));primitiveKinds.delete(null);isMixedUnion=primitiveKinds.size>1;}const target=baseType.target;const generic=target&&target!==baseType&&target.objectFlags&ts.ObjectFlags.Class?target:null;const name=getPrimitiveEnumType(baseType,typeChecker)??generic?.symbol.name??typeChecker.typeToString(baseType);return {type:generic??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=ts.isNumericLiteral(node);const isUnaryExpression=ts.isPrefixUnaryExpression(node)&&(node.operator===ts.SyntaxKind.MinusToken||node.operator===ts.SyntaxKind.PlusToken)&&node.operand.kind===ts.SyntaxKind.NumericLiteral;return isNumericLiteral||isUnaryExpression};const parseFloatNode=(node,isArgument=false)=>{if(!isNumericLiteral(node)){if(ts.isPropertyAssignment(node.parent)||ts.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(ts.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(ts.isPropertyAssignment(declaration)&&declaration.initializer){return getLiteralValue(declaration.initializer,typeChecker)}if(ts.isVariableDeclaration(declaration)&&declaration.initializer){return getLiteralValue(declaration.initializer,typeChecker)}if(ts.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 ts.SyntaxKind.PlusToken:return +operandValue;case ts.SyntaxKind.MinusToken:return -operandValue;case ts.SyntaxKind.ExclamationToken:return !operandValue;case ts.SyntaxKind.TildeToken:return ~operandValue}}return undefined};function handleObjectLiteral(node,typeChecker){const obj={};node.properties.forEach(prop=>{if(ts.isPropertyAssignment(prop)){const key=prop.name.getText();const value=getLiteralValue(prop.initializer,typeChecker);obj[key]=value;}else if(ts.isShorthandPropertyAssignment(prop)){const key=prop.name.getText();const value=resolveIdentifier(prop.name,typeChecker);obj[key]=value;}});return obj}function getLiteralValue(node,typeChecker){if(!node)return undefined;if(typeChecker){const type=typeChecker.getTypeAtLocation(node);if(type&&type.isLiteral()){return type.value}}const isBooleanLiteral=node.kind===ts.SyntaxKind.TrueKeyword||node.kind===ts.SyntaxKind.FalseKeyword;if(ts.isLiteralExpression(node)||isBooleanLiteral){if(ts.isStringLiteral(node)){return node.text}if(ts.isNumericLiteral(node)){return Number(node.text)}if(node.kind===ts.SyntaxKind.TrueKeyword){return true}if(node.kind===ts.SyntaxKind.FalseKeyword){return false}}switch(node.kind){case ts.SyntaxKind.NullKeyword:return null;case ts.SyntaxKind.ArrayLiteralExpression:return node.elements.map(element=>getLiteralValue(element,typeChecker));case ts.SyntaxKind.ObjectLiteralExpression:return handleObjectLiteral(node,typeChecker);case ts.SyntaxKind.Identifier:return resolveIdentifier(node,typeChecker);case ts.SyntaxKind.PropertyAccessExpression:return resolvePropertyAccess(node,typeChecker);case ts.SyntaxKind.ParenthesizedExpression:return getLiteralValue(node.expression,typeChecker);case ts.SyntaxKind.PrefixUnaryExpression:return evaluatePrefixUnaryExpression(node,typeChecker);default:return undefined}}const parseArrayLiteralNode=(node,isArgument=false)=>{if(node.kind!==ts.SyntaxKind.ArrayLiteralExpression){if(ts.isPropertyAssignment(node.parent)||ts.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 []};
12
12
 
13
13
  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===DiagnosticCategory.Error);if(typeErrors.length>0){const errorMessage=typeErrors[0].messageText.toString();throw new Error(`Type Validation Error: ${errorMessage}`)}return true}
14
14
 
15
15
  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=ts.getTextOfJSDocComment(node.jsDoc?.[0]?.comment)??"";const jsDocTags=node.jsDoc?.[0]?.tags??[];jsDocTags.forEach(tag=>{const tagName=tag.tagName.text;let commentText=(ts.getTextOfJSDocComment(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{let startPos=tag.getStart();let endPos=tag.getEnd();const fullText=tag.getText();const commentText=(ts.getTextOfJSDocComment(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 nameNode=node.name;const name=nameNode&&ts.isIdentifier(nameNode)&&nameNode.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}}const attribute={type,name,typeName,array,value,enum:enums};return attribute}getEnumMembers(node,_errors){let typeNode=null;let members=[];if(node.type){typeNode=node;}else {const jsDocs=ts.getJSDocTags(node);const typeTag=jsDocs.find(doc=>doc.tagName.text==="type");if(typeTag&&ts.isJSDocTypeTag(typeTag)){typeNode=typeTag.typeExpression;}}const type=typeNode&&(typeNode.type.elementType??typeNode.type);if(typeNode&&ts.isTypeReferenceNode(type)){let symbol=this.typeChecker.getSymbolAtLocation(type.typeName);if(symbol&&symbol.flags&ts.SymbolFlags.Alias){symbol=this.typeChecker.getAliasedSymbol(symbol);}if(symbol){const declarations=symbol.getDeclarations();if(declarations&&declarations.length>0){declarations.forEach(declaration=>{if(ts.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=ts.getJSDocTags(declaration);const enumTag=jsDocs.find(doc=>doc.tagName.text==="enum");if(enumTag&&ts.isJSDocEnumTag(enumTag)){members=members.concat(this.getMembers(declaration.initializer));}});}}}return members}getMembers(node){const members=[];if(ts.isObjectLiteralExpression(node)){node.properties.forEach(property=>{if(ts.isPropertyAssignment(property)){const isValidName=ts.isIdentifier(property.name)||ts.isStringLiteral(property.name);const name=isValidName&&property.name.text;const value=getLiteralValue(property.initializer,this.typeChecker);members.push({[name]:value});}});}return members}}
16
16
 
17
- const createNewExpressionParser=(name,argProcessor,defaultArr=[])=>{return node=>{if(node.kind!==ts.SyntaxKind.NewExpression){if(ts.isPropertyAssignment(node.parent)||ts.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 ts.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=ts.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;const scriptName=opts.scriptName??undefined;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,scriptName,memberName);if(attributeMetadata){const type=attributeMetadata.type;const{typeName}=attributeMetadata;const initializer=member.initializer;if(typeName==="any"){const supportedTypes=Array.from(SUPPORTED_INITIALIZABLE_TYPE_NAMES.keys()).join(", ");let errorMessage;if(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,node.name.getText(),memberName,undefined);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=ts.isJSDocTypedefTag(typeNode);const typeIsInterface=hasTag("interface",typeNode)||ts.isInterfaceDeclaration(typeNode);const isInitialized=!!initializer&&ts.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}`,undefined,node.name.getText(),memberName);}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}`,undefined,node.name.getText(),memberName);}else {error=new ParsingError(member,"Invalid Type",`Attribute is an invalid type. An attribute should be one of the following: ${supportedTypes}`,undefined,node.name.getText(),memberName);}errors.push(error);continue}const members=typeNode.members??typeNode.properties??Array.from(type.symbol.members).map(entry=>entry[1].declarations[0])??[];let commentAttributes;if(typeIsInterface||isInitialized){commentAttributes=this.extractAttributes(typeNode,{errors,requiresAttributeTag:false,depth:depth+1,scriptName});}const nestedAttributes=members.reduce((attributes,prop)=>{const attribute=this.attributeParser.getNodeAsAttribute(prop,errors,scriptName);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}}
17
+ const createNewExpressionParser=(name,argProcessor,defaultArr=[])=>{return node=>{if(node.kind!==ts.SyntaxKind.NewExpression){if(ts.isPropertyAssignment(node.parent)||ts.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 ts.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=ts.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;const scriptName=opts.scriptName??undefined;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,scriptName,memberName);if(attributeMetadata){const type=attributeMetadata.type;const{typeName}=attributeMetadata;const initializer=member.initializer;if(typeName==="any"){const supportedTypes=Array.from(SUPPORTED_INITIALIZABLE_TYPE_NAMES.keys()).join(", ");let errorMessage;if(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,node.name.getText(),memberName,undefined);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];const typeIsJSDocTypeDef=!!typeNode&&ts.isJSDocTypedefTag(typeNode);const typeIsInterface=!!typeNode&&(hasTag("interface",typeNode)||ts.isInterfaceDeclaration(typeNode));const isInitialized=!!initializer&&ts.isObjectLiteralExpression(initializer)&&initializer.properties.length>0;const isJSDocTypeLiteral=!!typeNode&&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}`,undefined,node.name.getText(),memberName);}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}`,undefined,node.name.getText(),memberName);}else {error=new ParsingError(member,"Invalid Type",`Attribute is an invalid type. An attribute should be one of the following: ${supportedTypes}`,undefined,node.name.getText(),memberName);}errors.push(error);continue}const members=typeNode.members??typeNode.properties??Array.from(type.symbol.members).map(entry=>entry[1].declarations[0])??[];let commentAttributes;if(typeIsInterface||isInitialized){commentAttributes=this.extractAttributes(typeNode,{errors,requiresAttributeTag:false,depth:depth+1,scriptName});}const nestedAttributes=members.reduce((attributes,prop)=>{const attribute=this.attributeParser.getNodeAsAttribute(prop,errors,scriptName);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}}
18
18
 
19
19
  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:ts.ScriptTarget.ES2023,module:ts.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(COMPILER_OPTIONS,ts);}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=>ts.isClassDeclaration(node)&&node.name.text==="Script")?.symbol;const isScriptNameMember=member=>ts.isPropertyDeclaration(member)&&ts.isIdentifier(member.name)&&member.name.text==="scriptName"&&isStaticMember(member)&&ts.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:`
20
20
  static scriptName = '${finalName}';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcanvas/attribute-parser",
3
- "version": "1.11.0",
3
+ "version": "1.11.1",
4
4
  "description": "The PlayCanvas Attribute Parser",
5
5
  "keywords": [
6
6
  "playcanvas",
@@ -60,7 +60,7 @@
60
60
  "@types/node": "26.1.0",
61
61
  "chai": "5.3.3",
62
62
  "mocha": "11.7.6",
63
- "playcanvas": "2.12.3",
63
+ "playcanvas": "2.23.3",
64
64
  "prettier": "3.9.4",
65
65
  "publint": "0.3.21",
66
66
  "rollup": "4.62.2"