gt 2.11.3 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # gtx-cli
2
2
 
3
+ ## 2.12.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1137](https://github.com/generaltranslation/gt/pull/1137) [`f8993aa`](https://github.com/generaltranslation/gt/commit/f8993aabe07acdfaf8a97177f038c408a8fc4c45) Thanks [@ErnestM1234](https://github.com/ErnestM1234)! - feat: object derivation
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [[`f8993aa`](https://github.com/generaltranslation/gt/commit/f8993aabe07acdfaf8a97177f038c408a8fc4c45)]:
12
+ - @generaltranslation/python-extractor@0.2.0
13
+
3
14
  ## 2.11.3
4
15
 
5
16
  ### Patch Changes
@@ -21,7 +21,12 @@ export declare const warnInvalidDeriveInitSync: (file: string, functionName: str
21
21
  export declare const warnDataAttrOnBranch: (file: string, attrName: string, location?: string) => string;
22
22
  export declare const warnRecursiveFunctionCallSync: (file: string, functionName: string, location?: string) => string;
23
23
  export declare const warnDeriveFunctionNotWrappedSync: (file: string, functionName: string, location?: string) => string;
24
+ export declare const warnDeriveNonConstVariableSync: (file: string, varName: string, kind: string, location?: string) => string;
24
25
  export declare const warnDeriveFunctionNoResultsSync: (file: string, functionName: string, location?: string) => string;
26
+ export declare const warnDeriveUnresolvableValueSync: (file: string, key: string, location?: string) => string;
27
+ export declare const warnDeriveCircularSpreadSync: (file: string, varName: string, location?: string) => string;
28
+ export declare const warnDeriveDestructuringSync: (file: string, varName: string, location?: string) => string;
29
+ export declare const warnDeriveOptionalChainingSync: (file: string, code: string, location?: string) => string;
25
30
  export declare const noLocalesError = "No locales found! Provide a list of locales for translation, or specify them in your gt.config.json file.";
26
31
  export declare const noDefaultLocaleError = "No default locale found! Provide a default locale, or specify it in your gt.config.json file.";
27
32
  export declare const noFilesError = "Incorrect or missing files configuration! Make sure your files are configured correctly in your gt.config.json file.";
@@ -32,7 +32,12 @@ Invalid: ${colorizeContent(`const ${colorizeFunctionName(functionName)} = [() =>
32
32
  export const warnDataAttrOnBranch = (file, attrName, location) => withLocation(file, `${colorizeComponent(`<${BRANCH_COMPONENT}>`)} component ignores attributes prefixed with ${colorizeIdString('"data-"')}. Found ${colorizeIdString(attrName)}. Remove it or use a different attribute name.`, location);
33
33
  export const warnRecursiveFunctionCallSync = (file, functionName, location) => withLocation(file, withDeriveComponentError(`Recursive function call detected: ${colorizeFunctionName(functionName)}. A derivable (statically analyzable) function cannot use recursive calls to construct its result.`), location);
34
34
  export const warnDeriveFunctionNotWrappedSync = (file, functionName, location) => withLocation(file, withDeriveFunctionError(`Could not resolve ${colorizeFunctionName(formatCodeClamp(functionName))}. This call is not wrapped in derive() (formerly declareStatic()). Ensure the function is properly wrapped with derive() and does not have circular import dependencies.`), location);
35
+ export const warnDeriveNonConstVariableSync = (file, varName, kind, location) => withLocation(file, withDeriveFunctionError(`Variable ${colorizeFunctionName(varName)} is declared with '${kind}' but only 'const' declarations can be resolved statically. Change it to 'const'.`), location);
35
36
  export const warnDeriveFunctionNoResultsSync = (file, functionName, location) => withLocation(file, withDeriveFunctionError(`Could not resolve ${colorizeFunctionName(formatCodeClamp(functionName))}. derive() (formerly declareStatic()) can only receive function invocations and cannot use undefined values or looped calls to construct its result.`), location);
37
+ export const warnDeriveUnresolvableValueSync = (file, key, location) => withLocation(file, withDeriveFunctionError(`Object property ${colorizeFunctionName(formatCodeClamp(key))} could not be resolved to a static string value. Only string literals, template literals, conditionals, and function calls returning strings are supported.`), location);
38
+ export const warnDeriveCircularSpreadSync = (file, varName, location) => withLocation(file, withDeriveFunctionError(`Circular spread detected involving ${colorizeFunctionName(varName)}. Spread references that form a cycle cannot be resolved statically.`), location);
39
+ export const warnDeriveDestructuringSync = (file, varName, location) => withLocation(file, withDeriveFunctionError(`Variable ${colorizeFunctionName(varName)} uses destructuring syntax, which is not yet supported in derive(). Assign the value to a const variable directly instead.`), location);
40
+ export const warnDeriveOptionalChainingSync = (file, code, location) => withLocation(file, withDeriveFunctionError(`Optional chaining (${colorizeFunctionName(formatCodeClamp(code))}) is not supported in derive(). Optional chaining implies the value could be undefined, which cannot be resolved statically. Use a non-optional access instead.`), location);
36
41
  // Re-export error messages
37
42
  export const noLocalesError = `No locales found! Provide a list of locales for translation, or specify them in your gt.config.json file.`;
38
43
  export const noDefaultLocaleError = `No default locale found! Provide a default locale, or specify it in your gt.config.json file.`;
@@ -1 +1 @@
1
- export declare const PACKAGE_VERSION = "2.11.3";
1
+ export declare const PACKAGE_VERSION = "2.12.0";
@@ -1,2 +1,2 @@
1
1
  // This file is auto-generated. Do not edit manually.
2
- export const PACKAGE_VERSION = '2.11.3';
2
+ export const PACKAGE_VERSION = '2.12.0';
@@ -17,7 +17,7 @@ import { StringNode } from './types.js';
17
17
  * @param warnings - Set to collect warning messages
18
18
  * @returns Node | null
19
19
  */
20
- export declare function parseStringExpression(node: t.Node, tPath: NodePath, file: string, parsingOptions: ParsingConfigOptions, warnings?: Set<string>): StringNode | null;
20
+ export declare function parseStringExpression(node: t.Node, tPath: NodePath, file: string, parsingOptions: ParsingConfigOptions, warnings: Set<string>, errors: string[]): StringNode | null;
21
21
  /**
22
22
  * Converts a Node tree to an array of all possible string combinations
23
23
  * This is a helper function for compatibility with existing code
@@ -3,7 +3,7 @@ import { buildImportMap } from './buildImportMap.js';
3
3
  import { resolveImportPath } from './resolveImportPath.js';
4
4
  import { parse } from '@babel/parser';
5
5
  import fs from 'node:fs';
6
- import { warnDeriveFunctionNoResultsSync, warnFunctionNotFoundSync, warnInvalidDeclareVarNameSync, } from '../../../console/index.js';
6
+ import { warnDeriveFunctionNoResultsSync, warnFunctionNotFoundSync, warnInvalidDeclareVarNameSync, warnDeriveNonConstVariableSync, warnDeriveUnresolvableValueSync, warnDeriveOptionalChainingSync, warnDeriveDestructuringSync, warnDeriveCircularSpreadSync, } from '../../../console/index.js';
7
7
  import traverseModule from '@babel/traverse';
8
8
  import { DECLARE_VAR_FUNCTION } from '../../jsx/utils/constants.js';
9
9
  import { GT_LIBRARIES } from '../../../types/libraries.js';
@@ -21,6 +21,419 @@ const resolveImportPathCache = new Map();
21
21
  * Cache for processed functions to avoid re-parsing the same files.
22
22
  */
23
23
  const processFunctionCache = new Map();
24
+ /**
25
+ * Cache for resolved object nodes to avoid re-parsing the same files.
26
+ */
27
+ const resolveObjectNodeCache = new Map();
28
+ /**
29
+ * Guard against infinite recursion when resolving identifier chains.
30
+ * Tracks AST nodes currently being resolved to detect circular references.
31
+ */
32
+ const resolvingIdentifiers = new Set();
33
+ /**
34
+ * Unwraps TypeScript type annotations to get the underlying expression.
35
+ * Handles: as, satisfies, non-null assertion (!), and angle-bracket assertions.
36
+ */
37
+ function unwrapTypeAnnotation(node) {
38
+ if (t.isTSAsExpression(node))
39
+ return node.expression;
40
+ if (t.isTSSatisfiesExpression(node))
41
+ return node.expression;
42
+ if (t.isTSNonNullExpression(node))
43
+ return node.expression;
44
+ if (t.isTSTypeAssertion(node))
45
+ return node.expression;
46
+ return node;
47
+ }
48
+ /**
49
+ * Collects all resolvable property entries from an ObjectExpression,
50
+ * including entries from spread sources.
51
+ */
52
+ function collectObjectProperties(objExpr, tPath, file, parsingOptions, warnings, errors = []) {
53
+ const entries = [];
54
+ for (const prop of objExpr.properties) {
55
+ if (t.isObjectProperty(prop)) {
56
+ const key = !prop.computed && t.isIdentifier(prop.key)
57
+ ? prop.key.name
58
+ : t.isStringLiteral(prop.key)
59
+ ? prop.key.value
60
+ : t.isNumericLiteral(prop.key)
61
+ ? String(prop.key.value)
62
+ : null;
63
+ if (t.isExpression(prop.value)) {
64
+ entries.push({ key, value: prop.value });
65
+ }
66
+ }
67
+ else if (t.isSpreadElement(prop)) {
68
+ // Handle inline ObjectExpression spread: { ...({ a: 'x' }) }
69
+ const spreadArg = unwrapTypeAnnotation(prop.argument);
70
+ if (t.isObjectExpression(spreadArg)) {
71
+ entries.push(...collectObjectProperties(spreadArg, tPath, file, parsingOptions, warnings, errors));
72
+ continue;
73
+ }
74
+ if (!t.isIdentifier(prop.argument))
75
+ continue;
76
+ const spreadBinding = tPath.scope.getBinding(prop.argument.name);
77
+ if (!spreadBinding)
78
+ continue;
79
+ // Same-file: VariableDeclarator
80
+ if (spreadBinding.path.isVariableDeclarator()) {
81
+ const spreadDecl = spreadBinding.path.parentPath;
82
+ if (spreadDecl?.isVariableDeclaration() &&
83
+ spreadDecl.node.kind !== 'const')
84
+ continue;
85
+ const spreadInit = spreadBinding.path.node.init;
86
+ if (!spreadInit)
87
+ continue;
88
+ // Guard against circular spreads
89
+ if (resolvingIdentifiers.has(spreadInit)) {
90
+ errors.push(warnDeriveCircularSpreadSync(file, prop.argument.name, `${prop.loc?.start?.line}:${prop.loc?.start?.column}`));
91
+ continue;
92
+ }
93
+ resolvingIdentifiers.add(spreadInit);
94
+ const spreadObj = unwrapTypeAnnotation(spreadInit);
95
+ if (!t.isObjectExpression(spreadObj)) {
96
+ resolvingIdentifiers.delete(spreadInit);
97
+ continue;
98
+ }
99
+ entries.push(...collectObjectProperties(spreadObj, spreadBinding.path, file, parsingOptions, warnings, errors));
100
+ resolvingIdentifiers.delete(spreadInit);
101
+ }
102
+ // Cross-file: ImportSpecifier or ImportDefaultSpecifier
103
+ else if (spreadBinding.path.isImportSpecifier() ||
104
+ spreadBinding.path.isImportDefaultSpecifier()) {
105
+ const programPath = tPath.scope.getProgramParent().path;
106
+ const importMap = buildImportMap(programPath);
107
+ const importPath = importMap.get(prop.argument.name);
108
+ if (!importPath)
109
+ continue;
110
+ let originalName = prop.argument.name;
111
+ if (spreadBinding.path.isImportSpecifier()) {
112
+ const imported = spreadBinding.path.node.imported;
113
+ originalName = t.isIdentifier(imported)
114
+ ? imported.name
115
+ : imported.value;
116
+ }
117
+ const resolvedFilePath = resolveImportPath(file, importPath, parsingOptions, resolveImportPathCache);
118
+ if (!resolvedFilePath)
119
+ continue;
120
+ const crossFileObjects = resolveObjectNodesInFile(resolvedFilePath, originalName, parsingOptions, warnings);
121
+ for (const crossObj of crossFileObjects) {
122
+ const crossEntries = t.isArrayExpression(crossObj.objExpr)
123
+ ? collectArrayElements(crossObj.objExpr, crossObj.tPath, crossObj.file, parsingOptions, warnings)
124
+ : collectObjectProperties(crossObj.objExpr, crossObj.tPath, crossObj.file, parsingOptions, warnings, errors);
125
+ entries.push(...crossEntries);
126
+ }
127
+ }
128
+ }
129
+ // Skip ObjectMethod (getters, setters, methods)
130
+ }
131
+ return entries;
132
+ }
133
+ /**
134
+ * Collects elements from an ArrayExpression as ObjectEntry[] with index as key.
135
+ * Handles spread elements by resolving the source array.
136
+ */
137
+ function collectArrayElements(arrExpr, tPath, file, parsingOptions, warnings, errors = []) {
138
+ const entries = [];
139
+ let index = 0;
140
+ for (const el of arrExpr.elements) {
141
+ if (!el) {
142
+ index++;
143
+ continue;
144
+ } // sparse hole
145
+ if (t.isSpreadElement(el)) {
146
+ if (!t.isIdentifier(el.argument)) {
147
+ continue;
148
+ }
149
+ const spreadBinding = tPath.scope.getBinding(el.argument.name);
150
+ if (!spreadBinding)
151
+ continue;
152
+ // Same-file spread
153
+ if (spreadBinding.path.isVariableDeclarator()) {
154
+ const spreadDecl = spreadBinding.path.parentPath;
155
+ if (spreadDecl?.isVariableDeclaration() &&
156
+ spreadDecl.node.kind !== 'const') {
157
+ errors.push(warnDeriveNonConstVariableSync(file, el.argument.name, spreadDecl.node.kind, `${el.loc?.start?.line}:${el.loc?.start?.column}`));
158
+ continue;
159
+ }
160
+ const spreadInit = spreadBinding.path.node.init;
161
+ if (!spreadInit)
162
+ continue;
163
+ const spreadUnwrapped = unwrapTypeAnnotation(spreadInit);
164
+ if (t.isArrayExpression(spreadUnwrapped)) {
165
+ const spreadEntries = collectArrayElements(spreadUnwrapped, spreadBinding.path, file, parsingOptions, warnings);
166
+ for (const e of spreadEntries) {
167
+ entries.push({ key: String(index++), value: e.value });
168
+ }
169
+ continue;
170
+ }
171
+ }
172
+ // Cross-file spread
173
+ else if (spreadBinding.path.isImportSpecifier() ||
174
+ spreadBinding.path.isImportDefaultSpecifier()) {
175
+ const programPath = tPath.scope.getProgramParent().path;
176
+ const importMap = buildImportMap(programPath);
177
+ const importPath = importMap.get(el.argument.name);
178
+ if (!importPath)
179
+ continue;
180
+ let originalName = el.argument.name;
181
+ if (spreadBinding.path.isImportSpecifier()) {
182
+ const imported = spreadBinding.path.node.imported;
183
+ originalName = t.isIdentifier(imported)
184
+ ? imported.name
185
+ : imported.value;
186
+ }
187
+ const resolvedFilePath = resolveImportPath(file, importPath, parsingOptions, resolveImportPathCache);
188
+ if (!resolvedFilePath)
189
+ continue;
190
+ const crossFileNodes = resolveObjectNodesInFile(resolvedFilePath, originalName, parsingOptions, warnings);
191
+ for (const crossNode of crossFileNodes) {
192
+ if (t.isArrayExpression(crossNode.objExpr)) {
193
+ const spreadEntries = collectArrayElements(crossNode.objExpr, crossNode.tPath, crossNode.file, parsingOptions, warnings);
194
+ for (const e of spreadEntries) {
195
+ entries.push({ key: String(index++), value: e.value });
196
+ }
197
+ }
198
+ }
199
+ }
200
+ continue;
201
+ }
202
+ if (t.isExpression(el)) {
203
+ entries.push({ key: String(index), value: unwrapTypeAnnotation(el) });
204
+ }
205
+ index++;
206
+ }
207
+ return entries;
208
+ }
209
+ /**
210
+ * Resolves an expression to ObjectExpression or ArrayExpression AST node(s).
211
+ * Handles Identifier (local + imported) and MemberExpression (nested access chains).
212
+ */
213
+ function resolveToObjectNodes(node, tPath, file, parsingOptions, warnings, errors = []) {
214
+ // Case 1: Identifier — base case
215
+ if (t.isIdentifier(node)) {
216
+ const binding = tPath.scope.getBinding(node.name);
217
+ if (!binding)
218
+ return [];
219
+ // Local variable
220
+ if (binding.path.isVariableDeclarator()) {
221
+ const declaration = binding.path.parentPath;
222
+ if (declaration?.isVariableDeclaration() &&
223
+ declaration.node.kind !== 'const') {
224
+ warnings.add(warnDeriveNonConstVariableSync(file, node.name, declaration.node.kind, `${node.loc?.start?.line}:${node.loc?.start?.column}`));
225
+ return [];
226
+ }
227
+ const init = binding.path.node.init;
228
+ if (!init)
229
+ return [];
230
+ const unwrapped = unwrapTypeAnnotation(init);
231
+ if (t.isObjectExpression(unwrapped) || t.isArrayExpression(unwrapped)) {
232
+ return [{ objExpr: unwrapped, tPath: binding.path, file }];
233
+ }
234
+ // Handle identifier chain: const A = B; const B = { ... }
235
+ if (t.isIdentifier(unwrapped)) {
236
+ return resolveToObjectNodes(unwrapped, binding.path, file, parsingOptions, warnings, errors);
237
+ }
238
+ // Handle conditional: cond ? { ... } : { ... } (including nested ternaries)
239
+ if (t.isConditionalExpression(unwrapped)) {
240
+ const collectConditionalLeaves = (expr) => {
241
+ const inner = unwrapTypeAnnotation(expr);
242
+ if (t.isConditionalExpression(inner)) {
243
+ return [
244
+ ...collectConditionalLeaves(inner.consequent),
245
+ ...collectConditionalLeaves(inner.alternate),
246
+ ];
247
+ }
248
+ if (t.isObjectExpression(inner) || t.isArrayExpression(inner)) {
249
+ return [{ objExpr: inner, tPath: binding.path, file }];
250
+ }
251
+ return [];
252
+ };
253
+ return [
254
+ ...collectConditionalLeaves(unwrapped.consequent),
255
+ ...collectConditionalLeaves(unwrapped.alternate),
256
+ ];
257
+ }
258
+ return [];
259
+ }
260
+ // Imported
261
+ if (binding.path.isImportSpecifier() ||
262
+ binding.path.isImportDefaultSpecifier()) {
263
+ const programPath = tPath.scope.getProgramParent().path;
264
+ const importMap = buildImportMap(programPath);
265
+ const importPath = importMap.get(node.name);
266
+ if (!importPath)
267
+ return [];
268
+ let originalName = node.name;
269
+ if (binding.path.isImportSpecifier()) {
270
+ const imported = binding.path.node.imported;
271
+ originalName = t.isIdentifier(imported)
272
+ ? imported.name
273
+ : imported.value;
274
+ }
275
+ const resolvedFilePath = resolveImportPath(file, importPath, parsingOptions, resolveImportPathCache);
276
+ if (!resolvedFilePath)
277
+ return [];
278
+ return resolveObjectNodesInFile(resolvedFilePath, originalName, parsingOptions, warnings);
279
+ }
280
+ return [];
281
+ }
282
+ // Case 2: MemberExpression — recursive
283
+ if (t.isMemberExpression(node)) {
284
+ if (!t.isExpression(node.object))
285
+ return [];
286
+ const parentObjects = resolveToObjectNodes(node.object, tPath, file, parsingOptions, warnings, errors);
287
+ if (parentObjects.length === 0)
288
+ return [];
289
+ const results = [];
290
+ for (const parent of parentObjects) {
291
+ const entries = t.isArrayExpression(parent.objExpr)
292
+ ? collectArrayElements(parent.objExpr, parent.tPath, parent.file, parsingOptions, warnings)
293
+ : collectObjectProperties(parent.objExpr, parent.tPath, parent.file, parsingOptions, warnings, errors);
294
+ // Determine if this is a static access (known key)
295
+ let staticKey = null;
296
+ if (!node.computed && t.isIdentifier(node.property)) {
297
+ staticKey = node.property.name;
298
+ }
299
+ else if (node.computed && t.isStringLiteral(node.property)) {
300
+ staticKey = node.property.value;
301
+ }
302
+ else if (node.computed && t.isNumericLiteral(node.property)) {
303
+ staticKey = String(node.property.value);
304
+ }
305
+ if (staticKey !== null) {
306
+ // Static: narrow to matching key(s) — don't break, spreads may duplicate keys
307
+ for (const entry of entries) {
308
+ if (entry.key === staticKey) {
309
+ const unwrapped = unwrapTypeAnnotation(entry.value);
310
+ if (t.isObjectExpression(unwrapped) ||
311
+ t.isArrayExpression(unwrapped)) {
312
+ results.push({
313
+ objExpr: unwrapped,
314
+ tPath: parent.tPath,
315
+ file: parent.file,
316
+ });
317
+ }
318
+ }
319
+ }
320
+ }
321
+ else {
322
+ // Dynamic: collect ALL entries whose values are ObjectExpressions or ArrayExpressions
323
+ for (const entry of entries) {
324
+ const unwrapped = unwrapTypeAnnotation(entry.value);
325
+ if (t.isObjectExpression(unwrapped) ||
326
+ t.isArrayExpression(unwrapped)) {
327
+ results.push({
328
+ objExpr: unwrapped,
329
+ tPath: parent.tPath,
330
+ file: parent.file,
331
+ });
332
+ }
333
+ }
334
+ }
335
+ }
336
+ return results;
337
+ }
338
+ return [];
339
+ }
340
+ /**
341
+ * Resolves an object declaration from an external file.
342
+ * Finds a const VariableDeclarator with ObjectExpression init.
343
+ * Follows re-export chains.
344
+ */
345
+ function resolveObjectNodesInFile(filePath, name, parsingOptions, warnings) {
346
+ const cacheKey = `${filePath}::${name}`;
347
+ if (resolveObjectNodeCache.has(cacheKey)) {
348
+ return resolveObjectNodeCache.get(cacheKey);
349
+ }
350
+ try {
351
+ const code = fs.readFileSync(filePath, 'utf8');
352
+ const ast = parse(code, {
353
+ sourceType: 'module',
354
+ plugins: ['jsx', 'typescript'],
355
+ });
356
+ const results = [];
357
+ traverse(ast, {
358
+ VariableDeclarator(path) {
359
+ if (t.isIdentifier(path.node.id) &&
360
+ path.node.id.name === name &&
361
+ results.length === 0) {
362
+ const init = path.node.init;
363
+ if (!init)
364
+ return;
365
+ const declaration = path.parentPath;
366
+ if (declaration?.isVariableDeclaration() &&
367
+ declaration.node.kind !== 'const')
368
+ return;
369
+ const unwrapped = unwrapTypeAnnotation(init);
370
+ if (t.isObjectExpression(unwrapped) ||
371
+ t.isArrayExpression(unwrapped)) {
372
+ results.push({
373
+ objExpr: unwrapped,
374
+ tPath: path,
375
+ file: filePath,
376
+ });
377
+ }
378
+ }
379
+ },
380
+ ExportAllDeclaration(path) {
381
+ if (results.length > 0)
382
+ return;
383
+ if (t.isStringLiteral(path.node.source)) {
384
+ const reexportPath = path.node.source.value;
385
+ const resolvedPath = resolveImportPath(filePath, reexportPath, parsingOptions, resolveImportPathCache);
386
+ if (resolvedPath) {
387
+ results.push(...resolveObjectNodesInFile(resolvedPath, name, parsingOptions, warnings));
388
+ }
389
+ }
390
+ },
391
+ ExportNamedDeclaration(path) {
392
+ if (results.length > 0)
393
+ return;
394
+ if (path.node.source && t.isStringLiteral(path.node.source)) {
395
+ const hasMatch = path.node.specifiers.some((spec) => {
396
+ if (t.isExportSpecifier(spec)) {
397
+ const exportedName = t.isIdentifier(spec.exported)
398
+ ? spec.exported.name
399
+ : spec.exported.value;
400
+ return exportedName === name;
401
+ }
402
+ return false;
403
+ });
404
+ if (hasMatch) {
405
+ const reexportPath = path.node.source.value;
406
+ const resolvedPath = resolveImportPath(filePath, reexportPath, parsingOptions, resolveImportPathCache);
407
+ if (resolvedPath) {
408
+ const specifier = path.node.specifiers.find((spec) => {
409
+ if (t.isExportSpecifier(spec)) {
410
+ const exportedName = t.isIdentifier(spec.exported)
411
+ ? spec.exported.name
412
+ : spec.exported.value;
413
+ return exportedName === name;
414
+ }
415
+ return false;
416
+ });
417
+ let originalName = name;
418
+ if (specifier &&
419
+ t.isExportSpecifier(specifier) &&
420
+ t.isIdentifier(specifier.local)) {
421
+ originalName = specifier.local.name;
422
+ }
423
+ results.push(...resolveObjectNodesInFile(resolvedPath, originalName, parsingOptions, warnings));
424
+ }
425
+ }
426
+ }
427
+ },
428
+ });
429
+ resolveObjectNodeCache.set(cacheKey, results);
430
+ return results;
431
+ }
432
+ catch {
433
+ resolveObjectNodeCache.set(cacheKey, []);
434
+ return [];
435
+ }
436
+ }
24
437
  /**
25
438
  * Processes a string expression node and resolves any function calls within it
26
439
  * This handles cases like:
@@ -36,7 +449,14 @@ const processFunctionCache = new Map();
36
449
  * @param warnings - Set to collect warning messages
37
450
  * @returns Node | null
38
451
  */
39
- export function parseStringExpression(node, tPath, file, parsingOptions, warnings = new Set()) {
452
+ export function parseStringExpression(node, tPath, file, parsingOptions, warnings, errors) {
453
+ // Unwrap TypeScript type annotations (as, satisfies, !, <Type>)
454
+ if (t.isTSAsExpression(node) ||
455
+ t.isTSSatisfiesExpression(node) ||
456
+ t.isTSNonNullExpression(node) ||
457
+ t.isTSTypeAssertion(node)) {
458
+ return parseStringExpression(node.expression, tPath, file, parsingOptions, warnings, errors);
459
+ }
40
460
  // Handle string literals
41
461
  if (t.isStringLiteral(node)) {
42
462
  return { type: 'text', text: node.value };
@@ -64,7 +484,7 @@ export function parseStringExpression(node, tPath, file, parsingOptions, warning
64
484
  }
65
485
  const exprNode = node.expressions[index];
66
486
  if (exprNode && t.isExpression(exprNode)) {
67
- const result = parseStringExpression(exprNode, tPath, file, parsingOptions, warnings);
487
+ const result = parseStringExpression(exprNode, tPath, file, parsingOptions, warnings, errors);
68
488
  if (result === null) {
69
489
  return null;
70
490
  }
@@ -84,8 +504,8 @@ export function parseStringExpression(node, tPath, file, parsingOptions, warning
84
504
  if (!t.isExpression(node.left) || !t.isExpression(node.right)) {
85
505
  return null;
86
506
  }
87
- const leftResult = parseStringExpression(node.left, tPath, file, parsingOptions, warnings);
88
- const rightResult = parseStringExpression(node.right, tPath, file, parsingOptions, warnings);
507
+ const leftResult = parseStringExpression(node.left, tPath, file, parsingOptions, warnings, errors);
508
+ const rightResult = parseStringExpression(node.right, tPath, file, parsingOptions, warnings, errors);
89
509
  if (leftResult === null || rightResult === null) {
90
510
  return null;
91
511
  }
@@ -96,8 +516,8 @@ export function parseStringExpression(node, tPath, file, parsingOptions, warning
96
516
  if (!t.isExpression(node.consequent) || !t.isExpression(node.alternate)) {
97
517
  return null;
98
518
  }
99
- const consequentResult = parseStringExpression(node.consequent, tPath, file, parsingOptions, warnings);
100
- const alternateResult = parseStringExpression(node.alternate, tPath, file, parsingOptions, warnings);
519
+ const consequentResult = parseStringExpression(node.consequent, tPath, file, parsingOptions, warnings, errors);
520
+ const alternateResult = parseStringExpression(node.alternate, tPath, file, parsingOptions, warnings, errors);
101
521
  if (consequentResult === null || alternateResult === null) {
102
522
  return null;
103
523
  }
@@ -116,15 +536,116 @@ export function parseStringExpression(node, tPath, file, parsingOptions, warning
116
536
  }
117
537
  // Check if it's a const/let/var with an initializer
118
538
  if (binding.path.isVariableDeclarator() && binding.path.node.init) {
539
+ // Check for destructuring patterns (not yet supported)
540
+ if (t.isObjectPattern(binding.path.node.id) ||
541
+ t.isArrayPattern(binding.path.node.id)) {
542
+ errors.push(warnDeriveDestructuringSync(file, node.name, `${node.loc?.start?.line}:${node.loc?.start?.column}`));
543
+ return null;
544
+ }
545
+ // Guard against circular references (e.g., const A = B; const B = A)
119
546
  const init = binding.path.node.init;
120
- if (t.isExpression(init)) {
121
- // Recursively resolve the initializer
122
- return parseStringExpression(init, binding.path, file, parsingOptions, warnings);
547
+ if (resolvingIdentifiers.has(init)) {
548
+ return null;
549
+ }
550
+ // Enforce const-only
551
+ const declaration = binding.path.parentPath;
552
+ if (declaration?.isVariableDeclaration() &&
553
+ declaration.node.kind !== 'const') {
554
+ warnings.add(warnDeriveNonConstVariableSync(file, node.name, declaration.node.kind, `${node.loc?.start?.line}:${node.loc?.start?.column}`));
555
+ return null;
123
556
  }
557
+ // Unwrap TSAsExpression (for `as const`)
558
+ const unwrapped = unwrapTypeAnnotation(init);
559
+ if (t.isExpression(unwrapped)) {
560
+ // Recursively resolve the initializer with recursion guard
561
+ resolvingIdentifiers.add(init);
562
+ try {
563
+ return parseStringExpression(unwrapped, binding.path, file, parsingOptions, warnings, errors);
564
+ }
565
+ finally {
566
+ resolvingIdentifiers.delete(init);
567
+ }
568
+ }
569
+ }
570
+ // Check for destructuring patterns (not yet supported)
571
+ if (binding.path.isObjectProperty() ||
572
+ binding.path.isArrayPattern() ||
573
+ binding.path.isRestElement()) {
574
+ errors.push(warnDeriveDestructuringSync(file, node.name, `${node.loc?.start?.line}:${node.loc?.start?.column}`));
575
+ return null;
124
576
  }
125
577
  // Not a resolvable variable
126
578
  return null;
127
579
  }
580
+ // Handle optional chaining — not supported, emit error
581
+ if (t.isOptionalMemberExpression(node)) {
582
+ errors.push(warnDeriveOptionalChainingSync(file, generate(node).code, `${node.loc?.start?.line}:${node.loc?.start?.column}`));
583
+ return null;
584
+ }
585
+ // Handle member expressions: obj[key], obj.prop, and nested obj.a.b / obj[a][b]
586
+ if (t.isMemberExpression(node)) {
587
+ if (!t.isExpression(node.object))
588
+ return null;
589
+ // Resolve the object part to ObjectExpression node(s)
590
+ const objectNodes = resolveToObjectNodes(node.object, tPath, file, parsingOptions, warnings, errors);
591
+ if (objectNodes.length === 0)
592
+ return null;
593
+ // Apply the final access to each resolved object
594
+ const branches = [];
595
+ for (const { objExpr, tPath: objPath, file: objFile } of objectNodes) {
596
+ const entries = t.isArrayExpression(objExpr)
597
+ ? collectArrayElements(objExpr, objPath, objFile, parsingOptions, warnings, errors)
598
+ : collectObjectProperties(objExpr, objPath, objFile, parsingOptions, warnings, errors);
599
+ // Check for static literal subscripts: obj['key'] or obj[0]
600
+ let staticLiteralKey = null;
601
+ if (node.computed && t.isStringLiteral(node.property)) {
602
+ staticLiteralKey = node.property.value;
603
+ }
604
+ else if (node.computed && t.isNumericLiteral(node.property)) {
605
+ staticLiteralKey = String(node.property.value);
606
+ }
607
+ // Determine the access key (if statically known)
608
+ const propName = staticLiteralKey ??
609
+ (!node.computed && t.isIdentifier(node.property)
610
+ ? node.property.name
611
+ : null);
612
+ // Check if we can narrow: need a known access key AND all object keys must be resolvable
613
+ const hasUnresolvableKeys = entries.some((e) => e.key === null);
614
+ const canNarrow = propName !== null && !hasUnresolvableKeys;
615
+ if (canNarrow) {
616
+ // STATIC: extract ONE specific property
617
+ for (const entry of entries) {
618
+ if (entry.key === propName) {
619
+ const resolved = parseStringExpression(entry.value, objPath, objFile, parsingOptions, warnings, errors);
620
+ if (resolved) {
621
+ branches.push(resolved);
622
+ }
623
+ else {
624
+ errors.push(warnDeriveUnresolvableValueSync(objFile, propName, `${entry.value.loc?.start?.line}:${entry.value.loc?.start?.column}`));
625
+ }
626
+ // Don't break — spreads may introduce duplicate keys
627
+ }
628
+ }
629
+ }
630
+ else {
631
+ // DYNAMIC: extract ALL values (can't determine which key matches)
632
+ for (const entry of entries) {
633
+ const resolved = parseStringExpression(entry.value, objPath, objFile, parsingOptions, warnings, errors);
634
+ if (resolved) {
635
+ branches.push(resolved);
636
+ }
637
+ else if (entry.key !== null) {
638
+ errors.push(warnDeriveUnresolvableValueSync(objFile, entry.key, `${entry.value.loc?.start?.line}:${entry.value.loc?.start?.column}`));
639
+ }
640
+ }
641
+ }
642
+ }
643
+ if (branches.length === 0)
644
+ return null;
645
+ if (branches.length === 1)
646
+ return branches[0];
647
+ return { type: 'choice', nodes: branches };
648
+ }
128
649
  // Handle function calls (e.g., getName())
129
650
  if (t.isCallExpression(node) && t.isIdentifier(node.callee)) {
130
651
  const functionName = node.callee.name;
@@ -184,16 +705,16 @@ export function parseStringExpression(node, tPath, file, parsingOptions, warning
184
705
  }
185
706
  const filePath = resolveImportPath(file, importPath, parsingOptions, resolveImportPathCache);
186
707
  if (filePath && originalName) {
187
- return resolveFunctionInFile(filePath, originalName, parsingOptions, warnings);
708
+ return resolveFunctionInFile(filePath, originalName, parsingOptions, warnings, errors);
188
709
  }
189
710
  return null;
190
711
  }
191
712
  // Resolve the function locally and get its return values
192
- return resolveFunctionCall(calleeBinding, tPath, file, parsingOptions, warnings);
713
+ return resolveFunctionCall(calleeBinding, tPath, file, parsingOptions, warnings, errors);
193
714
  }
194
715
  // Handle parenthesized expressions
195
716
  if (t.isParenthesizedExpression(node)) {
196
- return parseStringExpression(node.expression, tPath, file, parsingOptions, warnings);
717
+ return parseStringExpression(node.expression, tPath, file, parsingOptions, warnings, errors);
197
718
  }
198
719
  // Handle unary expressions (e.g., -123)
199
720
  if (t.isUnaryExpression(node)) {
@@ -220,7 +741,7 @@ export function parseStringExpression(node, tPath, file, parsingOptions, warning
220
741
  /**
221
742
  * Resolves a function call by traversing its body and collecting return values
222
743
  */
223
- function resolveFunctionCall(calleeBinding, tPath, file, parsingOptions, warnings) {
744
+ function resolveFunctionCall(calleeBinding, tPath, file, parsingOptions, warnings, errors) {
224
745
  if (!calleeBinding) {
225
746
  return null;
226
747
  }
@@ -242,7 +763,7 @@ function resolveFunctionCall(calleeBinding, tPath, file, parsingOptions, warning
242
763
  if (!returnArg || !t.isExpression(returnArg)) {
243
764
  return;
244
765
  }
245
- const returnResult = parseStringExpression(returnArg, returnPath, file, parsingOptions, warnings);
766
+ const returnResult = parseStringExpression(returnArg, returnPath, file, parsingOptions, warnings, errors);
246
767
  if (returnResult !== null) {
247
768
  branches.push(returnResult);
248
769
  }
@@ -258,7 +779,7 @@ function resolveFunctionCall(calleeBinding, tPath, file, parsingOptions, warning
258
779
  const body = init.get('body');
259
780
  // Handle expression body: () => "day"
260
781
  if (body.isExpression()) {
261
- const bodyResult = parseStringExpression(body.node, body, file, parsingOptions, warnings);
782
+ const bodyResult = parseStringExpression(body.node, body, file, parsingOptions, warnings, errors);
262
783
  if (bodyResult !== null) {
263
784
  branches.push(bodyResult);
264
785
  }
@@ -280,7 +801,7 @@ function resolveFunctionCall(calleeBinding, tPath, file, parsingOptions, warning
280
801
  if (!returnArg || !t.isExpression(returnArg)) {
281
802
  return;
282
803
  }
283
- const returnResult = parseStringExpression(returnArg, returnPath, file, parsingOptions, warnings);
804
+ const returnResult = parseStringExpression(returnArg, returnPath, file, parsingOptions, warnings, errors);
284
805
  if (returnResult !== null) {
285
806
  branches.push(returnResult);
286
807
  }
@@ -299,7 +820,7 @@ function resolveFunctionCall(calleeBinding, tPath, file, parsingOptions, warning
299
820
  /**
300
821
  * Resolves a function definition in an external file
301
822
  */
302
- function resolveFunctionInFile(filePath, functionName, parsingOptions, warnings) {
823
+ function resolveFunctionInFile(filePath, functionName, parsingOptions, warnings, errors) {
303
824
  // Check cache first
304
825
  const cacheKey = `${filePath}::${functionName}`;
305
826
  if (processFunctionCache.has(cacheKey)) {
@@ -323,7 +844,7 @@ function resolveFunctionInFile(filePath, functionName, parsingOptions, warnings)
323
844
  const resolvedPath = resolveImportPath(filePath, reexportPath, parsingOptions, resolveImportPathCache);
324
845
  if (resolvedPath) {
325
846
  // Recursively resolve in the re-exported file
326
- const reexportResult = resolveFunctionInFile(resolvedPath, functionName, parsingOptions, warnings);
847
+ const reexportResult = resolveFunctionInFile(resolvedPath, functionName, parsingOptions, warnings, errors);
327
848
  if (reexportResult) {
328
849
  result = reexportResult;
329
850
  }
@@ -368,7 +889,7 @@ function resolveFunctionInFile(filePath, functionName, parsingOptions, warnings)
368
889
  originalName = specifier.local.name;
369
890
  }
370
891
  // Recursively resolve in the re-exported file
371
- const reexportResult = resolveFunctionInFile(resolvedPath, originalName, parsingOptions, warnings);
892
+ const reexportResult = resolveFunctionInFile(resolvedPath, originalName, parsingOptions, warnings, errors);
372
893
  if (reexportResult) {
373
894
  result = reexportResult;
374
895
  }
@@ -393,7 +914,7 @@ function resolveFunctionInFile(filePath, functionName, parsingOptions, warnings)
393
914
  if (!returnArg || !t.isExpression(returnArg)) {
394
915
  return;
395
916
  }
396
- const returnResult = parseStringExpression(returnArg, returnPath, filePath, parsingOptions, warnings);
917
+ const returnResult = parseStringExpression(returnArg, returnPath, filePath, parsingOptions, warnings, errors);
397
918
  if (returnResult !== null) {
398
919
  branches.push(returnResult);
399
920
  }
@@ -411,58 +932,121 @@ function resolveFunctionInFile(filePath, functionName, parsingOptions, warnings)
411
932
  VariableDeclarator(path) {
412
933
  if (t.isIdentifier(path.node.id) &&
413
934
  path.node.id.name === functionName &&
414
- path.node.init &&
415
- (t.isArrowFunctionExpression(path.node.init) ||
416
- t.isFunctionExpression(path.node.init)) &&
417
935
  result === null) {
418
- const init = path.get('init');
419
- if (!init.isArrowFunctionExpression() &&
420
- !init.isFunctionExpression()) {
936
+ const init = path.node.init;
937
+ if (!init)
421
938
  return;
422
- }
423
- const bodyPath = init.get('body');
424
- const branches = [];
425
- // Handle expression body: () => "day"
426
- if (!Array.isArray(bodyPath) && t.isExpression(bodyPath.node)) {
427
- const bodyResult = parseStringExpression(bodyPath.node, bodyPath, filePath, parsingOptions, warnings);
428
- if (bodyResult !== null) {
429
- branches.push(bodyResult);
939
+ // Handle arrow/function expressions
940
+ if (t.isArrowFunctionExpression(init) ||
941
+ t.isFunctionExpression(init)) {
942
+ const initPath = path.get('init');
943
+ if (!initPath.isArrowFunctionExpression() &&
944
+ !initPath.isFunctionExpression()) {
945
+ return;
946
+ }
947
+ const bodyPath = initPath.get('body');
948
+ const branches = [];
949
+ // Handle expression body: () => "day"
950
+ if (!Array.isArray(bodyPath) && t.isExpression(bodyPath.node)) {
951
+ const bodyResult = parseStringExpression(bodyPath.node, bodyPath, filePath, parsingOptions, warnings, errors);
952
+ if (bodyResult !== null) {
953
+ branches.push(bodyResult);
954
+ }
955
+ }
956
+ // Handle block body: () => { return "day"; }
957
+ else if (!Array.isArray(bodyPath) &&
958
+ t.isBlockStatement(bodyPath.node)) {
959
+ const arrowFunction = initPath.node;
960
+ bodyPath.traverse({
961
+ Function(innerPath) {
962
+ // Skip nested functions
963
+ innerPath.skip();
964
+ },
965
+ ReturnStatement(returnPath) {
966
+ // Only process return statements that are direct children of this function
967
+ const parentFunction = returnPath.getFunctionParent();
968
+ if (parentFunction?.node !== arrowFunction) {
969
+ return;
970
+ }
971
+ if (!t.isReturnStatement(returnPath.node)) {
972
+ return;
973
+ }
974
+ const returnArg = returnPath.node.argument;
975
+ if (!returnArg || !t.isExpression(returnArg)) {
976
+ return;
977
+ }
978
+ const returnResult = parseStringExpression(returnArg, returnPath, filePath, parsingOptions, warnings, errors);
979
+ if (returnResult !== null) {
980
+ branches.push(returnResult);
981
+ }
982
+ },
983
+ });
984
+ }
985
+ if (branches.length === 1) {
986
+ result = branches[0];
987
+ }
988
+ else if (branches.length > 1) {
989
+ result = { type: 'choice', nodes: branches };
430
990
  }
431
991
  }
432
- // Handle block body: () => { return "day"; }
433
- else if (!Array.isArray(bodyPath) &&
434
- t.isBlockStatement(bodyPath.node)) {
435
- const arrowFunction = init.node;
436
- bodyPath.traverse({
437
- Function(innerPath) {
438
- // Skip nested functions
439
- innerPath.skip();
440
- },
441
- ReturnStatement(returnPath) {
442
- // Only process return statements that are direct children of this function
443
- const parentFunction = returnPath.getFunctionParent();
444
- if (parentFunction?.node !== arrowFunction) {
445
- return;
446
- }
447
- if (!t.isReturnStatement(returnPath.node)) {
448
- return;
449
- }
450
- const returnArg = returnPath.node.argument;
451
- if (!returnArg || !t.isExpression(returnArg)) {
452
- return;
453
- }
454
- const returnResult = parseStringExpression(returnArg, returnPath, filePath, parsingOptions, warnings);
455
- if (returnResult !== null) {
456
- branches.push(returnResult);
457
- }
458
- },
459
- });
992
+ // Handle string/numeric/boolean/null constants
993
+ else if (t.isStringLiteral(init)) {
994
+ result = { type: 'text', text: init.value };
460
995
  }
461
- if (branches.length === 1) {
462
- result = branches[0];
996
+ else if (t.isNumericLiteral(init)) {
997
+ result = { type: 'text', text: String(init.value) };
463
998
  }
464
- else if (branches.length > 1) {
465
- result = { type: 'choice', nodes: branches };
999
+ else if (t.isBooleanLiteral(init)) {
1000
+ result = { type: 'text', text: String(init.value) };
1001
+ }
1002
+ // Handle template literals
1003
+ else if (t.isTemplateLiteral(init)) {
1004
+ const parts = [];
1005
+ let failed = false;
1006
+ for (let index = 0; index < init.quasis.length; index++) {
1007
+ const quasi = init.quasis[index];
1008
+ const text = quasi.value.cooked ?? quasi.value.raw ?? '';
1009
+ if (text) {
1010
+ parts.push({ type: 'text', text });
1011
+ }
1012
+ const exprNode = init.expressions[index];
1013
+ if (exprNode && t.isExpression(exprNode)) {
1014
+ const exprResult = parseStringExpression(exprNode, path, filePath, parsingOptions, warnings, errors);
1015
+ if (exprResult === null) {
1016
+ failed = true;
1017
+ break;
1018
+ }
1019
+ parts.push(exprResult);
1020
+ }
1021
+ }
1022
+ if (!failed) {
1023
+ if (parts.length === 0) {
1024
+ result = { type: 'text', text: '' };
1025
+ }
1026
+ else if (parts.length === 1) {
1027
+ result = parts[0];
1028
+ }
1029
+ else {
1030
+ result = { type: 'sequence', nodes: parts };
1031
+ }
1032
+ }
1033
+ }
1034
+ // Handle object expressions (and `as const` / `satisfies`)
1035
+ else if (t.isObjectExpression(init) ||
1036
+ t.isObjectExpression(unwrapTypeAnnotation(init))) {
1037
+ const objExpr = unwrapTypeAnnotation(init);
1038
+ const branches = [];
1039
+ for (const prop of objExpr.properties) {
1040
+ if (!t.isObjectProperty(prop) || !t.isExpression(prop.value))
1041
+ continue;
1042
+ const resolved = parseStringExpression(prop.value, path, filePath, parsingOptions, warnings, errors);
1043
+ if (resolved)
1044
+ branches.push(resolved);
1045
+ }
1046
+ if (branches.length === 1)
1047
+ result = branches[0];
1048
+ else if (branches.length > 1)
1049
+ result = { type: 'choice', nodes: branches };
466
1050
  }
467
1051
  }
468
1052
  },
@@ -18,12 +18,13 @@ export type StringTree = (string | StringTree)[];
18
18
  * - Only provide at entry for template macros, otherwise omit
19
19
  * - t`Hello {nonDerivableValue}` -> t`Hello {0}`
20
20
  */
21
- export declare function handleDerivation({ expr, tPath, file, parsingOptions, errors, runtimeInterpolationState, }: {
21
+ export declare function handleDerivation({ expr, tPath, file, parsingOptions, errors, warnings, runtimeInterpolationState, }: {
22
22
  expr: t.Expression;
23
23
  tPath: NodePath;
24
24
  file: string;
25
25
  parsingOptions: ParsingConfigOptions;
26
26
  errors: string[];
27
+ warnings: Set<string>;
27
28
  runtimeInterpolationState?: {
28
29
  index: number;
29
30
  };
@@ -38,7 +38,7 @@ const processFunctionCache = new Map();
38
38
  * - Only provide at entry for template macros, otherwise omit
39
39
  * - t`Hello {nonDerivableValue}` -> t`Hello {0}`
40
40
  */
41
- export function handleDerivation({ expr, tPath, file, parsingOptions, errors, runtimeInterpolationState, }) {
41
+ export function handleDerivation({ expr, tPath, file, parsingOptions, errors, warnings, runtimeInterpolationState, }) {
42
42
  if (!expr) {
43
43
  return null;
44
44
  }
@@ -51,6 +51,7 @@ export function handleDerivation({ expr, tPath, file, parsingOptions, errors, ru
51
51
  file,
52
52
  parsingOptions,
53
53
  errors,
54
+ warnings,
54
55
  });
55
56
  if (variants) {
56
57
  return {
@@ -98,6 +99,7 @@ export function handleDerivation({ expr, tPath, file, parsingOptions, errors, ru
98
99
  file,
99
100
  parsingOptions,
100
101
  errors,
102
+ warnings,
101
103
  });
102
104
  if (result === null)
103
105
  return null;
@@ -111,6 +113,7 @@ export function handleDerivation({ expr, tPath, file, parsingOptions, errors, ru
111
113
  file,
112
114
  parsingOptions,
113
115
  errors,
116
+ warnings,
114
117
  runtimeInterpolationState,
115
118
  });
116
119
  if (result === null)
@@ -138,6 +141,7 @@ export function handleDerivation({ expr, tPath, file, parsingOptions, errors, ru
138
141
  file,
139
142
  parsingOptions,
140
143
  errors,
144
+ warnings,
141
145
  runtimeInterpolationState,
142
146
  });
143
147
  const rightResult = handleDerivation({
@@ -146,6 +150,7 @@ export function handleDerivation({ expr, tPath, file, parsingOptions, errors, ru
146
150
  file,
147
151
  parsingOptions,
148
152
  errors,
153
+ warnings,
149
154
  runtimeInterpolationState,
150
155
  });
151
156
  if (leftResult === null || rightResult === null) {
@@ -161,6 +166,7 @@ export function handleDerivation({ expr, tPath, file, parsingOptions, errors, ru
161
166
  file,
162
167
  parsingOptions,
163
168
  errors,
169
+ warnings,
164
170
  runtimeInterpolationState,
165
171
  });
166
172
  }
@@ -214,7 +220,7 @@ export function handleDerivation({ expr, tPath, file, parsingOptions, errors, ru
214
220
  *
215
221
  * Returns null if it can't be resolved.
216
222
  */
217
- function getDeriveVariants({ call, tPath, file, parsingOptions, errors, }) {
223
+ function getDeriveVariants({ call, tPath, file, parsingOptions, errors, warnings, }) {
218
224
  // --- Validate Callee --- //
219
225
  // Must be a derive(...) call or an alias of it
220
226
  if (!isDeriveCall({ expr: call, tPath })) {
@@ -233,12 +239,12 @@ function getDeriveVariants({ call, tPath, file, parsingOptions, errors, }) {
233
239
  // Handle await expression: derive(await time())
234
240
  if (t.isAwaitExpression(arg)) {
235
241
  // Resolve the inner call's possible string outcomes
236
- return resolveCallStringVariants(arg.argument, tPath, file, parsingOptions, errors);
242
+ return resolveCallStringVariants(arg.argument, tPath, file, parsingOptions, errors, warnings);
237
243
  }
238
244
  // Resolve the inner call's possible string outcomes
239
- return resolveCallStringVariants(arg, tPath, file, parsingOptions, errors);
245
+ return resolveCallStringVariants(arg, tPath, file, parsingOptions, errors, warnings);
240
246
  }
241
- function resolveCallStringVariants(expression, tPath, file, parsingOptions, errors) {
247
+ function resolveCallStringVariants(expression, tPath, file, parsingOptions, errors, warnings) {
242
248
  // Handle function identifier calls: derive(time())
243
249
  if (t.isCallExpression(expression) && t.isIdentifier(expression.callee)) {
244
250
  const functionName = expression.callee.name;
@@ -268,7 +274,7 @@ function resolveCallStringVariants(expression, tPath, file, parsingOptions, erro
268
274
  if (importPath) {
269
275
  const filePath = resolveImportPath(file, importPath, parsingOptions, resolveImportPathCache);
270
276
  if (filePath && originalName) {
271
- const node = resolveFunctionInFile(filePath, originalName, parsingOptions, errors);
277
+ const node = resolveFunctionInFile(filePath, originalName, parsingOptions, errors, warnings);
272
278
  if (node) {
273
279
  return nodeToStrings(node);
274
280
  }
@@ -277,7 +283,7 @@ function resolveCallStringVariants(expression, tPath, file, parsingOptions, erro
277
283
  }
278
284
  else {
279
285
  // Function is local - use parseStringExpression with resolveFunctionCall
280
- const node = resolveFunctionCallFromBinding(calleeBinding, tPath, file, parsingOptions);
286
+ const node = resolveFunctionCallFromBinding(calleeBinding, tPath, file, parsingOptions, warnings, errors);
281
287
  if (node) {
282
288
  return nodeToStrings(node);
283
289
  }
@@ -290,7 +296,7 @@ function resolveCallStringVariants(expression, tPath, file, parsingOptions, erro
290
296
  }
291
297
  }
292
298
  // If we get here: analyze this call as derivable (statically analyzable)
293
- const node = parseStringExpression(expression, tPath, file, parsingOptions);
299
+ const node = parseStringExpression(expression, tPath, file, parsingOptions, warnings, errors);
294
300
  if (node) {
295
301
  return nodeToStrings(node);
296
302
  }
@@ -299,7 +305,7 @@ function resolveCallStringVariants(expression, tPath, file, parsingOptions, erro
299
305
  /**
300
306
  * Resolves a function from a binding (local function) using parseStringExpression logic
301
307
  */
302
- function resolveFunctionCallFromBinding(calleeBinding, tPath, file, parsingOptions) {
308
+ function resolveFunctionCallFromBinding(calleeBinding, tPath, file, parsingOptions, warnings, errors) {
303
309
  if (!calleeBinding) {
304
310
  return null;
305
311
  }
@@ -318,7 +324,7 @@ function resolveFunctionCallFromBinding(calleeBinding, tPath, file, parsingOptio
318
324
  if (!returnArg || !t.isExpression(returnArg)) {
319
325
  return;
320
326
  }
321
- const returnResult = parseStringExpression(returnArg, returnPath, file, parsingOptions);
327
+ const returnResult = parseStringExpression(returnArg, returnPath, file, parsingOptions, warnings, errors);
322
328
  if (returnResult !== null) {
323
329
  branches.push(returnResult);
324
330
  }
@@ -334,7 +340,7 @@ function resolveFunctionCallFromBinding(calleeBinding, tPath, file, parsingOptio
334
340
  const body = init.get('body');
335
341
  // Handle expression body: () => "day"
336
342
  if (body.isExpression()) {
337
- const bodyResult = parseStringExpression(body.node, body, file, parsingOptions);
343
+ const bodyResult = parseStringExpression(body.node, body, file, parsingOptions, warnings, errors);
338
344
  if (bodyResult !== null) {
339
345
  branches.push(bodyResult);
340
346
  }
@@ -353,7 +359,7 @@ function resolveFunctionCallFromBinding(calleeBinding, tPath, file, parsingOptio
353
359
  if (!returnArg || !t.isExpression(returnArg)) {
354
360
  return;
355
361
  }
356
- const returnResult = parseStringExpression(returnArg, returnPath, file, parsingOptions);
362
+ const returnResult = parseStringExpression(returnArg, returnPath, file, parsingOptions, warnings, errors);
357
363
  if (returnResult !== null) {
358
364
  branches.push(returnResult);
359
365
  }
@@ -372,7 +378,7 @@ function resolveFunctionCallFromBinding(calleeBinding, tPath, file, parsingOptio
372
378
  /**
373
379
  * Resolves a function definition in an external file
374
380
  */
375
- function resolveFunctionInFile(filePath, functionName, parsingOptions, errors) {
381
+ function resolveFunctionInFile(filePath, functionName, parsingOptions, errors, warnings) {
376
382
  // Check cache first
377
383
  const cacheKey = `${filePath}::${functionName}`;
378
384
  if (processFunctionCache.has(cacheKey)) {
@@ -396,7 +402,7 @@ function resolveFunctionInFile(filePath, functionName, parsingOptions, errors) {
396
402
  const resolvedPath = resolveImportPath(filePath, reexportPath, parsingOptions, resolveImportPathCache);
397
403
  if (resolvedPath) {
398
404
  // Recursively resolve in the re-exported file
399
- const reexportResult = resolveFunctionInFile(resolvedPath, functionName, parsingOptions, errors);
405
+ const reexportResult = resolveFunctionInFile(resolvedPath, functionName, parsingOptions, errors, warnings);
400
406
  if (reexportResult) {
401
407
  result = reexportResult;
402
408
  }
@@ -441,7 +447,7 @@ function resolveFunctionInFile(filePath, functionName, parsingOptions, errors) {
441
447
  originalName = specifier.local.name;
442
448
  }
443
449
  // Recursively resolve in the re-exported file
444
- const reexportResult = resolveFunctionInFile(resolvedPath, originalName, parsingOptions, errors);
450
+ const reexportResult = resolveFunctionInFile(resolvedPath, originalName, parsingOptions, errors, warnings);
445
451
  if (reexportResult) {
446
452
  result = reexportResult;
447
453
  }
@@ -466,7 +472,7 @@ function resolveFunctionInFile(filePath, functionName, parsingOptions, errors) {
466
472
  if (!returnArg || !t.isExpression(returnArg)) {
467
473
  return;
468
474
  }
469
- const returnResult = parseStringExpression(returnArg, returnPath, filePath, parsingOptions);
475
+ const returnResult = parseStringExpression(returnArg, returnPath, filePath, parsingOptions, warnings, errors);
470
476
  if (returnResult !== null) {
471
477
  branches.push(returnResult);
472
478
  }
@@ -497,7 +503,7 @@ function resolveFunctionInFile(filePath, functionName, parsingOptions, errors) {
497
503
  const branches = [];
498
504
  // Handle expression body: () => "day"
499
505
  if (!Array.isArray(bodyPath) && t.isExpression(bodyPath.node)) {
500
- const bodyResult = parseStringExpression(bodyPath.node, bodyPath, filePath, parsingOptions);
506
+ const bodyResult = parseStringExpression(bodyPath.node, bodyPath, filePath, parsingOptions, warnings, errors);
501
507
  if (bodyResult !== null) {
502
508
  branches.push(bodyResult);
503
509
  }
@@ -524,7 +530,7 @@ function resolveFunctionInFile(filePath, functionName, parsingOptions, errors) {
524
530
  if (!returnArg || !t.isExpression(returnArg)) {
525
531
  return;
526
532
  }
527
- const returnResult = parseStringExpression(returnArg, returnPath, filePath, parsingOptions);
533
+ const returnResult = parseStringExpression(returnArg, returnPath, filePath, parsingOptions, warnings, errors);
528
534
  if (returnResult !== null) {
529
535
  branches.push(returnResult);
530
536
  }
@@ -27,6 +27,7 @@ export function deriveExpression({ tPath, expr, metadata, config, output, index,
27
27
  file: config.file,
28
28
  parsingOptions: config.parsingOptions,
29
29
  errors: output.errors,
30
+ warnings: output.warnings,
30
31
  runtimeInterpolationState: enableRuntimeInterpolation
31
32
  ? { index: 0 }
32
33
  : undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gt",
3
- "version": "2.11.3",
3
+ "version": "2.12.0",
4
4
  "main": "dist/index.js",
5
5
  "bin": "dist/main.js",
6
6
  "files": [
@@ -110,8 +110,8 @@
110
110
  "unified": "^11.0.5",
111
111
  "unist-util-visit": "^5.0.0",
112
112
  "yaml": "^2.8.0",
113
+ "@generaltranslation/python-extractor": "0.2.0",
113
114
  "generaltranslation": "8.1.20",
114
- "@generaltranslation/python-extractor": "0.1.6",
115
115
  "gt-remark": "1.0.6"
116
116
  },
117
117
  "devDependencies": {