Skip to content

Commit f7bbdfa

Browse files
committed
🔧 fix: Fix bug applying chnages back to nested objects created via {...foo, ...bar}
1 parent 8f7db8f commit f7bbdfa

File tree

2 files changed

+88
-59
lines changed

2 files changed

+88
-59
lines changed

‎src/ast/applyDifferencesUsingASTOrThrow.ts

Lines changed: 62 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import {
1111
import { logger } from '../util/Logger.ts';
1212
import { createTsProject } from './createTsProject.ts';
1313
import type { DiffResult } from './DiffResult.ts';
14-
import { getExportedVariableNamesAndValues } from './getExportedVariableNamesAndValues.ts';
14+
import {
15+
getAllVariableNamesAndValuesInFile,
16+
getExportedVariableNamesAndValues,
17+
} from './getExportedVariableNamesAndValues.ts';
1518

1619
/**
1720
Local function to unwrap AsExpressions and TypeAssertions
@@ -38,6 +41,8 @@ function findAndModify(
3841
localDeclarations: Map<string, Expression>,
3942
): boolean
4043
{
44+
objExpr = unwrapExpression(objExpr);
45+
4146
if (path.length === 0)
4247
{
4348
return false;
@@ -64,7 +69,9 @@ function findAndModify(
6469
if (objExpr.getKind() !== SyntaxKind.ObjectLiteralExpression)
6570
{
6671
throw new Error(
67-
`Expected an object literal or reference to a local variable, got: ${SyntaxKind[objExpr.getKind()]}`,
72+
`Expected an object literal or reference to a local variable, got: ${
73+
SyntaxKind[objExpr.getKind()]
74+
} after unwrapping`,
6875
);
6976
}
7077

@@ -79,49 +86,67 @@ function findAndModify(
7986
let modifiedInSpread = false;
8087
for (const child of objectLiteral.getChildrenOfKind(SyntaxKind.SpreadAssignment))
8188
{
82-
const spreadExpr = child.getExpression();
89+
const spreadExpr = unwrapExpression(child.getExpression()); // Ensure spread expression itself is unwrapped
8390
// We expect the spread expression to be an identifier referencing a known local declaration
8491
if (spreadExpr.getKind() === SyntaxKind.Identifier)
8592
{
8693
const identifierName = spreadExpr.getText();
8794
const localValue = localDeclarations.get(identifierName);
8895
if (localValue)
8996
{
90-
// Try to find and modify within the spread source object.
91-
// IMPORTANT: Pass the original 'path', not 'remainingPath', because the currentKey
92-
// is expected to be a property within the spread source object.
93-
if (findAndModify(localValue, path, newValue, localDeclarations))
97+
try
98+
{
99+
logger.debug('AST', `[Spread Try] Searching for path '${path.join('.')}' in spread '${identifierName}'`);
100+
// localValue will be unwrapped at the start of the recursive findAndModify call
101+
if (findAndModify(localValue, path, newValue, localDeclarations))
102+
{
103+
modifiedInSpread = true;
104+
break; // Found in this spread for the current path, no need to check other spreads.
105+
}
106+
}
107+
catch (error)
94108
{
95-
modifiedInSpread = true;
96-
break; // Found and modified, stop searching other spreads
109+
if (error instanceof Error)
110+
{
111+
logger.debug('AST',
112+
`[Spread Catch] Path '${
113+
path.join('.')
114+
}' not found in spread '${identifierName}' (or other error during its processing): ${error.message}`);
115+
}
116+
else
117+
{
118+
logger.debug('AST',
119+
`[Spread Catch] Path '${
120+
path.join('.')
121+
}' not found in spread '${identifierName}' (or other error during its processing): Unknown error type`);
122+
}
123+
// Continue to the next spread assignment
97124
}
98125
}
99126
else
100127
{
101-
// This might indicate an issue if a spread identifier isn't resolvable,
102-
// but we'll allow the search to continue in other spreads or properties.
103-
console.warn(`Spread identifier '${identifierName}' not found in local declarations.`);
128+
logger.warn('AST', `Spread identifier '${identifierName}' not found in local declarations.`);
104129
}
105130
}
106131
else
107132
{
108-
// Log if the spread expression isn't a simple identifier, as we don't handle complex spreads yet.
109-
console.warn(`Spread assignment expression is not an identifier: ${SyntaxKind[spreadExpr.getKind()]}`);
133+
logger.warn('AST',
134+
`Spread assignment expression is not an identifier or simple expression: ${
135+
SyntaxKind[spreadExpr.getKind()]
136+
}`);
110137
}
111138
}
112139

113140
if (modifiedInSpread)
114141
{
115-
return true; // Modification was successful within a spread assignment
142+
return true; // Modification was successful within a spread assignment for the current path
116143
}
117144

118-
// If not found in direct properties or any spread assignments, then it's truly not found.
145+
// If not found in direct properties or any spread assignments, then it's truly not found for this object literal.
119146
throw new Error(
120-
`Property '${currentKey}' not found in object literal or its spread assignments. Path: ${
121-
path.join(
122-
'.',
123-
)
124-
}. Object text: ${objectLiteral.getText().substring(0, 100)}...`, // Added object text for context
147+
`Property '${currentKey}' not found in object literal (or its spreads). Path: ${path.join('.')}. Object text: ${
148+
objectLiteral.getText().substring(0, 200)
149+
}...`,
125150
);
126151
}
127152

@@ -242,52 +267,30 @@ export function applyDifferencesUsingASTOrThrow(
242267

243268
const appliedKeypaths: string[] = [];
244269

270+
const allVarsInScope = getAllVariableNamesAndValuesInFile(sourceFile);
245271
const localDeclarations = new Map<string, Expression>();
246-
sourceFile.getVariableDeclarations().forEach(decl =>
272+
for (const { name, value } of allVarsInScope)
247273
{
248-
const name = decl.getName();
249-
const initializer = decl.getInitializer();
250-
if (initializer)
274+
if (value)
251275
{
252-
localDeclarations.set(name, unwrapExpression(initializer));
276+
localDeclarations.set(name, value);
253277
}
254-
});
255-
256-
// Find the exported constants
257-
const exportedConsts = sourceFile.getVariableStatements().filter(stmt => stmt.isExported());
258-
259-
const exportedNamesAndValues = getExportedVariableNamesAndValues(sourceFile);
260-
261-
logger.info('AST', 'EXPORTED NAMES:', exportedNamesAndValues.map(e => e.name));
262-
263-
// HERE I NEED TO FIGURE OUT THE NAMES OF ALL EXPORTED VARIABLES
264-
// diff is like:
265-
// {
266-
// "foo.name.en": { left: "Mason", right: "Boatie McBoatface" },
267-
// "foo.name.ja": { left: "メイソン", right: "ボーティー マックボートフェイス" }
268-
// }
269-
270-
// exportedNamesAndValues is like:
271-
// [{name: 'foo', value: AsExpression}]
278+
}
272279

273-
// 📖 OUTER LOOP: EXPORTED CONSTS
274-
//
275-
// We loop over the exported consts of the file, looking for a diff entry.
276-
//
277-
// This is not efficient, when you contemplate the diff being a huge diff generated by compating a combined JSON file for say, 500 files, with a single file (because we have the inner loops over every keyPath in the diff, below). However, we can fix that upstream of this method, because we can pare down the JSON to exclude unrelated translations. And it is actually desirable to be **able** to do the inefficient huge comparison of the "all translations vs one file", because that is useful for E2E tests of the entire system's integrity.
280+
const exportedVarsForIteration = getExportedVariableNamesAndValues(sourceFile);
278281

279-
for (const exportedConst of exportedConsts)
282+
// Iterate over each exported variable declaration found in the source file.
283+
// For each keyPath in the diff, we check if it starts with the name of one of these exported variables.
284+
for (const exportedVar of exportedVarsForIteration)
280285
{
281-
logger.debug('AST', 'exportedConst', exportedConst.getText());
282-
283-
const declarations = exportedConst.getDeclarations();
284-
logger.debug('AST', 'declarations', declarations.map(d => d.getText()));
286+
const name = exportedVar.name;
287+
const declaration = sourceFile.getVariableDeclaration(name);
285288

286-
const declaration = declarations[0];
287-
// logger.debug('AST', 'declaration', declaration);
288-
289-
const name = declaration.getName();
290-
logger.debug('AST', 'dec[0] name', name);
289+
if (!declaration)
290+
{
291+
logger.warn('AST', `Could not find declaration for exported variable: ${name}`);
292+
continue;
293+
}
291294

292295
const initializer = declaration.getInitializer();
293296
logger.debug('AST', 'initializer', initializer?.getKindName());

‎src/ast/getExportedVariableNamesAndValues.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,29 @@ export function getExportedVariableNamesAndValues(
4949

5050
return results;
5151
}
52+
53+
/**
54+
Returns an array of objects representing all variable declarations in the given source file, regardless of whether they are exported or not. This is useful for resolving identifiers that might be used in spread syntax within exported objects.
55+
*/
56+
export function getAllVariableNamesAndValuesInFile(
57+
sourceFile: ReturnType<Project['createSourceFile']>,
58+
): Array<{ name: string; value: Expression | undefined }>
59+
{
60+
// Find ALL VariableStatements in the file
61+
const allVarsInFile = sourceFile.getVariableStatements();
62+
63+
const results: Array<{ name: string; value: Expression | undefined }> = [];
64+
65+
allVarsInFile.forEach((stmt: VariableStatement) =>
66+
{
67+
stmt.getDeclarations().forEach((decl) =>
68+
{
69+
results.push({
70+
name: decl.getName(),
71+
value: decl.getInitializer(), // could be undefined if no initializer
72+
});
73+
});
74+
});
75+
76+
return results;
77+
}

0 commit comments

Comments
 (0)