@generaltranslation/python-extractor 0.2.20 → 0.2.21
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/constants.js +22 -17
- package/dist/constants.js.map +1 -0
- package/dist/extractCalls.js +188 -271
- package/dist/extractCalls.js.map +1 -0
- package/dist/extractImports.js +61 -76
- package/dist/extractImports.js.map +1 -0
- package/dist/index.js +50 -52
- package/dist/index.js.map +1 -0
- package/dist/parseStringExpression.js +722 -983
- package/dist/parseStringExpression.js.map +1 -0
- package/dist/parser.js +30 -37
- package/dist/parser.js.map +1 -0
- package/dist/resolveFunctionVariants.js +154 -181
- package/dist/resolveFunctionVariants.js.map +1 -0
- package/dist/resolveImport.js +48 -60
- package/dist/resolveImport.js.map +1 -0
- package/dist/stringNode.js +32 -46
- package/dist/stringNode.js.map +1 -0
- package/package.json +7 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseStringExpression.js","names":[],"sources":["../src/parseStringExpression.ts"],"sourcesContent":["import fs from 'node:fs';\nimport type { SyntaxNode } from './parser.js';\nimport { getParser } from './parser.js';\nimport type { StringNode } from './stringNode.js';\nimport type { ImportAlias } from './extractImports.js';\nimport {\n PYTHON_DERIVE,\n PYTHON_DECLARE_STATIC,\n PYTHON_DECLARE_VAR,\n} from './constants.js';\nimport {\n resolveFunctionInCurrentFile,\n resolveFunctionInFile,\n} from './resolveFunctionVariants.js';\nimport { extractImports } from './extractImports.js';\nimport { resolveImportPath } from './resolveImport.js';\nimport { declareVar } from 'generaltranslation/internal';\n\ntype ParseContext = {\n rootNode: SyntaxNode;\n imports: ImportAlias[];\n filePath: string;\n errors: string[];\n};\n\n/**\n * Returns true if the original import name is derive() or declare_static() (deprecated).\n */\nfunction isDeriveFunction(originalName: string | null): boolean {\n return (\n originalName === PYTHON_DERIVE || originalName === PYTHON_DECLARE_STATIC\n );\n}\n\n/**\n * Checks if an expression contains derive/declare_static or declare_var calls.\n */\nexport function containsStaticCalls(\n node: SyntaxNode,\n imports: ImportAlias[]\n): boolean {\n const staticNames = getDeriveImportNames(imports);\n if (staticNames.size === 0) return false;\n return hasDeriveCallRecursive(node, staticNames);\n}\n\nfunction hasDeriveCallRecursive(node: SyntaxNode, names: Set<string>): boolean {\n if (node.type === 'call') {\n const funcNode = node.childForFieldName('function');\n if (\n funcNode &&\n funcNode.type === 'identifier' &&\n names.has(funcNode.text)\n ) {\n return true;\n }\n }\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (child && hasDeriveCallRecursive(child, names)) return true;\n }\n return false;\n}\n\n/**\n * Parses the first argument of t() into a StringNode tree.\n * Handles: plain strings, f-strings with derive/declare_var,\n * binary + concatenation, and standalone derive calls.\n */\nexport async function parseStringExpression(\n node: SyntaxNode,\n ctx: ParseContext\n): Promise<StringNode | null> {\n // Parenthesized expression: unwrap and recurse\n if (node.type === 'parenthesized_expression') {\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (child && child.type !== '(' && child.type !== ')') {\n return parseStringExpression(child, ctx);\n }\n }\n return null;\n }\n\n // Plain string (no f-string)\n if (node.type === 'string' && !isFString(node)) {\n const content = extractStringContent(node);\n if (content === undefined) return null;\n return { type: 'text', text: content };\n }\n\n // F-string with interpolations\n if (node.type === 'string' && isFString(node)) {\n return parseFString(node, ctx);\n }\n\n // Binary operator: string concatenation with +\n if (node.type === 'binary_operator') {\n return parseBinaryOperator(node, ctx);\n }\n\n // Standalone call: derive/declare_static(...)\n if (node.type === 'call') {\n const funcNode = node.childForFieldName('function');\n if (funcNode && funcNode.type === 'identifier') {\n const originalName = getOriginalImportName(funcNode.text, ctx.imports);\n if (isDeriveFunction(originalName)) {\n return resolveDeclareStaticArg(node, ctx);\n }\n if (originalName === PYTHON_DECLARE_VAR) {\n return resolveDeclareVarArg(node, ctx);\n }\n }\n }\n\n ctx.errors.push(\n `${locationStr(node)}: unsupported expression type \"${node.type}\" in translation call`\n );\n return null;\n}\n\n/**\n * Parses an f-string into a StringNode tree.\n * string_content → text nodes, interpolation → check for derive/declare_var\n */\nasync function parseFString(\n node: SyntaxNode,\n ctx: ParseContext\n): Promise<StringNode | null> {\n const parts: StringNode[] = [];\n\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (!child) continue;\n\n if (child.type === 'string_content') {\n if (child.text.length > 0) {\n parts.push({ type: 'text', text: child.text });\n }\n continue;\n }\n\n if (child.type === 'interpolation') {\n const result = await parseInterpolation(child, ctx);\n if (result) {\n parts.push(result);\n }\n continue;\n }\n\n // Skip string_start, string_end, etc.\n }\n\n if (parts.length === 0) return { type: 'text', text: '' };\n if (parts.length === 1) return parts[0];\n return { type: 'sequence', nodes: parts };\n}\n\n/**\n * Parses an interpolation within an f-string.\n * Must be a derive() or declare_var() call.\n */\nasync function parseInterpolation(\n interpNode: SyntaxNode,\n ctx: ParseContext\n): Promise<StringNode | null> {\n // Find the expression inside the interpolation (skip { and })\n let expr: SyntaxNode | null = null;\n for (let i = 0; i < interpNode.childCount; i++) {\n const child = interpNode.child(i);\n if (\n child &&\n child.type !== '{' &&\n child.type !== '}' &&\n child.type !== 'type_conversion' &&\n child.type !== 'format_specifier'\n ) {\n expr = child;\n break;\n }\n }\n\n if (!expr) {\n ctx.errors.push(\n `${locationStr(interpNode)}: empty interpolation in f-string`\n );\n return null;\n }\n\n if (expr.type === 'call') {\n const funcNode = expr.childForFieldName('function');\n if (funcNode && funcNode.type === 'identifier') {\n const originalName = getOriginalImportName(funcNode.text, ctx.imports);\n if (isDeriveFunction(originalName)) {\n return resolveDeclareStaticArg(expr, ctx);\n }\n if (originalName === PYTHON_DECLARE_VAR) {\n return resolveDeclareVarArg(expr, ctx);\n }\n }\n }\n\n // Not a derive/declare_var call — error\n ctx.errors.push(\n `${locationStr(interpNode)}: f-string interpolation must use derive() or declare_var(), got \"${expr.text}\"`\n );\n return null;\n}\n\n/**\n * Parses binary + concatenation into a sequence node.\n */\nasync function parseBinaryOperator(\n node: SyntaxNode,\n ctx: ParseContext\n): Promise<StringNode | null> {\n const left = node.childForFieldName('left');\n const operator = node.childForFieldName('operator');\n const right = node.childForFieldName('right');\n\n if (!left || !right) {\n ctx.errors.push(`${locationStr(node)}: binary operator missing operands`);\n return null;\n }\n\n // Verify it's a + operator\n if (operator && operator.text !== '+') {\n ctx.errors.push(\n `${locationStr(node)}: unsupported binary operator \"${operator.text}\" in translation call`\n );\n return null;\n }\n\n const leftNode = await parseStringExpression(left, ctx);\n const rightNode = await parseStringExpression(right, ctx);\n\n if (!leftNode || !rightNode) return null;\n\n // Flatten nested sequences\n const parts: StringNode[] = [];\n if (leftNode.type === 'sequence') {\n parts.push(...leftNode.nodes);\n } else {\n parts.push(leftNode);\n }\n if (rightNode.type === 'sequence') {\n parts.push(...rightNode.nodes);\n } else {\n parts.push(rightNode);\n }\n\n return { type: 'sequence', nodes: parts };\n}\n\n/**\n * Resolves the argument of a derive() call into a StringNode.\n * Handles: string literals, ternary expressions, function calls.\n */\nasync function resolveDeclareStaticArg(\n callNode: SyntaxNode,\n ctx: ParseContext\n): Promise<StringNode | null> {\n const arg = getFirstPositionalArg(callNode);\n if (!arg) {\n ctx.errors.push(\n `${locationStr(callNode)}: derive() / declare_static() requires an argument`\n );\n return null;\n }\n\n return resolveStaticValue(arg, ctx);\n}\n\n/**\n * Resolves a value expression that should produce string variants.\n * Handles: string literals, ternary, function calls, binary concat,\n * and declare_var() calls (nested inside derive).\n */\nasync function resolveStaticValue(\n node: SyntaxNode,\n ctx: ParseContext\n): Promise<StringNode | null> {\n // Parenthesized expression: unwrap and recurse\n if (node.type === 'parenthesized_expression') {\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (child && child.type !== '(' && child.type !== ')') {\n return resolveStaticValue(child, ctx);\n }\n }\n return null;\n }\n\n // String literal\n if (node.type === 'string' && !isFString(node)) {\n const content = extractStringContent(node);\n if (content === undefined) return null;\n return { type: 'text', text: content };\n }\n\n // Ternary / conditional expression: \"day\" if cond else \"night\"\n if (node.type === 'conditional_expression') {\n return resolveConditional(node, ctx);\n }\n\n // Binary operator: string concatenation with +\n if (node.type === 'binary_operator') {\n return resolveStaticBinaryOperator(node, ctx);\n }\n\n // Function call — could be a user function or declare_var()\n if (node.type === 'call') {\n const funcNode = node.childForFieldName('function');\n if (funcNode && funcNode.type === 'identifier') {\n const originalName = getOriginalImportName(funcNode.text, ctx.imports);\n if (originalName === PYTHON_DECLARE_VAR) {\n return resolveDeclareVarArg(node, ctx);\n }\n }\n return resolveFunctionCall(node, ctx);\n }\n\n // Identifier: resolve to its assigned value\n if (node.type === 'identifier') {\n const result = await resolveIdentifier(node, ctx);\n if (result) return result;\n\n ctx.errors.push(\n `${locationStr(node)}: could not resolve identifier \"${node.text}\" to a static value`\n );\n return null;\n }\n\n // Subscript: dictionary access like LABELS[score] — returns all values as choices\n if (node.type === 'subscript') {\n return resolveSubscript(node, ctx);\n }\n\n // Attribute: dictionary access like obj.attr — returns the specific value\n if (node.type === 'attribute') {\n return resolveAttribute(node, ctx);\n }\n\n ctx.errors.push(\n `${locationStr(node)}: unsupported derive() argument type \"${node.type}\"`\n );\n return null;\n}\n\n/**\n * Handles binary + concatenation within a static value context.\n */\nasync function resolveStaticBinaryOperator(\n node: SyntaxNode,\n ctx: ParseContext\n): Promise<StringNode | null> {\n const left = node.childForFieldName('left');\n const right = node.childForFieldName('right');\n\n if (!left || !right) {\n ctx.errors.push(`${locationStr(node)}: binary operator missing operands`);\n return null;\n }\n\n // Verify it's a + operator\n const operator = node.childForFieldName('operator');\n if (operator && operator.text !== '+') {\n ctx.errors.push(\n `${locationStr(node)}: unsupported binary operator \"${operator.text}\" in static expression`\n );\n return null;\n }\n\n const leftNode = await resolveStaticValue(left, ctx);\n const rightNode = await resolveStaticValue(right, ctx);\n\n if (!leftNode || !rightNode) return null;\n\n // Flatten nested sequences\n const parts: StringNode[] = [];\n if (leftNode.type === 'sequence') {\n parts.push(...leftNode.nodes);\n } else {\n parts.push(leftNode);\n }\n if (rightNode.type === 'sequence') {\n parts.push(...rightNode.nodes);\n } else {\n parts.push(rightNode);\n }\n\n return { type: 'sequence', nodes: parts };\n}\n\n/**\n * Resolves a Python conditional expression (ternary):\n * \"day\" if cond else \"night\"\n * tree-sitter: conditional_expression → [consequent, if, condition, else, alternate]\n */\nasync function resolveConditional(\n node: SyntaxNode,\n ctx: ParseContext\n): Promise<StringNode | null> {\n // In Python's tree-sitter, the conditional_expression fields are:\n // body = consequent (the value if true)\n // condition = the test\n // alternative = the else value (named 'alternative' field)\n // But field names vary by tree-sitter version. Let's use positional children.\n // Structure: consequent \"if\" condition \"else\" alternative\n\n // The tree-sitter Python grammar uses named children:\n // body (first expression), if keyword, condition, else keyword, alternative\n // But let's find them by field name first, then fall back to positional.\n\n // Try using children directly: first non-keyword child is consequent,\n // child after \"else\" keyword is alternate\n let consequent: SyntaxNode | null = null;\n let alternate: SyntaxNode | null = null;\n let seenElse = false;\n\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (!child) continue;\n\n if (child.type === 'if') continue;\n if (child.type === 'else') {\n seenElse = true;\n continue;\n }\n\n if (!seenElse && !consequent) {\n consequent = child;\n } else if (seenElse && !alternate) {\n alternate = child;\n }\n }\n\n if (!consequent || !alternate) {\n ctx.errors.push(\n `${locationStr(node)}: could not parse conditional expression`\n );\n return null;\n }\n\n // Recursively resolve both branches (handles nested ternaries)\n const consequentNode = await resolveStaticValue(consequent, ctx);\n const alternateNode = await resolveStaticValue(alternate, ctx);\n\n if (!consequentNode || !alternateNode) return null;\n\n // Flatten choices\n const branches: StringNode[] = [];\n if (consequentNode.type === 'choice') {\n branches.push(...consequentNode.nodes);\n } else {\n branches.push(consequentNode);\n }\n if (alternateNode.type === 'choice') {\n branches.push(...alternateNode.nodes);\n } else {\n branches.push(alternateNode);\n }\n\n return { type: 'choice', nodes: branches };\n}\n\n/**\n * Resolves a function call to its string return variants.\n * Looks up the function locally, then in imported files.\n */\nasync function resolveFunctionCall(\n callNode: SyntaxNode,\n ctx: ParseContext\n): Promise<StringNode | null> {\n const funcNode = callNode.childForFieldName('function');\n if (!funcNode || funcNode.type !== 'identifier') {\n ctx.errors.push(\n `${locationStr(callNode)}: cannot resolve non-identifier function call`\n );\n return null;\n }\n\n const funcName = funcNode.text;\n\n // Expression parser callback for resolving return expressions.\n // Receives the actual rootNode and filePath from whichever file\n // the function is defined in (handles re-exports correctly).\n const exprParser = (\n node: SyntaxNode,\n targetRootNode: SyntaxNode,\n targetFilePath: string\n ) => {\n const targetImports = extractImportsFromRoot(targetRootNode, ctx.imports);\n return resolveStaticValue(node, {\n rootNode: targetRootNode,\n imports: targetImports,\n filePath: targetFilePath,\n errors: ctx.errors,\n });\n };\n\n // Try resolving in current file\n const localResult = await resolveFunctionInCurrentFile(\n funcName,\n ctx.rootNode,\n ctx.filePath,\n exprParser\n );\n if (localResult) return localResult;\n\n // Try resolving from imports (follows re-export chains automatically)\n const importInfo = findImportForName(funcName, ctx);\n if (importInfo) {\n const result = await resolveFunctionInFile(\n importInfo.originalName,\n importInfo.filePath,\n exprParser\n );\n if (result) return result;\n }\n\n ctx.errors.push(\n `${locationStr(callNode)}: could not resolve function \"${funcName}\" to string return values`\n );\n return null;\n}\n\n/**\n * Extracts GT import aliases from a target file's root node.\n * Merges with parent imports for GT package functions (declare_var, etc.)\n * that may not be imported in the target file.\n */\nfunction extractImportsFromRoot(\n rootNode: SyntaxNode,\n parentImports: ImportAlias[]\n): ImportAlias[] {\n // Extract GT-only imports from the target file using the same\n // filtering logic as the main extractImports (filters by GT packages)\n const fileImports = extractImports(rootNode);\n\n // Carry over GT declare_* imports from the calling context\n // (in case the helper file doesn't import them directly)\n const parentDeclareImports = parentImports.filter(\n (imp) =>\n isDeriveFunction(imp.originalName) ||\n imp.originalName === PYTHON_DECLARE_VAR\n );\n\n // Deduplicate: prefer the target file's own imports over parent's\n const seen = new Set(fileImports.map((imp) => imp.localName));\n const merged = [...fileImports];\n for (const imp of parentDeclareImports) {\n if (!seen.has(imp.localName)) {\n merged.push(imp);\n }\n }\n\n return merged;\n}\n\n/**\n * Resolves the argument of a declare_var() call.\n * Produces ICU placeholder text using declareVar from generaltranslation.\n */\nasync function resolveDeclareVarArg(\n callNode: SyntaxNode,\n ctx: ParseContext\n): Promise<StringNode | null> {\n const argsNode = callNode.childForFieldName('arguments');\n if (!argsNode) {\n ctx.errors.push(\n `${locationStr(callNode)}: declare_var() requires arguments`\n );\n return null;\n }\n\n // Get the first positional arg (the variable - we use empty string since it's runtime)\n const firstArg = getFirstPositionalArg(callNode);\n if (!firstArg) {\n ctx.errors.push(\n `${locationStr(callNode)}: declare_var() requires a variable argument`\n );\n return null;\n }\n\n // Extract optional _name kwarg\n let nameOption: string | undefined;\n for (let i = 0; i < argsNode.childCount; i++) {\n const child = argsNode.child(i);\n if (!child || child.type !== 'keyword_argument') continue;\n\n const nameNode = child.childForFieldName('name');\n const valueNode = child.childForFieldName('value');\n if (!nameNode || !valueNode) continue;\n\n if (nameNode.text === '_name') {\n if (valueNode.type === 'string' && !isFString(valueNode)) {\n nameOption = extractStringContent(valueNode);\n }\n }\n }\n\n // Use declareVar with empty string for the runtime variable value\n const options = nameOption ? { $name: nameOption } : undefined;\n const icuText = declareVar('', options);\n\n return { type: 'text', text: icuText };\n}\n\n// ===== Constant / Dictionary Resolution ===== //\n\n/**\n * Finds a top-level assignment `name = <value>` in the given root node.\n * Returns the right-hand side (value) node, or null if not found.\n */\nfunction findConstantAssignment(\n name: string,\n rootNode: SyntaxNode\n): SyntaxNode | null {\n for (let i = 0; i < rootNode.childCount; i++) {\n const child = rootNode.child(i);\n if (!child || child.type !== 'expression_statement') continue;\n const expr = child.child(0);\n if (!expr || expr.type !== 'assignment') continue;\n const left = expr.childForFieldName('left');\n const right = expr.childForFieldName('right');\n if (left?.type === 'identifier' && left.text === name && right) {\n return right;\n }\n }\n return null;\n}\n\n/**\n * Guard against infinite recursion when resolving identifier chains.\n * Tracks variable names currently being resolved to detect circular references.\n */\nconst resolvingIdentifiers = new Set<string>();\n\n/**\n * Resolves an identifier to its static value by finding the assignment\n * in the current file or cross-file via imports.\n */\nasync function resolveIdentifier(\n node: SyntaxNode,\n ctx: ParseContext\n): Promise<StringNode | null> {\n const name = node.text;\n\n // Guard against circular references (e.g., x = y; y = x)\n const guardKey = `${ctx.filePath}::${name}`;\n if (resolvingIdentifiers.has(guardKey)) {\n return null;\n }\n\n resolvingIdentifiers.add(guardKey);\n try {\n // Try local assignment first\n const localValue = findConstantAssignment(name, ctx.rootNode);\n if (localValue) {\n return await resolveStaticValue(localValue, ctx);\n }\n\n // Try cross-file via imports\n const importInfo = findImportForName(name, ctx);\n if (importInfo) {\n let source: string;\n try {\n source = fs.readFileSync(importInfo.filePath, 'utf8');\n } catch {\n return null;\n }\n\n const parser = await getParser();\n const tree = parser.parse(source);\n if (!tree) return null;\n\n const externalValue = findConstantAssignment(\n importInfo.originalName,\n tree.rootNode\n );\n if (externalValue) {\n const externalImports = extractImportsFromRoot(\n tree.rootNode,\n ctx.imports\n );\n return await resolveStaticValue(externalValue, {\n rootNode: tree.rootNode,\n imports: externalImports,\n filePath: importInfo.filePath,\n errors: ctx.errors,\n });\n }\n }\n\n return null;\n } finally {\n resolvingIdentifiers.delete(guardKey);\n }\n}\n\n/**\n * Finds a dictionary assignment and returns the dictionary node.\n * Searches locally first, then cross-file via imports.\n * Returns the dictionary node and the context (rootNode, filePath, imports)\n * for resolving values within it.\n */\nasync function findDictionaryAssignment(\n name: string,\n ctx: ParseContext\n): Promise<{ dictNode: SyntaxNode; valueCtx: ParseContext } | null> {\n // Try local assignment\n const localValue = findConstantAssignment(name, ctx.rootNode);\n if (\n localValue &&\n (localValue.type === 'dictionary' || localValue.type === 'list')\n ) {\n return { dictNode: localValue, valueCtx: ctx };\n }\n\n // Try cross-file\n const importInfo = findImportForName(name, ctx);\n if (importInfo) {\n let source: string;\n try {\n source = fs.readFileSync(importInfo.filePath, 'utf8');\n } catch {\n return null;\n }\n\n const parser = await getParser();\n const tree = parser.parse(source);\n if (!tree) return null;\n\n const externalValue = findConstantAssignment(\n importInfo.originalName,\n tree.rootNode\n );\n if (\n externalValue &&\n (externalValue.type === 'dictionary' || externalValue.type === 'list')\n ) {\n const externalImports = extractImportsFromRoot(\n tree.rootNode,\n ctx.imports\n );\n return {\n dictNode: externalValue,\n valueCtx: {\n rootNode: tree.rootNode,\n imports: externalImports,\n filePath: importInfo.filePath,\n errors: ctx.errors,\n },\n };\n }\n }\n\n return null;\n}\n\n// ===== Dictionary Entry Collection & Nesting Helpers ===== //\n\ninterface DictEntry {\n key: string | null;\n valueNode: SyntaxNode;\n}\n\ninterface ResolvedDict {\n dictNode: SyntaxNode;\n valueCtx: ParseContext;\n}\n\n/**\n * Collects all key-value entries from a dictionary node,\n * including entries from spread sources (**base).\n */\nasync function collectDictEntries(\n dictNode: SyntaxNode,\n ctx: ParseContext\n): Promise<DictEntry[]> {\n const entries: DictEntry[] = [];\n\n for (let i = 0; i < dictNode.childCount; i++) {\n const child = dictNode.child(i);\n if (!child) continue;\n\n if (child.type === 'pair') {\n const keyNode = child.childForFieldName('key');\n const valueNode = child.childForFieldName('value');\n if (!valueNode) continue;\n\n let key: string | null = null;\n if (keyNode) {\n if (keyNode.type === 'string' && !isFString(keyNode)) {\n key = extractStringContent(keyNode) ?? null;\n } else if (keyNode.type === 'identifier') {\n key = keyNode.text;\n } else if (keyNode.type === 'integer') {\n key = keyNode.text;\n }\n }\n entries.push({ key, valueNode });\n } else if (child.type === 'dictionary_splat') {\n // Get the spread source expression (child after **)\n let splatExpr: SyntaxNode | null = null;\n for (let j = 0; j < child.childCount; j++) {\n const splatChild = child.child(j);\n if (splatChild && splatChild.type !== '**') {\n splatExpr = splatChild;\n break;\n }\n }\n if (!splatExpr || splatExpr.type !== 'identifier') continue;\n\n const name = splatExpr.text;\n\n // Try local first\n const localDict = findConstantAssignment(name, ctx.rootNode);\n if (localDict && localDict.type === 'dictionary') {\n entries.push(...(await collectDictEntries(localDict, ctx)));\n } else {\n // Try cross-file\n const importInfo = findImportForName(name, ctx);\n if (importInfo) {\n let source: string;\n try {\n source = fs.readFileSync(importInfo.filePath, 'utf8');\n } catch {\n continue;\n }\n const parser = await getParser();\n const tree = parser.parse(source);\n if (!tree) continue;\n const externalValue = findConstantAssignment(\n importInfo.originalName,\n tree.rootNode\n );\n if (externalValue && externalValue.type === 'dictionary') {\n const externalImports = extractImportsFromRoot(\n tree.rootNode,\n ctx.imports\n );\n const externalCtx: ParseContext = {\n rootNode: tree.rootNode,\n imports: externalImports,\n filePath: importInfo.filePath,\n errors: ctx.errors,\n };\n entries.push(\n ...(await collectDictEntries(externalValue, externalCtx))\n );\n }\n }\n }\n }\n }\n\n return entries;\n}\n\n/**\n * Collects all elements from a list node as DictEntry[] with index as key.\n * Handles list_splat (*spread).\n */\nasync function collectListEntries(\n listNode: SyntaxNode,\n ctx: ParseContext\n): Promise<DictEntry[]> {\n const entries: DictEntry[] = [];\n let index = 0;\n for (let i = 0; i < listNode.childCount; i++) {\n const child = listNode.child(i);\n if (!child) continue;\n // Skip brackets and commas\n if (child.type === '[' || child.type === ']' || child.type === ',')\n continue;\n\n if (child.type === 'list_splat') {\n // *base spread — resolve the source identifier\n let splatExpr: SyntaxNode | null = null;\n for (let j = 0; j < child.childCount; j++) {\n const sc = child.child(j);\n if (sc && sc.type !== '*') {\n splatExpr = sc;\n break;\n }\n }\n if (!splatExpr || splatExpr.type !== 'identifier') continue;\n\n const localList = findConstantAssignment(splatExpr.text, ctx.rootNode);\n if (localList && localList.type === 'list') {\n const spreadEntries = await collectListEntries(localList, ctx);\n for (const e of spreadEntries) {\n entries.push({ key: String(index++), valueNode: e.valueNode });\n }\n } else {\n // Try cross-file\n const importInfo = findImportForName(splatExpr.text, ctx);\n if (importInfo) {\n let source: string;\n try {\n source = fs.readFileSync(importInfo.filePath, 'utf8');\n } catch {\n continue;\n }\n const parser = await getParser();\n const tree = parser.parse(source);\n if (!tree) continue;\n const externalValue = findConstantAssignment(\n importInfo.originalName,\n tree.rootNode\n );\n if (externalValue && externalValue.type === 'list') {\n const externalImports = extractImportsFromRoot(\n tree.rootNode,\n ctx.imports\n );\n const externalCtx: ParseContext = {\n rootNode: tree.rootNode,\n imports: externalImports,\n filePath: importInfo.filePath,\n errors: ctx.errors,\n };\n const spreadEntries = await collectListEntries(\n externalValue,\n externalCtx\n );\n for (const e of spreadEntries) {\n entries.push({ key: String(index++), valueNode: e.valueNode });\n }\n }\n }\n }\n continue;\n }\n\n // Regular element — any expression\n entries.push({ key: String(index), valueNode: child });\n index++;\n }\n return entries;\n}\n\n/**\n * Resolves an expression to dictionary AST node(s).\n * Handles identifier, subscript chains, and attribute chains.\n */\nasync function resolveToDictNodes(\n node: SyntaxNode,\n ctx: ParseContext\n): Promise<ResolvedDict[]> {\n // Case 1: identifier — base case\n if (node.type === 'identifier') {\n const result = await findDictionaryAssignment(node.text, ctx);\n if (result) return [result];\n return [];\n }\n\n // Case 2: subscript (e.g., D[\"a\"] in D[\"a\"][\"x\"])\n if (node.type === 'subscript') {\n const valueNode = node.childForFieldName('value');\n if (!valueNode) return [];\n\n const parentDicts = await resolveToDictNodes(valueNode, ctx);\n if (parentDicts.length === 0) return [];\n\n const subscriptKey = node.childForFieldName('subscript');\n if (!subscriptKey) return [];\n\n // Check if key is a static string literal\n const isStaticKey =\n subscriptKey.type === 'string' && !isFString(subscriptKey);\n const staticKeyValue = isStaticKey\n ? extractStringContent(subscriptKey)\n : null;\n\n const isStaticIntKey = subscriptKey.type === 'integer';\n const staticIntKeyValue = isStaticIntKey ? subscriptKey.text : null;\n\n const results: ResolvedDict[] = [];\n for (const parent of parentDicts) {\n const entries =\n parent.dictNode.type === 'list'\n ? await collectListEntries(parent.dictNode, parent.valueCtx)\n : await collectDictEntries(parent.dictNode, parent.valueCtx);\n\n if (staticKeyValue != null || staticIntKeyValue != null) {\n const keyToMatch = staticKeyValue ?? staticIntKeyValue;\n // Static: narrow to matching keys\n for (const entry of entries) {\n if (\n entry.key === keyToMatch &&\n (entry.valueNode.type === 'dictionary' ||\n entry.valueNode.type === 'list')\n ) {\n results.push({\n dictNode: entry.valueNode,\n valueCtx: parent.valueCtx,\n });\n }\n }\n } else {\n // Dynamic: collect ALL entries whose values are dicts/lists\n for (const entry of entries) {\n if (\n entry.valueNode.type === 'dictionary' ||\n entry.valueNode.type === 'list'\n ) {\n results.push({\n dictNode: entry.valueNode,\n valueCtx: parent.valueCtx,\n });\n }\n }\n }\n }\n return results;\n }\n\n // Case 3: attribute (e.g., D.a in D.a.x)\n if (node.type === 'attribute') {\n const objectNode = node.childForFieldName('object');\n const attrNode = node.childForFieldName('attribute');\n if (!objectNode || !attrNode) return [];\n\n const parentDicts = await resolveToDictNodes(objectNode, ctx);\n if (parentDicts.length === 0) return [];\n\n const attrName = attrNode.text;\n const results: ResolvedDict[] = [];\n for (const parent of parentDicts) {\n const entries = await collectDictEntries(\n parent.dictNode,\n parent.valueCtx\n );\n for (const entry of entries) {\n if (\n entry.key === attrName &&\n (entry.valueNode.type === 'dictionary' ||\n entry.valueNode.type === 'list')\n ) {\n results.push({\n dictNode: entry.valueNode,\n valueCtx: parent.valueCtx,\n });\n }\n }\n }\n return results;\n }\n\n return [];\n}\n\n/**\n * Resolves a subscript expression (e.g., `LABELS[score]` or `D[\"a\"][\"x\"]`)\n * by extracting values from the resolved dictionary.\n * Supports nested access chains and spread resolution.\n */\nasync function resolveSubscript(\n node: SyntaxNode,\n ctx: ParseContext\n): Promise<StringNode | null> {\n const valueNode = node.childForFieldName('value');\n if (!valueNode) {\n ctx.errors.push(`${locationStr(node)}: subscript missing value`);\n return null;\n }\n\n // Resolve the object to dict node(s) — supports nesting\n const dicts = await resolveToDictNodes(valueNode, ctx);\n if (dicts.length === 0) {\n ctx.errors.push(\n `${locationStr(node)}: could not find dictionary or list for \"${valueNode.text}\"`\n );\n return null;\n }\n\n const subscriptKey = node.childForFieldName('subscript');\n const isStaticStringKey =\n subscriptKey?.type === 'string' && !isFString(subscriptKey);\n const staticStringKeyValue = isStaticStringKey\n ? extractStringContent(subscriptKey!)\n : null;\n const isStaticIntKey = subscriptKey?.type === 'integer';\n const staticIntKeyValue = isStaticIntKey ? subscriptKey!.text : null;\n const staticKeyValue = staticStringKeyValue ?? staticIntKeyValue;\n\n const branches: StringNode[] = [];\n for (const { dictNode, valueCtx } of dicts) {\n const entries =\n dictNode.type === 'list'\n ? await collectListEntries(dictNode, valueCtx)\n : await collectDictEntries(dictNode, valueCtx);\n\n if (staticKeyValue != null) {\n // Static key: resolve matching values (no break — collect all for spread overrides)\n for (const entry of entries) {\n if (entry.key === staticKeyValue) {\n const resolved = await resolveStaticValue(entry.valueNode, valueCtx);\n if (resolved) {\n if (resolved.type === 'choice') {\n branches.push(...resolved.nodes);\n } else {\n branches.push(resolved);\n }\n }\n }\n }\n } else {\n // Dynamic key: extract ALL values\n for (const entry of entries) {\n const resolved = await resolveStaticValue(entry.valueNode, valueCtx);\n if (resolved) {\n if (resolved.type === 'choice') {\n branches.push(...resolved.nodes);\n } else {\n branches.push(resolved);\n }\n }\n }\n }\n }\n\n if (branches.length === 0) {\n ctx.errors.push(\n `${locationStr(node)}: collection has no resolvable values`\n );\n return null;\n }\n\n if (branches.length === 1) return branches[0];\n return { type: 'choice', nodes: branches };\n}\n\n/**\n * Resolves an attribute access expression (e.g., `obj.attr` or `obj.a.b`)\n * by finding the specific dictionary pair with a matching key.\n * Supports nested access chains and spread resolution.\n */\nasync function resolveAttribute(\n node: SyntaxNode,\n ctx: ParseContext\n): Promise<StringNode | null> {\n const objectNode = node.childForFieldName('object');\n const attrNode = node.childForFieldName('attribute');\n\n if (!objectNode || !attrNode) {\n ctx.errors.push(\n `${locationStr(node)}: attribute access missing object or attribute`\n );\n return null;\n }\n\n const attrName = attrNode.text;\n\n // Resolve the object to dict node(s) — supports nesting\n const dicts = await resolveToDictNodes(objectNode, ctx);\n if (dicts.length === 0) {\n ctx.errors.push(\n `${locationStr(node)}: could not find dictionary or list for \"${objectNode.text}\"`\n );\n return null;\n }\n\n const branches: StringNode[] = [];\n for (const { dictNode, valueCtx } of dicts) {\n const entries = await collectDictEntries(dictNode, valueCtx);\n for (const entry of entries) {\n if (entry.key === attrName) {\n const resolved = await resolveStaticValue(entry.valueNode, valueCtx);\n if (resolved) {\n if (resolved.type === 'choice') {\n branches.push(...resolved.nodes);\n } else {\n branches.push(resolved);\n }\n }\n }\n }\n }\n\n if (branches.length === 0) {\n ctx.errors.push(\n `${locationStr(node)}: could not find key \"${attrName}\" in dictionary or list`\n );\n return null;\n }\n\n if (branches.length === 1) return branches[0];\n return { type: 'choice', nodes: branches };\n}\n\n// ===== Helpers ===== //\n\nfunction getFirstPositionalArg(callNode: SyntaxNode): SyntaxNode | null {\n const argsNode = callNode.childForFieldName('arguments');\n if (!argsNode) return null;\n\n for (let i = 0; i < argsNode.childCount; i++) {\n const child = argsNode.child(i);\n if (\n child &&\n child.type !== '(' &&\n child.type !== ')' &&\n child.type !== ',' &&\n child.type !== 'keyword_argument'\n ) {\n return child;\n }\n }\n return null;\n}\n\nfunction getOriginalImportName(\n localName: string,\n imports: ImportAlias[]\n): string | null {\n for (const imp of imports) {\n if (imp.localName === localName) {\n return imp.originalName;\n }\n }\n return null;\n}\n\nfunction getDeriveImportNames(imports: ImportAlias[]): Set<string> {\n const names = new Set<string>();\n for (const imp of imports) {\n if (\n isDeriveFunction(imp.originalName) ||\n imp.originalName === PYTHON_DECLARE_VAR\n ) {\n names.add(imp.localName);\n }\n }\n return names;\n}\n\n/**\n * Finds import info for a given local name (for cross-file function resolution).\n * Only looks at non-GT imports (user function imports).\n */\nfunction findImportForName(\n localName: string,\n ctx: ParseContext\n): { originalName: string; filePath: string } | null {\n // Walk the AST to find import_from_statement nodes\n for (let i = 0; i < ctx.rootNode.childCount; i++) {\n const node = ctx.rootNode.child(i);\n if (!node || node.type !== 'import_from_statement') continue;\n\n const moduleName = getModuleName(node);\n if (!moduleName) continue;\n\n // Check all imported names in this statement\n for (let j = 0; j < node.childCount; j++) {\n const child = node.child(j);\n if (!child) continue;\n\n if (child.type === 'aliased_import') {\n const nameNode = child.childForFieldName('name');\n const aliasNode = child.childForFieldName('alias');\n const importedName = nameNode?.text;\n const alias = aliasNode?.text ?? importedName;\n if (alias === localName && importedName) {\n const filePath = resolveImportPath(moduleName, ctx.filePath);\n if (filePath) {\n return { originalName: importedName, filePath };\n }\n }\n } else if (child.type === 'dotted_name') {\n if (child.text === moduleName) continue; // Skip module name itself\n if (child.text === localName) {\n const filePath = resolveImportPath(moduleName, ctx.filePath);\n if (filePath) {\n return { originalName: localName, filePath };\n }\n }\n }\n }\n }\n\n return null;\n}\n\nfunction getModuleName(importNode: SyntaxNode): string | undefined {\n const moduleNode = importNode.childForFieldName('module_name');\n if (moduleNode) return moduleNode.text;\n\n for (let i = 0; i < importNode.childCount; i++) {\n const child = importNode.child(i);\n if (!child) continue;\n if (child.type === 'import') break;\n if (child.type === 'dotted_name') return child.text;\n if (child.type === 'relative_import') return child.text;\n }\n return undefined;\n}\n\nfunction isFString(stringNode: SyntaxNode): boolean {\n for (let i = 0; i < stringNode.childCount; i++) {\n const child = stringNode.child(i);\n if (child && child.type === 'string_start') {\n return /^[fF]/.test(child.text);\n }\n if (child && child.type === 'interpolation') {\n return true;\n }\n }\n return false;\n}\n\nfunction extractStringContent(stringNode: SyntaxNode): string | undefined {\n for (let i = 0; i < stringNode.childCount; i++) {\n const child = stringNode.child(i);\n if (child && child.type === 'string_content') {\n return child.text;\n }\n }\n\n let hasStart = false;\n let hasEnd = false;\n for (let i = 0; i < stringNode.childCount; i++) {\n const child = stringNode.child(i);\n if (child?.type === 'string_start') hasStart = true;\n if (child?.type === 'string_end') hasEnd = true;\n }\n if (hasStart && hasEnd) return '';\n\n return undefined;\n}\n\nfunction locationStr(node: SyntaxNode): string {\n return `line ${node.startPosition.row + 1}, col ${node.startPosition.column}`;\n}\n"],"mappings":"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"}
|
package/dist/parser.js
CHANGED
|
@@ -1,45 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createRequire } from
|
|
1
|
+
import { Language, Parser } from "web-tree-sitter";
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
//#region src/parser.ts
|
|
3
4
|
let parserPromise = null;
|
|
4
5
|
const require = createRequire(import.meta.url);
|
|
5
|
-
const isBun = typeof process !==
|
|
6
|
+
const isBun = typeof process !== "undefined" && !!process.versions.bun;
|
|
6
7
|
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
return parserPromise;
|
|
8
|
+
* Lazily initializes and returns a singleton tree-sitter Parser
|
|
9
|
+
* configured for Python.
|
|
10
|
+
*/
|
|
11
|
+
function getParser() {
|
|
12
|
+
if (!parserPromise) parserPromise = initParser();
|
|
13
|
+
return parserPromise;
|
|
15
14
|
}
|
|
16
15
|
async function initParser() {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const wasmPath = await resolveWasmPath('tree-sitter-python/tree-sitter-python.wasm');
|
|
28
|
-
const Python = await Language.load(wasmPath);
|
|
29
|
-
parser.setLanguage(Python);
|
|
30
|
-
return parser;
|
|
16
|
+
const parserWasmPath = await resolveWasmPath("web-tree-sitter/web-tree-sitter.wasm");
|
|
17
|
+
await Parser.init({ locateFile(path) {
|
|
18
|
+
if (path === "web-tree-sitter.wasm") return parserWasmPath;
|
|
19
|
+
return path;
|
|
20
|
+
} });
|
|
21
|
+
const parser = new Parser();
|
|
22
|
+
const wasmPath = await resolveWasmPath("tree-sitter-python/tree-sitter-python.wasm");
|
|
23
|
+
const Python = await Language.load(wasmPath);
|
|
24
|
+
parser.setLanguage(Python);
|
|
25
|
+
return parser;
|
|
31
26
|
}
|
|
32
27
|
async function resolveWasmPath(specifier) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
case 'tree-sitter-python/tree-sitter-python.wasm':
|
|
40
|
-
return (await import('tree-sitter-python/tree-sitter-python.wasm'))
|
|
41
|
-
.default;
|
|
42
|
-
default:
|
|
43
|
-
throw new Error(`Unknown WASM specifier for Bun: ${specifier}`);
|
|
44
|
-
}
|
|
28
|
+
if (!isBun) return require.resolve(specifier);
|
|
29
|
+
switch (specifier) {
|
|
30
|
+
case "web-tree-sitter/web-tree-sitter.wasm": return (await import("web-tree-sitter/web-tree-sitter.wasm")).default;
|
|
31
|
+
case "tree-sitter-python/tree-sitter-python.wasm": return (await import("tree-sitter-python/tree-sitter-python.wasm")).default;
|
|
32
|
+
default: throw new Error(`Unknown WASM specifier for Bun: ${specifier}`);
|
|
33
|
+
}
|
|
45
34
|
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { getParser };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","names":[],"sources":["../src/parser.ts"],"sourcesContent":["import { Parser, Language } from 'web-tree-sitter';\nimport { createRequire } from 'module';\n\nlet parserPromise: Promise<Parser> | null = null;\nconst require = createRequire(import.meta.url);\nconst isBun = typeof process !== 'undefined' && !!process.versions.bun;\n\n/**\n * Lazily initializes and returns a singleton tree-sitter Parser\n * configured for Python.\n */\nexport function getParser(): Promise<Parser> {\n if (!parserPromise) {\n parserPromise = initParser();\n }\n return parserPromise;\n}\n\nasync function initParser(): Promise<Parser> {\n const parserWasmPath = await resolveWasmPath(\n 'web-tree-sitter/web-tree-sitter.wasm'\n );\n\n await Parser.init({\n locateFile(path: string) {\n if (path === 'web-tree-sitter.wasm') {\n return parserWasmPath;\n }\n return path;\n },\n });\n const parser = new Parser();\n\n const wasmPath = await resolveWasmPath(\n 'tree-sitter-python/tree-sitter-python.wasm'\n );\n const Python = await Language.load(wasmPath);\n parser.setLanguage(Python);\n\n return parser;\n}\n\nasync function resolveWasmPath(specifier: string): Promise<string> {\n if (!isBun) {\n return require.resolve(specifier);\n }\n\n switch (specifier) {\n case 'web-tree-sitter/web-tree-sitter.wasm':\n return (await import('web-tree-sitter/web-tree-sitter.wasm')).default;\n case 'tree-sitter-python/tree-sitter-python.wasm':\n return (await import('tree-sitter-python/tree-sitter-python.wasm'))\n .default;\n default:\n throw new Error(`Unknown WASM specifier for Bun: ${specifier}`);\n }\n}\n\nexport type { Parser };\nexport { type Tree, type Node as SyntaxNode } from 'web-tree-sitter';\n"],"mappings":";;;AAGA,IAAI,gBAAwC;AAC5C,MAAM,UAAU,cAAc,OAAO,KAAK,IAAI;AAC9C,MAAM,QAAQ,OAAO,YAAY,eAAe,CAAC,CAAC,QAAQ,SAAS;;;;;AAMnE,SAAgB,YAA6B;AAC3C,KAAI,CAAC,cACH,iBAAgB,YAAY;AAE9B,QAAO;;AAGT,eAAe,aAA8B;CAC3C,MAAM,iBAAiB,MAAM,gBAC3B,uCACD;AAED,OAAM,OAAO,KAAK,EAChB,WAAW,MAAc;AACvB,MAAI,SAAS,uBACX,QAAO;AAET,SAAO;IAEV,CAAC;CACF,MAAM,SAAS,IAAI,QAAQ;CAE3B,MAAM,WAAW,MAAM,gBACrB,6CACD;CACD,MAAM,SAAS,MAAM,SAAS,KAAK,SAAS;AAC5C,QAAO,YAAY,OAAO;AAE1B,QAAO;;AAGT,eAAe,gBAAgB,WAAoC;AACjE,KAAI,CAAC,MACH,QAAO,QAAQ,QAAQ,UAAU;AAGnC,SAAQ,WAAR;EACE,KAAK,uCACH,SAAQ,MAAM,OAAO,yCAAyC;EAChE,KAAK,6CACH,SAAQ,MAAM,OAAO,+CAClB;EACL,QACE,OAAM,IAAI,MAAM,mCAAmC,YAAY"}
|
|
@@ -1,200 +1,173 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
|
|
1
|
+
import { getParser } from "./parser.js";
|
|
2
|
+
import { resolveImportPath } from "./resolveImport.js";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
//#region src/resolveFunctionVariants.ts
|
|
5
|
+
const crossFileCache = /* @__PURE__ */ new Map();
|
|
5
6
|
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return extractReturnVariants(funcDef, rootNode, filePath, parseExpr);
|
|
7
|
+
* Resolves all return values of a function defined in the current file's AST.
|
|
8
|
+
* Uses the provided expression parser to handle complex return expressions
|
|
9
|
+
* (concat, declare_var, etc.).
|
|
10
|
+
*/
|
|
11
|
+
async function resolveFunctionInCurrentFile(functionName, rootNode, filePath, parseExpr) {
|
|
12
|
+
const funcDef = findFunctionDefinition(rootNode, functionName);
|
|
13
|
+
if (!funcDef) return null;
|
|
14
|
+
return extractReturnVariants(funcDef, rootNode, filePath, parseExpr);
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (reExportInfo) {
|
|
57
|
-
const reResult = await resolveFunctionInFile(reExportInfo.originalName, reExportInfo.filePath, parseExpr, visitedSet);
|
|
58
|
-
crossFileCache.set(cacheKey, reResult);
|
|
59
|
-
return reResult;
|
|
60
|
-
}
|
|
61
|
-
crossFileCache.set(cacheKey, null);
|
|
62
|
-
return null;
|
|
17
|
+
* Resolves all return values of a function defined in an external file.
|
|
18
|
+
* Follows re-export chains: if the function isn't defined in the target file
|
|
19
|
+
* but is imported from another module, follows the import to the source.
|
|
20
|
+
* Results are cached by filePath::functionName.
|
|
21
|
+
*/
|
|
22
|
+
async function resolveFunctionInFile(functionName, filePath, parseExpr, visited) {
|
|
23
|
+
const cacheKey = `${filePath}::${functionName}`;
|
|
24
|
+
if (crossFileCache.has(cacheKey)) return crossFileCache.get(cacheKey);
|
|
25
|
+
const visitedSet = visited ?? /* @__PURE__ */ new Set();
|
|
26
|
+
if (visitedSet.has(cacheKey)) {
|
|
27
|
+
crossFileCache.set(cacheKey, null);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
visitedSet.add(cacheKey);
|
|
31
|
+
let source;
|
|
32
|
+
try {
|
|
33
|
+
source = fs.readFileSync(filePath, "utf8");
|
|
34
|
+
} catch {
|
|
35
|
+
crossFileCache.set(cacheKey, null);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const tree = (await getParser()).parse(source);
|
|
39
|
+
if (!tree) {
|
|
40
|
+
crossFileCache.set(cacheKey, null);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const result = await resolveFunctionInCurrentFile(functionName, tree.rootNode, filePath, parseExpr);
|
|
44
|
+
if (result) {
|
|
45
|
+
crossFileCache.set(cacheKey, result);
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
const reExportInfo = findReExport(functionName, tree.rootNode, filePath);
|
|
49
|
+
if (reExportInfo) {
|
|
50
|
+
const reResult = await resolveFunctionInFile(reExportInfo.originalName, reExportInfo.filePath, parseExpr, visitedSet);
|
|
51
|
+
crossFileCache.set(cacheKey, reResult);
|
|
52
|
+
return reResult;
|
|
53
|
+
}
|
|
54
|
+
crossFileCache.set(cacheKey, null);
|
|
55
|
+
return null;
|
|
63
56
|
}
|
|
64
57
|
/**
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
* Finds a top-level function_definition by name in the AST.
|
|
59
|
+
*/
|
|
67
60
|
function findFunctionDefinition(rootNode, name) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return defNode;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return null;
|
|
61
|
+
for (let i = 0; i < rootNode.childCount; i++) {
|
|
62
|
+
const child = rootNode.child(i);
|
|
63
|
+
if (!child) continue;
|
|
64
|
+
if (child.type === "function_definition") {
|
|
65
|
+
const nameNode = child.childForFieldName("name");
|
|
66
|
+
if (nameNode && nameNode.text === name) return child;
|
|
67
|
+
}
|
|
68
|
+
if (child.type === "decorated_definition") {
|
|
69
|
+
const defNode = child.childForFieldName("definition");
|
|
70
|
+
if (defNode && defNode.type === "function_definition") {
|
|
71
|
+
const nameNode = defNode.childForFieldName("name");
|
|
72
|
+
if (nameNode && nameNode.text === name) return defNode;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
90
77
|
}
|
|
91
78
|
/**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
79
|
+
* Extracts all return values from a function body and parses them into StringNodes.
|
|
80
|
+
* Skips nested function definitions.
|
|
81
|
+
*/
|
|
95
82
|
async function extractReturnVariants(funcDef, rootNode, filePath, parseExpr) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (nodes.length === 1)
|
|
113
|
-
return nodes[0];
|
|
114
|
-
return { type: 'choice', nodes };
|
|
83
|
+
const body = funcDef.childForFieldName("body");
|
|
84
|
+
if (!body) return null;
|
|
85
|
+
const returnExprs = [];
|
|
86
|
+
collectReturnExpressions(body, returnExprs);
|
|
87
|
+
if (returnExprs.length === 0) return null;
|
|
88
|
+
const nodes = [];
|
|
89
|
+
for (const expr of returnExprs) {
|
|
90
|
+
const node = await parseExpr(expr, rootNode, filePath);
|
|
91
|
+
if (node) nodes.push(node);
|
|
92
|
+
}
|
|
93
|
+
if (nodes.length === 0) return null;
|
|
94
|
+
if (nodes.length === 1) return nodes[0];
|
|
95
|
+
return {
|
|
96
|
+
type: "choice",
|
|
97
|
+
nodes
|
|
98
|
+
};
|
|
115
99
|
}
|
|
116
100
|
/**
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
101
|
+
* Recursively collects return expression nodes from a function body,
|
|
102
|
+
* skipping nested function definitions.
|
|
103
|
+
*/
|
|
120
104
|
function collectReturnExpressions(node, results) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
137
|
-
const child = node.child(i);
|
|
138
|
-
if (child)
|
|
139
|
-
collectReturnExpressions(child, results);
|
|
140
|
-
}
|
|
105
|
+
if (node.type === "function_definition") return;
|
|
106
|
+
if (node.type === "return_statement") {
|
|
107
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
108
|
+
const child = node.child(i);
|
|
109
|
+
if (!child || child.type === "return") continue;
|
|
110
|
+
results.push(child);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
116
|
+
const child = node.child(i);
|
|
117
|
+
if (child) collectReturnExpressions(child, results);
|
|
118
|
+
}
|
|
141
119
|
}
|
|
142
120
|
/**
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
121
|
+
* Checks if a function name is re-exported from another module in the given file.
|
|
122
|
+
* e.g., `from derive_test import get_gender` makes `get_gender` a re-export.
|
|
123
|
+
*/
|
|
146
124
|
function findReExport(functionName, rootNode, currentFilePath) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return null;
|
|
125
|
+
for (let i = 0; i < rootNode.childCount; i++) {
|
|
126
|
+
const node = rootNode.child(i);
|
|
127
|
+
if (!node || node.type !== "import_from_statement") continue;
|
|
128
|
+
const moduleName = getModuleName(node);
|
|
129
|
+
if (!moduleName) continue;
|
|
130
|
+
for (let j = 0; j < node.childCount; j++) {
|
|
131
|
+
const child = node.child(j);
|
|
132
|
+
if (!child) continue;
|
|
133
|
+
if (child.type === "dotted_name" && child.text !== moduleName) {
|
|
134
|
+
if (child.text === functionName) {
|
|
135
|
+
const resolved = resolveImportPath(moduleName, currentFilePath);
|
|
136
|
+
if (resolved) return {
|
|
137
|
+
originalName: functionName,
|
|
138
|
+
filePath: resolved
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
} else if (child.type === "aliased_import") {
|
|
142
|
+
const nameNode = child.childForFieldName("name");
|
|
143
|
+
const aliasNode = child.childForFieldName("alias");
|
|
144
|
+
if (((aliasNode === null || aliasNode === void 0 ? void 0 : aliasNode.text) ?? (nameNode === null || nameNode === void 0 ? void 0 : nameNode.text)) === functionName && nameNode) {
|
|
145
|
+
const resolved = resolveImportPath(moduleName, currentFilePath);
|
|
146
|
+
if (resolved) return {
|
|
147
|
+
originalName: nameNode.text,
|
|
148
|
+
filePath: resolved
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
180
155
|
}
|
|
181
156
|
function getModuleName(importNode) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (child.type === 'dotted_name')
|
|
192
|
-
return child.text;
|
|
193
|
-
if (child.type === 'relative_import')
|
|
194
|
-
return child.text;
|
|
195
|
-
}
|
|
196
|
-
return undefined;
|
|
157
|
+
const moduleNode = importNode.childForFieldName("module_name");
|
|
158
|
+
if (moduleNode) return moduleNode.text;
|
|
159
|
+
for (let i = 0; i < importNode.childCount; i++) {
|
|
160
|
+
const child = importNode.child(i);
|
|
161
|
+
if (!child) continue;
|
|
162
|
+
if (child.type === "import") break;
|
|
163
|
+
if (child.type === "dotted_name") return child.text;
|
|
164
|
+
if (child.type === "relative_import") return child.text;
|
|
165
|
+
}
|
|
197
166
|
}
|
|
198
|
-
|
|
199
|
-
|
|
167
|
+
function clearFunctionCache() {
|
|
168
|
+
crossFileCache.clear();
|
|
200
169
|
}
|
|
170
|
+
//#endregion
|
|
171
|
+
export { clearFunctionCache, resolveFunctionInCurrentFile, resolveFunctionInFile };
|
|
172
|
+
|
|
173
|
+
//# sourceMappingURL=resolveFunctionVariants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveFunctionVariants.js","names":[],"sources":["../src/resolveFunctionVariants.ts"],"sourcesContent":["import fs from 'node:fs';\nimport type { SyntaxNode } from './parser.js';\nimport { getParser } from './parser.js';\nimport type { StringNode } from './stringNode.js';\nimport { resolveImportPath } from './resolveImport.js';\n\n/**\n * Callback to parse a return expression into a StringNode.\n * Provided by the caller so function resolution doesn't need to know about\n * declare_var, derive, imports, etc.\n *\n * @param node - The return expression AST node\n * @param rootNode - The root AST node of the file containing the function\n * @param filePath - The absolute path of the file containing the function\n */\nexport type ExpressionParser = (\n node: SyntaxNode,\n rootNode: SyntaxNode,\n filePath: string\n) => Promise<StringNode | null>;\n\nconst crossFileCache = new Map<string, StringNode | null>();\n\n/**\n * Resolves all return values of a function defined in the current file's AST.\n * Uses the provided expression parser to handle complex return expressions\n * (concat, declare_var, etc.).\n */\nexport async function resolveFunctionInCurrentFile(\n functionName: string,\n rootNode: SyntaxNode,\n filePath: string,\n parseExpr: ExpressionParser\n): Promise<StringNode | null> {\n const funcDef = findFunctionDefinition(rootNode, functionName);\n if (!funcDef) return null;\n return extractReturnVariants(funcDef, rootNode, filePath, parseExpr);\n}\n\n/**\n * Resolves all return values of a function defined in an external file.\n * Follows re-export chains: if the function isn't defined in the target file\n * but is imported from another module, follows the import to the source.\n * Results are cached by filePath::functionName.\n */\nexport async function resolveFunctionInFile(\n functionName: string,\n filePath: string,\n parseExpr: ExpressionParser,\n visited?: Set<string>\n): Promise<StringNode | null> {\n const cacheKey = `${filePath}::${functionName}`;\n if (crossFileCache.has(cacheKey)) {\n return crossFileCache.get(cacheKey)!;\n }\n\n // Prevent infinite re-export loops\n const visitedSet = visited ?? new Set<string>();\n if (visitedSet.has(cacheKey)) {\n crossFileCache.set(cacheKey, null);\n return null;\n }\n visitedSet.add(cacheKey);\n\n let source: string;\n try {\n source = fs.readFileSync(filePath, 'utf8');\n } catch {\n crossFileCache.set(cacheKey, null);\n return null;\n }\n\n const parser = await getParser();\n const tree = parser.parse(source);\n if (!tree) {\n crossFileCache.set(cacheKey, null);\n return null;\n }\n\n // Try to find function definition in this file\n const result = await resolveFunctionInCurrentFile(\n functionName,\n tree.rootNode,\n filePath,\n parseExpr\n );\n if (result) {\n crossFileCache.set(cacheKey, result);\n return result;\n }\n\n // Function not defined here — check for re-exports\n const reExportInfo = findReExport(functionName, tree.rootNode, filePath);\n if (reExportInfo) {\n const reResult = await resolveFunctionInFile(\n reExportInfo.originalName,\n reExportInfo.filePath,\n parseExpr,\n visitedSet\n );\n crossFileCache.set(cacheKey, reResult);\n return reResult;\n }\n\n crossFileCache.set(cacheKey, null);\n return null;\n}\n\n/**\n * Finds a top-level function_definition by name in the AST.\n */\nfunction findFunctionDefinition(\n rootNode: SyntaxNode,\n name: string\n): SyntaxNode | null {\n for (let i = 0; i < rootNode.childCount; i++) {\n const child = rootNode.child(i);\n if (!child) continue;\n\n if (child.type === 'function_definition') {\n const nameNode = child.childForFieldName('name');\n if (nameNode && nameNode.text === name) {\n return child;\n }\n }\n\n // Also check decorated definitions\n if (child.type === 'decorated_definition') {\n const defNode = child.childForFieldName('definition');\n if (defNode && defNode.type === 'function_definition') {\n const nameNode = defNode.childForFieldName('name');\n if (nameNode && nameNode.text === name) {\n return defNode;\n }\n }\n }\n }\n return null;\n}\n\n/**\n * Extracts all return values from a function body and parses them into StringNodes.\n * Skips nested function definitions.\n */\nasync function extractReturnVariants(\n funcDef: SyntaxNode,\n rootNode: SyntaxNode,\n filePath: string,\n parseExpr: ExpressionParser\n): Promise<StringNode | null> {\n const body = funcDef.childForFieldName('body');\n if (!body) return null;\n\n const returnExprs: SyntaxNode[] = [];\n collectReturnExpressions(body, returnExprs);\n\n if (returnExprs.length === 0) return null;\n\n // Parse each return expression into a StringNode\n const nodes: StringNode[] = [];\n for (const expr of returnExprs) {\n const node = await parseExpr(expr, rootNode, filePath);\n if (node) nodes.push(node);\n }\n\n if (nodes.length === 0) return null;\n if (nodes.length === 1) return nodes[0];\n\n return { type: 'choice', nodes };\n}\n\n/**\n * Recursively collects return expression nodes from a function body,\n * skipping nested function definitions.\n */\nfunction collectReturnExpressions(\n node: SyntaxNode,\n results: SyntaxNode[]\n): void {\n if (node.type === 'function_definition') {\n // Skip nested function bodies\n return;\n }\n\n if (node.type === 'return_statement') {\n // Get the expression being returned (skip the 'return' keyword)\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (!child || child.type === 'return') continue;\n results.push(child);\n break; // Only one expression per return\n }\n return;\n }\n\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (child) collectReturnExpressions(child, results);\n }\n}\n\n/**\n * Checks if a function name is re-exported from another module in the given file.\n * e.g., `from derive_test import get_gender` makes `get_gender` a re-export.\n */\nfunction findReExport(\n functionName: string,\n rootNode: SyntaxNode,\n currentFilePath: string\n): { originalName: string; filePath: string } | null {\n for (let i = 0; i < rootNode.childCount; i++) {\n const node = rootNode.child(i);\n if (!node || node.type !== 'import_from_statement') continue;\n\n const moduleName = getModuleName(node);\n if (!moduleName) continue;\n\n for (let j = 0; j < node.childCount; j++) {\n const child = node.child(j);\n if (!child) continue;\n\n if (child.type === 'dotted_name' && child.text !== moduleName) {\n if (child.text === functionName) {\n const resolved = resolveImportPath(moduleName, currentFilePath);\n if (resolved) {\n return { originalName: functionName, filePath: resolved };\n }\n }\n } else if (child.type === 'aliased_import') {\n const nameNode = child.childForFieldName('name');\n const aliasNode = child.childForFieldName('alias');\n const alias = aliasNode?.text ?? nameNode?.text;\n if (alias === functionName && nameNode) {\n const resolved = resolveImportPath(moduleName, currentFilePath);\n if (resolved) {\n return { originalName: nameNode.text, filePath: resolved };\n }\n }\n }\n }\n }\n return null;\n}\n\nfunction getModuleName(importNode: SyntaxNode): string | undefined {\n const moduleNode = importNode.childForFieldName('module_name');\n if (moduleNode) return moduleNode.text;\n\n for (let i = 0; i < importNode.childCount; i++) {\n const child = importNode.child(i);\n if (!child) continue;\n if (child.type === 'import') break;\n if (child.type === 'dotted_name') return child.text;\n if (child.type === 'relative_import') return child.text;\n }\n return undefined;\n}\n\nexport function clearFunctionCache(): void {\n crossFileCache.clear();\n}\n"],"mappings":"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"}
|