react-i18next-scanner 0.1.5 → 0.1.6
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.
|
@@ -154,106 +154,157 @@ function getJsxAttribute(attributes, attributeName) {
|
|
|
154
154
|
}
|
|
155
155
|
return undefined;
|
|
156
156
|
}
|
|
157
|
+
function unwrapExpression(expression) {
|
|
158
|
+
let current = expression;
|
|
159
|
+
while (true) {
|
|
160
|
+
if (ts.isParenthesizedExpression(current)) {
|
|
161
|
+
current = current.expression;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (ts.isAsExpression(current)) {
|
|
165
|
+
current = current.expression;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (ts.isTypeAssertionExpression(current)) {
|
|
169
|
+
current = current.expression;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (ts.isNonNullExpression(current)) {
|
|
173
|
+
current = current.expression;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (ts.isCallExpression(current) &&
|
|
177
|
+
ts.isIdentifier(current.expression) &&
|
|
178
|
+
current.expression.text === "String" &&
|
|
179
|
+
current.arguments[0]) {
|
|
180
|
+
current = current.arguments[0];
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
return current;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
157
186
|
function extractPropertyChain(expression) {
|
|
158
|
-
|
|
187
|
+
const unwrapped = unwrapExpression(expression);
|
|
188
|
+
if (ts.isIdentifier(unwrapped)) {
|
|
159
189
|
return {
|
|
160
|
-
root:
|
|
190
|
+
root: unwrapped.text,
|
|
161
191
|
path: [],
|
|
162
192
|
};
|
|
163
193
|
}
|
|
164
|
-
if (ts.isPropertyAccessExpression(
|
|
165
|
-
const left = extractPropertyChain(
|
|
194
|
+
if (ts.isPropertyAccessExpression(unwrapped)) {
|
|
195
|
+
const left = extractPropertyChain(unwrapped.expression);
|
|
166
196
|
if (!left) {
|
|
167
197
|
return null;
|
|
168
198
|
}
|
|
169
199
|
return {
|
|
170
200
|
root: left.root,
|
|
171
|
-
path: [...left.path,
|
|
201
|
+
path: [...left.path, unwrapped.name.text],
|
|
172
202
|
};
|
|
173
203
|
}
|
|
174
|
-
if (ts.isElementAccessExpression(
|
|
175
|
-
const left = extractPropertyChain(
|
|
176
|
-
if (!left
|
|
204
|
+
if (ts.isElementAccessExpression(unwrapped)) {
|
|
205
|
+
const left = extractPropertyChain(unwrapped.expression);
|
|
206
|
+
if (!left) {
|
|
177
207
|
return null;
|
|
178
208
|
}
|
|
179
|
-
|
|
180
|
-
|
|
209
|
+
const argument = unwrapped.argumentExpression;
|
|
210
|
+
if (argument &&
|
|
211
|
+
(ts.isStringLiteral(argument) ||
|
|
212
|
+
ts.isNoSubstitutionTemplateLiteral(argument))) {
|
|
181
213
|
return {
|
|
182
214
|
root: left.root,
|
|
183
|
-
path: [...left.path,
|
|
215
|
+
path: [...left.path, argument.text],
|
|
184
216
|
};
|
|
185
217
|
}
|
|
218
|
+
return {
|
|
219
|
+
root: left.root,
|
|
220
|
+
path: [...left.path, "*"],
|
|
221
|
+
};
|
|
186
222
|
}
|
|
187
223
|
return null;
|
|
188
224
|
}
|
|
189
|
-
function
|
|
190
|
-
const
|
|
191
|
-
if (!
|
|
192
|
-
return
|
|
225
|
+
function resolvePropertyChainToTranslationKeys(expression, currentFilePath, importBindings, objectRegistry, availableFiles) {
|
|
226
|
+
const access = resolveImportedObjectAccess(expression, currentFilePath, importBindings, objectRegistry, availableFiles);
|
|
227
|
+
if (!access) {
|
|
228
|
+
return new Set();
|
|
193
229
|
}
|
|
194
|
-
const
|
|
195
|
-
if (!
|
|
196
|
-
return
|
|
197
|
-
}
|
|
198
|
-
const importedFilePath = resolveImportSource(currentFilePath, binding.source, availableFiles);
|
|
199
|
-
if (!importedFilePath) {
|
|
200
|
-
return null;
|
|
230
|
+
const objectMap = objectRegistry.get(`${access.importedFilePath}::${access.importedName}`);
|
|
231
|
+
if (!objectMap || access.path.length === 0) {
|
|
232
|
+
return new Set();
|
|
201
233
|
}
|
|
202
|
-
|
|
203
|
-
if (!objectMap) {
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
const propertyPath = chain.path.join(".");
|
|
207
|
-
if (!propertyPath) {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
return objectMap.get(propertyPath) ?? null;
|
|
234
|
+
return resolveObjectValuesByPath(objectMap, access.path);
|
|
211
235
|
}
|
|
212
236
|
function collectLocalAliases(sourceFile, currentFilePath, importBindings, objectRegistry, availableFiles) {
|
|
213
|
-
const
|
|
237
|
+
const objectAliases = new Map();
|
|
238
|
+
const keyAliases = new Map();
|
|
214
239
|
function visit(node) {
|
|
215
240
|
if (ts.isVariableDeclaration(node) &&
|
|
216
241
|
ts.isIdentifier(node.name) &&
|
|
217
242
|
node.initializer) {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
243
|
+
const initializer = unwrapExpression(node.initializer);
|
|
244
|
+
const importedAccess = resolveImportedObjectAccess(initializer, currentFilePath, importBindings, objectRegistry, availableFiles);
|
|
245
|
+
if (importedAccess) {
|
|
246
|
+
const keys = resolvePropertyChainToTranslationKeys(initializer, currentFilePath, importBindings, objectRegistry, availableFiles);
|
|
247
|
+
if (keys.size > 0) {
|
|
248
|
+
keyAliases.set(node.name.text, keys);
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
objectAliases.set(node.name.text, importedAccess);
|
|
252
|
+
}
|
|
221
253
|
}
|
|
222
254
|
}
|
|
223
255
|
ts.forEachChild(node, visit);
|
|
224
256
|
}
|
|
225
257
|
visit(sourceFile);
|
|
226
|
-
return
|
|
258
|
+
return { objectAliases, keyAliases };
|
|
259
|
+
}
|
|
260
|
+
function resolveAliasPropertyAccessToTranslationKeys(expression, objectAliases, keyAliases, objectRegistry) {
|
|
261
|
+
const unwrapped = unwrapExpression(expression);
|
|
262
|
+
if (ts.isIdentifier(unwrapped)) {
|
|
263
|
+
return keyAliases.get(unwrapped.text) ?? new Set();
|
|
264
|
+
}
|
|
265
|
+
const chain = extractPropertyChain(unwrapped);
|
|
266
|
+
if (!chain) {
|
|
267
|
+
return new Set();
|
|
268
|
+
}
|
|
269
|
+
const objectAlias = objectAliases.get(chain.root);
|
|
270
|
+
if (!objectAlias) {
|
|
271
|
+
return new Set();
|
|
272
|
+
}
|
|
273
|
+
const objectMap = objectRegistry.get(`${objectAlias.importedFilePath}::${objectAlias.importedName}`);
|
|
274
|
+
if (!objectMap) {
|
|
275
|
+
return new Set();
|
|
276
|
+
}
|
|
277
|
+
const fullPath = [...objectAlias.path, ...chain.path];
|
|
278
|
+
return resolveObjectValuesByPath(objectMap, fullPath);
|
|
227
279
|
}
|
|
228
280
|
export async function collectResolvedImportedObjectTranslationKeys(filePaths) {
|
|
281
|
+
const normalizedFilePaths = filePaths.map((filePath) => path.resolve(filePath));
|
|
229
282
|
const sourceFiles = new Map();
|
|
230
|
-
const availableFiles = new Set(
|
|
283
|
+
const availableFiles = new Set(normalizedFilePaths);
|
|
231
284
|
const resolvedKeys = new Set();
|
|
232
|
-
for (const filePath of
|
|
285
|
+
for (const filePath of normalizedFilePaths) {
|
|
233
286
|
const content = await fs.readFile(filePath, "utf-8");
|
|
234
287
|
sourceFiles.set(filePath, createSourceFile(filePath, content));
|
|
235
288
|
}
|
|
236
|
-
const objectRegistry = buildObjectRegistry(
|
|
237
|
-
for (const filePath of
|
|
289
|
+
const objectRegistry = buildObjectRegistry(normalizedFilePaths, sourceFiles);
|
|
290
|
+
for (const filePath of normalizedFilePaths) {
|
|
238
291
|
const sourceFile = sourceFiles.get(filePath);
|
|
239
292
|
if (!sourceFile) {
|
|
240
293
|
continue;
|
|
241
294
|
}
|
|
242
295
|
const importBindings = collectImportBindings(sourceFile);
|
|
243
|
-
const
|
|
296
|
+
const { objectAliases, keyAliases } = collectLocalAliases(sourceFile, filePath, importBindings, objectRegistry, availableFiles);
|
|
244
297
|
function visit(node) {
|
|
245
298
|
if (ts.isCallExpression(node) && isTranslationCall(node)) {
|
|
246
299
|
const firstArg = node.arguments[0];
|
|
247
300
|
if (firstArg) {
|
|
248
|
-
const directResolved =
|
|
249
|
-
|
|
250
|
-
resolvedKeys.add(
|
|
301
|
+
const directResolved = resolvePropertyChainToTranslationKeys(firstArg, filePath, importBindings, objectRegistry, availableFiles);
|
|
302
|
+
for (const key of directResolved) {
|
|
303
|
+
resolvedKeys.add(key);
|
|
251
304
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
resolvedKeys.add(aliasResolved);
|
|
256
|
-
}
|
|
305
|
+
const aliasResolved = resolveAliasPropertyAccessToTranslationKeys(firstArg, objectAliases, keyAliases, objectRegistry);
|
|
306
|
+
for (const key of aliasResolved) {
|
|
307
|
+
resolvedKeys.add(key);
|
|
257
308
|
}
|
|
258
309
|
}
|
|
259
310
|
}
|
|
@@ -273,18 +324,57 @@ export async function collectResolvedImportedObjectTranslationKeys(filePaths) {
|
|
|
273
324
|
return;
|
|
274
325
|
}
|
|
275
326
|
const expr = i18nKeyAttr.initializer.expression;
|
|
276
|
-
const directResolved =
|
|
277
|
-
|
|
278
|
-
resolvedKeys.add(
|
|
327
|
+
const directResolved = resolvePropertyChainToTranslationKeys(expr, filePath, importBindings, objectRegistry, availableFiles);
|
|
328
|
+
for (const key of directResolved) {
|
|
329
|
+
resolvedKeys.add(key);
|
|
279
330
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
resolvedKeys.add(aliasResolved);
|
|
284
|
-
}
|
|
331
|
+
const aliasResolved = resolveAliasPropertyAccessToTranslationKeys(expr, objectAliases, keyAliases, objectRegistry);
|
|
332
|
+
for (const key of aliasResolved) {
|
|
333
|
+
resolvedKeys.add(key);
|
|
285
334
|
}
|
|
286
335
|
}
|
|
287
336
|
visit(sourceFile);
|
|
288
337
|
}
|
|
289
338
|
return resolvedKeys;
|
|
290
339
|
}
|
|
340
|
+
function matchObjectPath(pattern, actual) {
|
|
341
|
+
if (pattern.length !== actual.length) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
return pattern.every((part, index) => {
|
|
345
|
+
return part === "*" || part === actual[index];
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
function resolveObjectValuesByPath(objectMap, pathPattern) {
|
|
349
|
+
const result = new Set();
|
|
350
|
+
for (const [objectPath, translationKey] of objectMap.entries()) {
|
|
351
|
+
const actualPath = objectPath.split(".");
|
|
352
|
+
if (matchObjectPath(pathPattern, actualPath)) {
|
|
353
|
+
result.add(translationKey);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
function resolveImportedObjectAccess(expression, currentFilePath, importBindings, objectRegistry, availableFiles) {
|
|
359
|
+
const chain = extractPropertyChain(expression);
|
|
360
|
+
if (!chain) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
const binding = importBindings.get(chain.root);
|
|
364
|
+
if (!binding) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
const importedFilePath = resolveImportSource(currentFilePath, binding.source, availableFiles);
|
|
368
|
+
if (!importedFilePath) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
const objectMap = objectRegistry.get(`${importedFilePath}::${binding.importedName}`);
|
|
372
|
+
if (!objectMap) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
importedFilePath,
|
|
377
|
+
importedName: binding.importedName,
|
|
378
|
+
path: chain.path,
|
|
379
|
+
};
|
|
380
|
+
}
|