@generaltranslation/python-extractor 0.2.22 → 0.2.23-odysseus.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,11 +3,9 @@ export declare const PYTHON_GT_DEPENDENCIES: readonly ["gt-flask", "gt-fastapi"]
3
3
  export declare const PYTHON_T_FUNCTION = "t";
4
4
  export declare const PYTHON_MSG_FUNCTION = "msg";
5
5
  export declare const PYTHON_DERIVE = "derive";
6
- /** @deprecated Use PYTHON_DERIVE instead */
7
- export declare const PYTHON_DECLARE_STATIC = "declare_static";
8
6
  export declare const PYTHON_DECLARE_VAR = "declare_var";
9
7
  /** These imported names are tracked (translation functions + derive helpers) */
10
- export declare const PYTHON_TRANSLATION_FUNCTIONS: readonly ["t", "msg", "derive", "declare_static", "declare_var"];
8
+ export declare const PYTHON_TRANSLATION_FUNCTIONS: readonly ["t", "msg", "derive", "declare_var"];
11
9
  export declare const PYTHON_METADATA_KWARGS: {
12
10
  readonly _id: "id";
13
11
  readonly _context: "context";
package/dist/constants.js CHANGED
@@ -4,15 +4,12 @@ const PYTHON_GT_DEPENDENCIES = ["gt-flask", "gt-fastapi"];
4
4
  const PYTHON_T_FUNCTION = "t";
5
5
  const PYTHON_MSG_FUNCTION = "msg";
6
6
  const PYTHON_DERIVE = "derive";
7
- /** @deprecated Use PYTHON_DERIVE instead */
8
- const PYTHON_DECLARE_STATIC = "declare_static";
9
7
  const PYTHON_DECLARE_VAR = "declare_var";
10
8
  /** These imported names are tracked (translation functions + derive helpers) */
11
9
  const PYTHON_TRANSLATION_FUNCTIONS = [
12
10
  "t",
13
11
  "msg",
14
12
  "derive",
15
- "declare_static",
16
13
  "declare_var"
17
14
  ];
18
15
  const PYTHON_METADATA_KWARGS = {
@@ -21,6 +18,6 @@ const PYTHON_METADATA_KWARGS = {
21
18
  _max_chars: "maxChars"
22
19
  };
23
20
  //#endregion
24
- export { PYTHON_DECLARE_STATIC, PYTHON_DECLARE_VAR, PYTHON_DERIVE, PYTHON_GT_DEPENDENCIES, PYTHON_GT_PACKAGES, PYTHON_METADATA_KWARGS, PYTHON_MSG_FUNCTION, PYTHON_TRANSLATION_FUNCTIONS, PYTHON_T_FUNCTION };
21
+ export { PYTHON_DECLARE_VAR, PYTHON_DERIVE, PYTHON_GT_DEPENDENCIES, PYTHON_GT_PACKAGES, PYTHON_METADATA_KWARGS, PYTHON_MSG_FUNCTION, PYTHON_TRANSLATION_FUNCTIONS, PYTHON_T_FUNCTION };
25
22
 
26
23
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","names":[],"sources":["../src/constants.ts"],"sourcesContent":["export const PYTHON_GT_PACKAGES = ['gt_flask', 'gt_fastapi'] as const;\nexport const PYTHON_GT_DEPENDENCIES = ['gt-flask', 'gt-fastapi'] as const;\nexport const PYTHON_T_FUNCTION = 't';\nexport const PYTHON_MSG_FUNCTION = 'msg';\nexport const PYTHON_DERIVE = 'derive';\n/** @deprecated Use PYTHON_DERIVE instead */\nexport const PYTHON_DECLARE_STATIC = 'declare_static';\nexport const PYTHON_DECLARE_VAR = 'declare_var';\n/** These imported names are tracked (translation functions + derive helpers) */\nexport const PYTHON_TRANSLATION_FUNCTIONS = [\n 't',\n 'msg',\n 'derive',\n 'declare_static',\n 'declare_var',\n] as const;\nexport const PYTHON_METADATA_KWARGS = {\n _id: 'id',\n _context: 'context',\n _max_chars: 'maxChars',\n} as const;\n"],"mappings":";AAAA,MAAa,qBAAqB,CAAC,YAAY,aAAa;AAC5D,MAAa,yBAAyB,CAAC,YAAY,aAAa;AAChE,MAAa,oBAAoB;AACjC,MAAa,sBAAsB;AACnC,MAAa,gBAAgB;;AAE7B,MAAa,wBAAwB;AACrC,MAAa,qBAAqB;;AAElC,MAAa,+BAA+B;CAC1C;CACA;CACA;CACA;CACA;CACD;AACD,MAAa,yBAAyB;CACpC,KAAK;CACL,UAAU;CACV,YAAY;CACb"}
1
+ {"version":3,"file":"constants.js","names":[],"sources":["../src/constants.ts"],"sourcesContent":["export const PYTHON_GT_PACKAGES = ['gt_flask', 'gt_fastapi'] as const;\nexport const PYTHON_GT_DEPENDENCIES = ['gt-flask', 'gt-fastapi'] as const;\nexport const PYTHON_T_FUNCTION = 't';\nexport const PYTHON_MSG_FUNCTION = 'msg';\nexport const PYTHON_DERIVE = 'derive';\nexport const PYTHON_DECLARE_VAR = 'declare_var';\n/** These imported names are tracked (translation functions + derive helpers) */\nexport const PYTHON_TRANSLATION_FUNCTIONS = [\n 't',\n 'msg',\n 'derive',\n 'declare_var',\n] as const;\nexport const PYTHON_METADATA_KWARGS = {\n _id: 'id',\n _context: 'context',\n _max_chars: 'maxChars',\n} as const;\n"],"mappings":";AAAA,MAAa,qBAAqB,CAAC,YAAY,aAAa;AAC5D,MAAa,yBAAyB,CAAC,YAAY,aAAa;AAChE,MAAa,oBAAoB;AACjC,MAAa,sBAAsB;AACnC,MAAa,gBAAgB;AAC7B,MAAa,qBAAqB;;AAElC,MAAa,+BAA+B;CAC1C;CACA;CACA;CACA;CACD;AACD,MAAa,yBAAyB;CACpC,KAAK;CACL,UAAU;CACV,YAAY;CACb"}
@@ -12,7 +12,7 @@ async function extractCalls(rootNode, imports, filePath) {
12
12
  const calls = [];
13
13
  const errors = [];
14
14
  const warnings = [];
15
- const trackedNames = new Set(imports.filter((imp) => imp.originalName !== "derive" && imp.originalName !== "declare_static" && imp.originalName !== "declare_var").map((imp) => imp.localName));
15
+ const trackedNames = new Set(imports.filter((imp) => imp.originalName !== "derive" && imp.originalName !== "declare_var").map((imp) => imp.localName));
16
16
  if (trackedNames.size === 0) return {
17
17
  calls,
18
18
  errors,
@@ -1 +1 @@
1
- {"version":3,"file":"extractCalls.js","names":[],"sources":["../src/extractCalls.ts"],"sourcesContent":["import type { SyntaxNode } from './parser.js';\nimport type { ImportAlias } from './extractImports.js';\nimport {\n PYTHON_METADATA_KWARGS,\n PYTHON_DERIVE,\n PYTHON_DECLARE_STATIC,\n PYTHON_DECLARE_VAR,\n} from './constants.js';\nimport {\n containsStaticCalls,\n parseStringExpression,\n} from './parseStringExpression.js';\nimport { nodeToStrings } from './stringNode.js';\nimport { indexVars } from 'generaltranslation/internal';\nimport { randomUUID } from 'node:crypto';\n\nexport type RawTranslationCall = {\n source: string;\n id?: string;\n context?: string;\n maxChars?: number;\n staticId?: string;\n line: number;\n column: number;\n};\n\n/**\n * Extracts translation function calls from a Python AST.\n * Walks all `call` nodes and checks if they reference a tracked import.\n */\nexport async function extractCalls(\n rootNode: SyntaxNode,\n imports: ImportAlias[],\n filePath: string\n): Promise<{\n calls: RawTranslationCall[];\n errors: string[];\n warnings: string[];\n}> {\n const calls: RawTranslationCall[] = [];\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // Only track t/msg as translation functions (not derive/declare_static/declare_var)\n const trackedNames = new Set(\n imports\n .filter(\n (imp) =>\n imp.originalName !== PYTHON_DERIVE &&\n imp.originalName !== PYTHON_DECLARE_STATIC &&\n imp.originalName !== PYTHON_DECLARE_VAR\n )\n .map((imp) => imp.localName)\n );\n if (trackedNames.size === 0) return { calls, errors, warnings };\n\n await walkCalls(\n rootNode,\n trackedNames,\n imports,\n filePath,\n calls,\n errors,\n warnings\n );\n\n return { calls, errors, warnings };\n}\n\nasync function walkCalls(\n node: SyntaxNode,\n trackedNames: Set<string>,\n imports: ImportAlias[],\n filePath: string,\n calls: RawTranslationCall[],\n errors: string[],\n warnings: string[]\n): Promise<void> {\n if (node.type === 'call') {\n const funcNode = node.childForFieldName('function');\n if (\n funcNode &&\n funcNode.type === 'identifier' &&\n trackedNames.has(funcNode.text)\n ) {\n await processCall(node, imports, filePath, calls, errors, warnings);\n }\n }\n\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (child)\n await walkCalls(\n child,\n trackedNames,\n imports,\n filePath,\n calls,\n errors,\n warnings\n );\n }\n}\n\nasync function processCall(\n callNode: SyntaxNode,\n imports: ImportAlias[],\n filePath: string,\n calls: RawTranslationCall[],\n errors: string[],\n _warnings: string[]\n): Promise<void> {\n const argsNode = callNode.childForFieldName('arguments');\n if (!argsNode) {\n errors.push(`${locationStr(callNode)}: translation call has no arguments`);\n return;\n }\n\n // Find first positional argument (skip punctuation)\n let firstArg: SyntaxNode | null = null;\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 firstArg = child;\n break;\n }\n }\n\n if (!firstArg) {\n errors.push(\n `${locationStr(callNode)}: translation call has no positional argument`\n );\n return;\n }\n\n // Check if this expression contains declare_static/declare_var\n const hasStaticHelpers =\n (firstArg.type === 'string' &&\n isFString(firstArg) &&\n containsStaticCalls(firstArg, imports)) ||\n (firstArg.type === 'binary_operator' &&\n containsStaticCalls(firstArg, imports)) ||\n (firstArg.type === 'call' && containsStaticCalls(firstArg, imports)) ||\n (firstArg.type === 'parenthesized_expression' &&\n containsStaticCalls(firstArg, imports));\n\n if (hasStaticHelpers) {\n // Compound expression path: parse into StringNode tree\n const rootNode = callNode.tree?.rootNode;\n if (!rootNode) {\n errors.push(`${locationStr(callNode)}: could not access AST root`);\n return;\n }\n\n const stringNode = await parseStringExpression(firstArg, {\n rootNode,\n imports,\n filePath,\n errors,\n });\n\n if (!stringNode) return;\n\n const strings = nodeToStrings(stringNode).map(indexVars);\n if (strings.length === 0) {\n errors.push(`${locationStr(callNode)}: no string variants produced`);\n return;\n }\n\n // Extract static metadata and check for derive in _context\n const metadata = extractKwargs(argsNode, errors, callNode, imports);\n const contextVariants = await extractDeriveContext(\n argsNode,\n imports,\n filePath,\n rootNode,\n errors\n );\n\n const staticId = `static-temp-id-${randomUUID()}`;\n\n if (contextVariants) {\n // Cross-product: content variants × context variants\n for (const source of strings) {\n for (const context of contextVariants) {\n calls.push({\n source,\n ...metadata,\n context,\n staticId,\n line: callNode.startPosition.row + 1,\n column: callNode.startPosition.column,\n });\n }\n }\n } else {\n for (const source of strings) {\n calls.push({\n source,\n ...metadata,\n staticId,\n line: callNode.startPosition.row + 1,\n column: callNode.startPosition.column,\n });\n }\n }\n return;\n }\n\n // Simple path: validate first argument is a plain string literal\n if (firstArg.type !== 'string') {\n if (firstArg.type === 'identifier') {\n errors.push(\n `${locationStr(callNode)}: translation call uses a variable \"${firstArg.text}\" instead of a string literal`\n );\n } else if (firstArg.type === 'concatenated_string') {\n errors.push(\n `${locationStr(callNode)}: translation call uses concatenated strings — use a single string literal`\n );\n } else {\n errors.push(\n `${locationStr(callNode)}: translation call first argument must be a string literal, got \"${firstArg.type}\"`\n );\n }\n return;\n }\n\n // Check for f-strings (without declare_static/declare_var)\n if (isFString(firstArg)) {\n errors.push(\n `${locationStr(callNode)}: translation call uses an f-string — use a plain string literal or derive()/declare_var()`\n );\n return;\n }\n\n const source = extractStringContent(firstArg);\n if (source === undefined) {\n errors.push(`${locationStr(callNode)}: could not extract string content`);\n return;\n }\n\n // Extract keyword arguments and check for derive in _context\n const metadata = extractKwargs(argsNode, errors, callNode, imports);\n\n const rootNode = callNode.tree?.rootNode;\n const contextVariants = rootNode\n ? await extractDeriveContext(argsNode, imports, filePath, rootNode, errors)\n : null;\n\n if (contextVariants) {\n const staticId = `static-temp-id-${randomUUID()}`;\n for (const context of contextVariants) {\n calls.push({\n source,\n ...metadata,\n context,\n staticId,\n line: callNode.startPosition.row + 1,\n column: callNode.startPosition.column,\n });\n }\n } else {\n calls.push({\n source,\n ...metadata,\n line: callNode.startPosition.row + 1,\n column: callNode.startPosition.column,\n });\n }\n}\n\nfunction extractKwargs(\n argsNode: SyntaxNode,\n errors: string[],\n callNode: SyntaxNode,\n imports?: ImportAlias[]\n): { id?: string; context?: string; maxChars?: number } {\n const result: { id?: string; context?: string; maxChars?: number } = {};\n\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 const kwargName = nameNode.text;\n const metadataKey = (\n PYTHON_METADATA_KWARGS as Record<string, string | undefined>\n )[kwargName];\n if (!metadataKey) continue;\n\n if (metadataKey === 'maxChars') {\n if (valueNode.type === 'integer') {\n result.maxChars = parseInt(valueNode.text, 10);\n } else {\n errors.push(\n `${locationStr(callNode)}: _max_chars must be an integer literal`\n );\n }\n } else {\n if (valueNode.type === 'string' && !isFString(valueNode)) {\n const value = extractStringContent(valueNode);\n if (value !== undefined) {\n if (metadataKey === 'id') result.id = value;\n else if (metadataKey === 'context') result.context = value;\n }\n } else if (\n metadataKey === 'context' &&\n imports &&\n containsStaticCalls(valueNode, imports)\n ) {\n // _context contains derive() — skip error, caller handles derivation\n } else {\n errors.push(\n `${locationStr(callNode)}: _${metadataKey} must be a string literal`\n );\n }\n }\n }\n\n return result;\n}\n\n/**\n * Finds the _context keyword argument node and, if it contains a derive() call,\n * parses it into context variants. Returns null if _context is static or absent.\n */\nasync function extractDeriveContext(\n argsNode: SyntaxNode,\n imports: ImportAlias[],\n filePath: string,\n rootNode: SyntaxNode,\n errors: string[]\n): Promise<string[] | null> {\n // Find _context kwarg\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 if (nameNode.text !== '_context') continue;\n\n // Check if value contains derive()\n if (!containsStaticCalls(valueNode, imports)) return null;\n\n const contextNode = await parseStringExpression(valueNode, {\n rootNode,\n imports,\n filePath,\n errors,\n });\n if (!contextNode) return null;\n\n return nodeToStrings(contextNode);\n }\n return null;\n}\n\nfunction isFString(stringNode: SyntaxNode): boolean {\n // Check if string_start begins with 'f' or 'F'\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 // Also check for interpolation children (hallmark of f-strings)\n if (child && child.type === 'interpolation') {\n return true;\n }\n }\n return false;\n}\n\nfunction extractStringContent(stringNode: SyntaxNode): string | undefined {\n // Look for string_content child\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 // Empty string — no string_content child, but has string_start and string_end\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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"}
1
+ {"version":3,"file":"extractCalls.js","names":[],"sources":["../src/extractCalls.ts"],"sourcesContent":["import type { SyntaxNode } from './parser.js';\nimport type { ImportAlias } from './extractImports.js';\nimport {\n PYTHON_METADATA_KWARGS,\n PYTHON_DERIVE,\n PYTHON_DECLARE_VAR,\n} from './constants.js';\nimport {\n containsStaticCalls,\n parseStringExpression,\n} from './parseStringExpression.js';\nimport { nodeToStrings } from './stringNode.js';\nimport { indexVars } from 'generaltranslation/internal';\nimport { randomUUID } from 'node:crypto';\n\nexport type RawTranslationCall = {\n source: string;\n id?: string;\n context?: string;\n maxChars?: number;\n staticId?: string;\n line: number;\n column: number;\n};\n\n/**\n * Extracts translation function calls from a Python AST.\n * Walks all `call` nodes and checks if they reference a tracked import.\n */\nexport async function extractCalls(\n rootNode: SyntaxNode,\n imports: ImportAlias[],\n filePath: string\n): Promise<{\n calls: RawTranslationCall[];\n errors: string[];\n warnings: string[];\n}> {\n const calls: RawTranslationCall[] = [];\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // Only track t/msg as translation functions (not derive/declare_var)\n const trackedNames = new Set(\n imports\n .filter(\n (imp) =>\n imp.originalName !== PYTHON_DERIVE &&\n imp.originalName !== PYTHON_DECLARE_VAR\n )\n .map((imp) => imp.localName)\n );\n if (trackedNames.size === 0) return { calls, errors, warnings };\n\n await walkCalls(\n rootNode,\n trackedNames,\n imports,\n filePath,\n calls,\n errors,\n warnings\n );\n\n return { calls, errors, warnings };\n}\n\nasync function walkCalls(\n node: SyntaxNode,\n trackedNames: Set<string>,\n imports: ImportAlias[],\n filePath: string,\n calls: RawTranslationCall[],\n errors: string[],\n warnings: string[]\n): Promise<void> {\n if (node.type === 'call') {\n const funcNode = node.childForFieldName('function');\n if (\n funcNode &&\n funcNode.type === 'identifier' &&\n trackedNames.has(funcNode.text)\n ) {\n await processCall(node, imports, filePath, calls, errors, warnings);\n }\n }\n\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (child)\n await walkCalls(\n child,\n trackedNames,\n imports,\n filePath,\n calls,\n errors,\n warnings\n );\n }\n}\n\nasync function processCall(\n callNode: SyntaxNode,\n imports: ImportAlias[],\n filePath: string,\n calls: RawTranslationCall[],\n errors: string[],\n _warnings: string[]\n): Promise<void> {\n const argsNode = callNode.childForFieldName('arguments');\n if (!argsNode) {\n errors.push(`${locationStr(callNode)}: translation call has no arguments`);\n return;\n }\n\n // Find first positional argument (skip punctuation)\n let firstArg: SyntaxNode | null = null;\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 firstArg = child;\n break;\n }\n }\n\n if (!firstArg) {\n errors.push(\n `${locationStr(callNode)}: translation call has no positional argument`\n );\n return;\n }\n\n // Check if this expression contains derive/declare_var\n const hasStaticHelpers =\n (firstArg.type === 'string' &&\n isFString(firstArg) &&\n containsStaticCalls(firstArg, imports)) ||\n (firstArg.type === 'binary_operator' &&\n containsStaticCalls(firstArg, imports)) ||\n (firstArg.type === 'call' && containsStaticCalls(firstArg, imports)) ||\n (firstArg.type === 'parenthesized_expression' &&\n containsStaticCalls(firstArg, imports));\n\n if (hasStaticHelpers) {\n // Compound expression path: parse into StringNode tree\n const rootNode = callNode.tree?.rootNode;\n if (!rootNode) {\n errors.push(`${locationStr(callNode)}: could not access AST root`);\n return;\n }\n\n const stringNode = await parseStringExpression(firstArg, {\n rootNode,\n imports,\n filePath,\n errors,\n });\n\n if (!stringNode) return;\n\n const strings = nodeToStrings(stringNode).map(indexVars);\n if (strings.length === 0) {\n errors.push(`${locationStr(callNode)}: no string variants produced`);\n return;\n }\n\n // Extract static metadata and check for derive in _context\n const metadata = extractKwargs(argsNode, errors, callNode, imports);\n const contextVariants = await extractDeriveContext(\n argsNode,\n imports,\n filePath,\n rootNode,\n errors\n );\n\n const staticId = `static-temp-id-${randomUUID()}`;\n\n if (contextVariants) {\n // Cross-product: content variants × context variants\n for (const source of strings) {\n for (const context of contextVariants) {\n calls.push({\n source,\n ...metadata,\n context,\n staticId,\n line: callNode.startPosition.row + 1,\n column: callNode.startPosition.column,\n });\n }\n }\n } else {\n for (const source of strings) {\n calls.push({\n source,\n ...metadata,\n staticId,\n line: callNode.startPosition.row + 1,\n column: callNode.startPosition.column,\n });\n }\n }\n return;\n }\n\n // Simple path: validate first argument is a plain string literal\n if (firstArg.type !== 'string') {\n if (firstArg.type === 'identifier') {\n errors.push(\n `${locationStr(callNode)}: translation call uses a variable \"${firstArg.text}\" instead of a string literal`\n );\n } else if (firstArg.type === 'concatenated_string') {\n errors.push(\n `${locationStr(callNode)}: translation call uses concatenated strings — use a single string literal`\n );\n } else {\n errors.push(\n `${locationStr(callNode)}: translation call first argument must be a string literal, got \"${firstArg.type}\"`\n );\n }\n return;\n }\n\n // Check for f-strings (without derive/declare_var)\n if (isFString(firstArg)) {\n errors.push(\n `${locationStr(callNode)}: translation call uses an f-string — use a plain string literal or derive()/declare_var()`\n );\n return;\n }\n\n const source = extractStringContent(firstArg);\n if (source === undefined) {\n errors.push(`${locationStr(callNode)}: could not extract string content`);\n return;\n }\n\n // Extract keyword arguments and check for derive in _context\n const metadata = extractKwargs(argsNode, errors, callNode, imports);\n\n const rootNode = callNode.tree?.rootNode;\n const contextVariants = rootNode\n ? await extractDeriveContext(argsNode, imports, filePath, rootNode, errors)\n : null;\n\n if (contextVariants) {\n const staticId = `static-temp-id-${randomUUID()}`;\n for (const context of contextVariants) {\n calls.push({\n source,\n ...metadata,\n context,\n staticId,\n line: callNode.startPosition.row + 1,\n column: callNode.startPosition.column,\n });\n }\n } else {\n calls.push({\n source,\n ...metadata,\n line: callNode.startPosition.row + 1,\n column: callNode.startPosition.column,\n });\n }\n}\n\nfunction extractKwargs(\n argsNode: SyntaxNode,\n errors: string[],\n callNode: SyntaxNode,\n imports?: ImportAlias[]\n): { id?: string; context?: string; maxChars?: number } {\n const result: { id?: string; context?: string; maxChars?: number } = {};\n\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 const kwargName = nameNode.text;\n const metadataKey = (\n PYTHON_METADATA_KWARGS as Record<string, string | undefined>\n )[kwargName];\n if (!metadataKey) continue;\n\n if (metadataKey === 'maxChars') {\n if (valueNode.type === 'integer') {\n result.maxChars = parseInt(valueNode.text, 10);\n } else {\n errors.push(\n `${locationStr(callNode)}: _max_chars must be an integer literal`\n );\n }\n } else {\n if (valueNode.type === 'string' && !isFString(valueNode)) {\n const value = extractStringContent(valueNode);\n if (value !== undefined) {\n if (metadataKey === 'id') result.id = value;\n else if (metadataKey === 'context') result.context = value;\n }\n } else if (\n metadataKey === 'context' &&\n imports &&\n containsStaticCalls(valueNode, imports)\n ) {\n // _context contains derive() — skip error, caller handles derivation\n } else {\n errors.push(\n `${locationStr(callNode)}: _${metadataKey} must be a string literal`\n );\n }\n }\n }\n\n return result;\n}\n\n/**\n * Finds the _context keyword argument node and, if it contains a derive() call,\n * parses it into context variants. Returns null if _context is static or absent.\n */\nasync function extractDeriveContext(\n argsNode: SyntaxNode,\n imports: ImportAlias[],\n filePath: string,\n rootNode: SyntaxNode,\n errors: string[]\n): Promise<string[] | null> {\n // Find _context kwarg\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 if (nameNode.text !== '_context') continue;\n\n // Check if value contains derive()\n if (!containsStaticCalls(valueNode, imports)) return null;\n\n const contextNode = await parseStringExpression(valueNode, {\n rootNode,\n imports,\n filePath,\n errors,\n });\n if (!contextNode) return null;\n\n return nodeToStrings(contextNode);\n }\n return null;\n}\n\nfunction isFString(stringNode: SyntaxNode): boolean {\n // Check if string_start begins with 'f' or 'F'\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 // Also check for interpolation children (hallmark of f-strings)\n if (child && child.type === 'interpolation') {\n return true;\n }\n }\n return false;\n}\n\nfunction extractStringContent(stringNode: SyntaxNode): string | undefined {\n // Look for string_content child\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 // Empty string — no string_content child, but has string_start and string_end\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/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { ExtractionResult } from './types.js';
2
2
  export type { ExtractionResult, ExtractionMetadata } from './types.js';
3
3
  export type { ImportAlias } from './extractImports.js';
4
- export { PYTHON_GT_PACKAGES, PYTHON_GT_DEPENDENCIES, PYTHON_T_FUNCTION, PYTHON_MSG_FUNCTION, PYTHON_DERIVE, PYTHON_DECLARE_STATIC, PYTHON_DECLARE_VAR, PYTHON_TRANSLATION_FUNCTIONS, PYTHON_METADATA_KWARGS, } from './constants.js';
4
+ export { PYTHON_GT_PACKAGES, PYTHON_GT_DEPENDENCIES, PYTHON_T_FUNCTION, PYTHON_MSG_FUNCTION, PYTHON_DERIVE, PYTHON_DECLARE_VAR, PYTHON_TRANSLATION_FUNCTIONS, PYTHON_METADATA_KWARGS, } from './constants.js';
5
5
  export declare function extractFromPythonSource(sourceCode: string, filePath: string): Promise<{
6
6
  results: ExtractionResult[];
7
7
  errors: string[];
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { PYTHON_DECLARE_STATIC, PYTHON_DECLARE_VAR, PYTHON_DERIVE, PYTHON_GT_DEPENDENCIES, PYTHON_GT_PACKAGES, PYTHON_METADATA_KWARGS, PYTHON_MSG_FUNCTION, PYTHON_TRANSLATION_FUNCTIONS, PYTHON_T_FUNCTION } from "./constants.js";
1
+ import { PYTHON_DECLARE_VAR, PYTHON_DERIVE, PYTHON_GT_DEPENDENCIES, PYTHON_GT_PACKAGES, PYTHON_METADATA_KWARGS, PYTHON_MSG_FUNCTION, PYTHON_TRANSLATION_FUNCTIONS, PYTHON_T_FUNCTION } from "./constants.js";
2
2
  import { getParser } from "./parser.js";
3
3
  import { extractImports } from "./extractImports.js";
4
4
  import { extractCalls } from "./extractCalls.js";
@@ -50,6 +50,6 @@ function formatError(error) {
50
50
  return error instanceof Error ? error.message : String(error);
51
51
  }
52
52
  //#endregion
53
- export { PYTHON_DECLARE_STATIC, PYTHON_DECLARE_VAR, PYTHON_DERIVE, PYTHON_GT_DEPENDENCIES, PYTHON_GT_PACKAGES, PYTHON_METADATA_KWARGS, PYTHON_MSG_FUNCTION, PYTHON_TRANSLATION_FUNCTIONS, PYTHON_T_FUNCTION, extractFromPythonSource };
53
+ export { PYTHON_DECLARE_VAR, PYTHON_DERIVE, PYTHON_GT_DEPENDENCIES, PYTHON_GT_PACKAGES, PYTHON_METADATA_KWARGS, PYTHON_MSG_FUNCTION, PYTHON_TRANSLATION_FUNCTIONS, PYTHON_T_FUNCTION, extractFromPythonSource };
54
54
 
55
55
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { ExtractionResult } from './types.js';\nimport { getParser } from './parser.js';\nimport { extractImports } from './extractImports.js';\nimport { extractCalls } from './extractCalls.js';\n\nexport type { ExtractionResult, ExtractionMetadata } from './types.js';\nexport type { ImportAlias } from './extractImports.js';\nexport {\n PYTHON_GT_PACKAGES,\n PYTHON_GT_DEPENDENCIES,\n PYTHON_T_FUNCTION,\n PYTHON_MSG_FUNCTION,\n PYTHON_DERIVE,\n PYTHON_DECLARE_STATIC,\n PYTHON_DECLARE_VAR,\n PYTHON_TRANSLATION_FUNCTIONS,\n PYTHON_METADATA_KWARGS,\n} from './constants.js';\n\nexport async function extractFromPythonSource(\n sourceCode: string,\n filePath: string\n): Promise<{\n results: ExtractionResult[];\n errors: string[];\n warnings: string[];\n}> {\n let parser: Awaited<ReturnType<typeof getParser>>;\n try {\n parser = await getParser();\n } catch (error) {\n return {\n results: [],\n errors: [],\n warnings: [\n `${filePath}: Failed to initialize Python parser; skipping Python extraction. ${formatError(error)}`,\n ],\n };\n }\n\n const tree = parser.parse(sourceCode);\n if (!tree) {\n return {\n results: [],\n errors: [`Failed to parse ${filePath}`],\n warnings: [],\n };\n }\n\n // Step 1: Extract GT imports\n const imports = extractImports(tree.rootNode);\n if (imports.length === 0) {\n return { results: [], errors: [], warnings: [] };\n }\n\n // Step 2: Extract translation calls\n const { calls, errors, warnings } = await extractCalls(\n tree.rootNode,\n imports,\n filePath\n );\n\n // Step 3: Map to ExtractionResult\n const results: ExtractionResult[] = calls.map((call) => ({\n dataFormat: 'ICU' as const,\n source: call.source,\n metadata: {\n ...(call.id && { id: call.id }),\n ...(call.context && { context: call.context }),\n ...(call.maxChars != null && { maxChars: call.maxChars }),\n ...(call.staticId && { staticId: call.staticId }),\n filePaths: [filePath],\n },\n }));\n\n return {\n results,\n errors: prefixErrors(errors, filePath),\n warnings: prefixErrors(warnings, filePath),\n };\n}\n\nfunction prefixErrors(messages: string[], filePath: string): string[] {\n return messages.map((msg) => `${filePath}: ${msg}`);\n}\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":";;;;;AAmBA,eAAsB,wBACpB,YACA,UAKC;CACD,IAAI;AACJ,KAAI;AACF,WAAS,MAAM,WAAW;UACnB,OAAO;AACd,SAAO;GACL,SAAS,EAAE;GACX,QAAQ,EAAE;GACV,UAAU,CACR,GAAG,SAAS,oEAAoE,YAAY,MAAM,GACnG;GACF;;CAGH,MAAM,OAAO,OAAO,MAAM,WAAW;AACrC,KAAI,CAAC,KACH,QAAO;EACL,SAAS,EAAE;EACX,QAAQ,CAAC,mBAAmB,WAAW;EACvC,UAAU,EAAE;EACb;CAIH,MAAM,UAAU,eAAe,KAAK,SAAS;AAC7C,KAAI,QAAQ,WAAW,EACrB,QAAO;EAAE,SAAS,EAAE;EAAE,QAAQ,EAAE;EAAE,UAAU,EAAE;EAAE;CAIlD,MAAM,EAAE,OAAO,QAAQ,aAAa,MAAM,aACxC,KAAK,UACL,SACA,SACD;AAeD,QAAO;EACL,SAbkC,MAAM,KAAK,UAAU;GACvD,YAAY;GACZ,QAAQ,KAAK;GACb,UAAU;IACR,GAAI,KAAK,MAAM,EAAE,IAAI,KAAK,IAAI;IAC9B,GAAI,KAAK,WAAW,EAAE,SAAS,KAAK,SAAS;IAC7C,GAAI,KAAK,YAAY,QAAQ,EAAE,UAAU,KAAK,UAAU;IACxD,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,UAAU;IAChD,WAAW,CAAC,SAAS;IACtB;GACF,EAGQ;EACP,QAAQ,aAAa,QAAQ,SAAS;EACtC,UAAU,aAAa,UAAU,SAAS;EAC3C;;AAGH,SAAS,aAAa,UAAoB,UAA4B;AACpE,QAAO,SAAS,KAAK,QAAQ,GAAG,SAAS,IAAI,MAAM;;AAGrD,SAAS,YAAY,OAAwB;AAC3C,QAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { ExtractionResult } from './types.js';\nimport { getParser } from './parser.js';\nimport { extractImports } from './extractImports.js';\nimport { extractCalls } from './extractCalls.js';\n\nexport type { ExtractionResult, ExtractionMetadata } from './types.js';\nexport type { ImportAlias } from './extractImports.js';\nexport {\n PYTHON_GT_PACKAGES,\n PYTHON_GT_DEPENDENCIES,\n PYTHON_T_FUNCTION,\n PYTHON_MSG_FUNCTION,\n PYTHON_DERIVE,\n PYTHON_DECLARE_VAR,\n PYTHON_TRANSLATION_FUNCTIONS,\n PYTHON_METADATA_KWARGS,\n} from './constants.js';\n\nexport async function extractFromPythonSource(\n sourceCode: string,\n filePath: string\n): Promise<{\n results: ExtractionResult[];\n errors: string[];\n warnings: string[];\n}> {\n let parser: Awaited<ReturnType<typeof getParser>>;\n try {\n parser = await getParser();\n } catch (error) {\n return {\n results: [],\n errors: [],\n warnings: [\n `${filePath}: Failed to initialize Python parser; skipping Python extraction. ${formatError(error)}`,\n ],\n };\n }\n\n const tree = parser.parse(sourceCode);\n if (!tree) {\n return {\n results: [],\n errors: [`Failed to parse ${filePath}`],\n warnings: [],\n };\n }\n\n // Step 1: Extract GT imports\n const imports = extractImports(tree.rootNode);\n if (imports.length === 0) {\n return { results: [], errors: [], warnings: [] };\n }\n\n // Step 2: Extract translation calls\n const { calls, errors, warnings } = await extractCalls(\n tree.rootNode,\n imports,\n filePath\n );\n\n // Step 3: Map to ExtractionResult\n const results: ExtractionResult[] = calls.map((call) => ({\n dataFormat: 'ICU' as const,\n source: call.source,\n metadata: {\n ...(call.id && { id: call.id }),\n ...(call.context && { context: call.context }),\n ...(call.maxChars != null && { maxChars: call.maxChars }),\n ...(call.staticId && { staticId: call.staticId }),\n filePaths: [filePath],\n },\n }));\n\n return {\n results,\n errors: prefixErrors(errors, filePath),\n warnings: prefixErrors(warnings, filePath),\n };\n}\n\nfunction prefixErrors(messages: string[], filePath: string): string[] {\n return messages.map((msg) => `${filePath}: ${msg}`);\n}\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":";;;;;AAkBA,eAAsB,wBACpB,YACA,UAKC;CACD,IAAI;AACJ,KAAI;AACF,WAAS,MAAM,WAAW;UACnB,OAAO;AACd,SAAO;GACL,SAAS,EAAE;GACX,QAAQ,EAAE;GACV,UAAU,CACR,GAAG,SAAS,oEAAoE,YAAY,MAAM,GACnG;GACF;;CAGH,MAAM,OAAO,OAAO,MAAM,WAAW;AACrC,KAAI,CAAC,KACH,QAAO;EACL,SAAS,EAAE;EACX,QAAQ,CAAC,mBAAmB,WAAW;EACvC,UAAU,EAAE;EACb;CAIH,MAAM,UAAU,eAAe,KAAK,SAAS;AAC7C,KAAI,QAAQ,WAAW,EACrB,QAAO;EAAE,SAAS,EAAE;EAAE,QAAQ,EAAE;EAAE,UAAU,EAAE;EAAE;CAIlD,MAAM,EAAE,OAAO,QAAQ,aAAa,MAAM,aACxC,KAAK,UACL,SACA,SACD;AAeD,QAAO;EACL,SAbkC,MAAM,KAAK,UAAU;GACvD,YAAY;GACZ,QAAQ,KAAK;GACb,UAAU;IACR,GAAI,KAAK,MAAM,EAAE,IAAI,KAAK,IAAI;IAC9B,GAAI,KAAK,WAAW,EAAE,SAAS,KAAK,SAAS;IAC7C,GAAI,KAAK,YAAY,QAAQ,EAAE,UAAU,KAAK,UAAU;IACxD,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,UAAU;IAChD,WAAW,CAAC,SAAS;IACtB;GACF,EAGQ;EACP,QAAQ,aAAa,QAAQ,SAAS;EACtC,UAAU,aAAa,UAAU,SAAS;EAC3C;;AAGH,SAAS,aAAa,UAAoB,UAA4B;AACpE,QAAO,SAAS,KAAK,QAAQ,GAAG,SAAS,IAAI,MAAM;;AAGrD,SAAS,YAAY,OAAwB;AAC3C,QAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM"}
@@ -8,7 +8,7 @@ type ParseContext = {
8
8
  errors: string[];
9
9
  };
10
10
  /**
11
- * Checks if an expression contains derive/declare_static or declare_var calls.
11
+ * Checks if an expression contains derive or declare_var calls.
12
12
  */
13
13
  export declare function containsStaticCalls(node: SyntaxNode, imports: ImportAlias[]): boolean;
14
14
  /**
@@ -1,4 +1,4 @@
1
- import "./constants.js";
1
+ import { PYTHON_DERIVE } from "./constants.js";
2
2
  import { getParser } from "./parser.js";
3
3
  import { resolveImportPath } from "./resolveImport.js";
4
4
  import { resolveFunctionInCurrentFile, resolveFunctionInFile } from "./resolveFunctionVariants.js";
@@ -7,13 +7,13 @@ import fs from "node:fs";
7
7
  import { declareVar } from "generaltranslation/internal";
8
8
  //#region src/parseStringExpression.ts
9
9
  /**
10
- * Returns true if the original import name is derive() or declare_static() (deprecated).
10
+ * Returns true if the original import name is derive().
11
11
  */
12
12
  function isDeriveFunction(originalName) {
13
- return originalName === "derive" || originalName === "declare_static";
13
+ return originalName === PYTHON_DERIVE;
14
14
  }
15
15
  /**
16
- * Checks if an expression contains derive/declare_static or declare_var calls.
16
+ * Checks if an expression contains derive or declare_var calls.
17
17
  */
18
18
  function containsStaticCalls(node, imports) {
19
19
  const staticNames = getDeriveImportNames(imports);
@@ -160,7 +160,7 @@ async function parseBinaryOperator(node, ctx) {
160
160
  async function resolveDeclareStaticArg(callNode, ctx) {
161
161
  const arg = getFirstPositionalArg(callNode);
162
162
  if (!arg) {
163
- ctx.errors.push(`${locationStr(callNode)}: derive() / declare_static() requires an argument`);
163
+ ctx.errors.push(`${locationStr(callNode)}: derive() requires an argument`);
164
164
  return null;
165
165
  }
166
166
  return resolveStaticValue(arg, ctx);
@@ -1 +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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"}
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 { PYTHON_DERIVE, PYTHON_DECLARE_VAR } 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().\n */\nfunction isDeriveFunction(originalName: string | null): boolean {\n return originalName === PYTHON_DERIVE;\n}\n\n/**\n * Checks if an expression contains derive 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(...)\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(`${locationStr(callNode)}: derive() requires an argument`);\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.d.ts CHANGED
@@ -4,5 +4,6 @@ import { Parser } from 'web-tree-sitter';
4
4
  * configured for Python.
5
5
  */
6
6
  export declare function getParser(): Promise<Parser>;
7
+ export declare function disposeParser(): Promise<void>;
7
8
  export type { Parser };
8
9
  export { type Tree, type Node as SyntaxNode } from 'web-tree-sitter';
package/dist/parser.js CHANGED
@@ -12,6 +12,14 @@ function getParser() {
12
12
  if (!parserPromise) parserPromise = initParser();
13
13
  return parserPromise;
14
14
  }
15
+ async function disposeParser() {
16
+ const parser = parserPromise;
17
+ parserPromise = null;
18
+ if (!parser) return;
19
+ try {
20
+ (await parser).delete();
21
+ } catch {}
22
+ }
15
23
  async function initParser() {
16
24
  const parserWasmPath = await resolveWasmPath("web-tree-sitter/web-tree-sitter.wasm");
17
25
  await Parser.init({ locateFile(path) {
@@ -33,6 +41,6 @@ async function resolveWasmPath(specifier) {
33
41
  }
34
42
  }
35
43
  //#endregion
36
- export { getParser };
44
+ export { disposeParser, getParser };
37
45
 
38
46
  //# sourceMappingURL=parser.js.map
@@ -1 +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
+ {"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\nexport async function disposeParser(): Promise<void> {\n const parser = parserPromise;\n parserPromise = null;\n\n if (!parser) {\n return;\n }\n\n try {\n (await parser).delete();\n } catch {\n // Failed initialization leaves no parser instance to free.\n }\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,eAAsB,gBAA+B;CACnD,MAAM,SAAS;AACf,iBAAgB;AAEhB,KAAI,CAAC,OACH;AAGF,KAAI;AACF,GAAC,MAAM,QAAQ,QAAQ;SACjB;;AAKV,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"}
package/dist/types.d.ts CHANGED
@@ -11,6 +11,6 @@ export type ExtractionMetadata = {
11
11
  context?: string;
12
12
  maxChars?: number;
13
13
  filePaths?: string[];
14
- /** Groups related derive content variants together (for derive/declare_static) */
14
+ /** Groups related derive content variants together */
15
15
  staticId?: string;
16
16
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@generaltranslation/python-extractor",
3
- "version": "0.2.22",
3
+ "version": "0.2.23-odysseus.0",
4
4
  "description": "Python source code extraction for General Translation",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -48,7 +48,7 @@
48
48
  "dependencies": {
49
49
  "tree-sitter-python": "^0.25.0",
50
50
  "web-tree-sitter": "^0.26.6",
51
- "generaltranslation": "8.2.16"
51
+ "generaltranslation": "9.0.0-odysseus.0"
52
52
  },
53
53
  "scripts": {
54
54
  "emit-types": "sh ../../scripts/emit-types.sh",