@twin.org/validate-locales 0.0.2-next.22 → 0.0.3-next.1

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/bin/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // Copyright 2024 IOTA Stiftung.
3
3
  // SPDX-License-Identifier: Apache-2.0.
4
- import { CLI } from '../dist/esm/index.mjs';
4
+ import { CLI } from '../dist/es/index.js';
5
5
 
6
6
  const cli = new CLI();
7
7
  const result = await cli.run(process.argv);
package/dist/es/cli.js ADDED
@@ -0,0 +1,37 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { CLIBase } from "@twin.org/cli-core";
6
+ import { buildCommandValidateLocales } from "./commands/validateLocales.js";
7
+ /**
8
+ * The main entry point for the CLI.
9
+ */
10
+ export class CLI extends CLIBase {
11
+ /**
12
+ * Run the app.
13
+ * @param argv The process arguments.
14
+ * @param localesDirectory The directory for the locales, default to relative to the script.
15
+ * @param options Additional options.
16
+ * @param options.overrideOutputWidth Override the output width.
17
+ * @returns The exit code.
18
+ */
19
+ async run(argv, localesDirectory, options) {
20
+ return this.execute({
21
+ title: "TWIN Validate Locales",
22
+ appName: "validate-locales",
23
+ version: "0.0.3-next.1", // x-release-please-version
24
+ icon: "⚙️ ",
25
+ supportsEnvFiles: false,
26
+ overrideOutputWidth: options?.overrideOutputWidth
27
+ }, localesDirectory ?? path.join(path.dirname(fileURLToPath(import.meta.url)), "../locales"), argv);
28
+ }
29
+ /**
30
+ * Configure any options or actions at the root program level.
31
+ * @param program The root program command.
32
+ */
33
+ configureRoot(program) {
34
+ buildCommandValidateLocales(program);
35
+ }
36
+ }
37
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAE5E;;GAEG;AACH,MAAM,OAAO,GAAI,SAAQ,OAAO;IAC/B;;;;;;;OAOG;IACI,KAAK,CAAC,GAAG,CACf,IAAc,EACd,gBAAyB,EACzB,OAA0C;QAE1C,OAAO,IAAI,CAAC,OAAO,CAClB;YACC,KAAK,EAAE,uBAAuB;YAC9B,OAAO,EAAE,kBAAkB;YAC3B,OAAO,EAAE,cAAc,EAAE,2BAA2B;YACpD,IAAI,EAAE,KAAK;YACX,gBAAgB,EAAE,KAAK;YACvB,mBAAmB,EAAE,OAAO,EAAE,mBAAmB;SACjD,EACD,gBAAgB,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,EACzF,IAAI,CACJ,CAAC;IACH,CAAC;IAED;;;OAGG;IACO,aAAa,CAAC,OAAgB;QACvC,2BAA2B,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;CACD","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { CLIBase } from \"@twin.org/cli-core\";\nimport type { Command } from \"commander\";\nimport { buildCommandValidateLocales } from \"./commands/validateLocales.js\";\n\n/**\n * The main entry point for the CLI.\n */\nexport class CLI extends CLIBase {\n\t/**\n\t * Run the app.\n\t * @param argv The process arguments.\n\t * @param localesDirectory The directory for the locales, default to relative to the script.\n\t * @param options Additional options.\n\t * @param options.overrideOutputWidth Override the output width.\n\t * @returns The exit code.\n\t */\n\tpublic async run(\n\t\targv: string[],\n\t\tlocalesDirectory?: string,\n\t\toptions?: { overrideOutputWidth?: number }\n\t): Promise<number> {\n\t\treturn this.execute(\n\t\t\t{\n\t\t\t\ttitle: \"TWIN Validate Locales\",\n\t\t\t\tappName: \"validate-locales\",\n\t\t\t\tversion: \"0.0.3-next.1\", // x-release-please-version\n\t\t\t\ticon: \"⚙️ \",\n\t\t\t\tsupportsEnvFiles: false,\n\t\t\t\toverrideOutputWidth: options?.overrideOutputWidth\n\t\t\t},\n\t\t\tlocalesDirectory ?? path.join(path.dirname(fileURLToPath(import.meta.url)), \"../locales\"),\n\t\t\targv\n\t\t);\n\t}\n\n\t/**\n\t * Configure any options or actions at the root program level.\n\t * @param program The root program command.\n\t */\n\tprotected configureRoot(program: Command): void {\n\t\tbuildCommandValidateLocales(program);\n\t}\n}\n"]}
@@ -1,14 +1,12 @@
1
- import path from 'node:path';
2
- import { fileURLToPath } from 'node:url';
3
- import { CLIDisplay, CLIUtils, CLIBase } from '@twin.org/cli-core';
4
- import { readFile } from 'node:fs/promises';
5
- import { I18n, Is, GeneralError, StringHelper } from '@twin.org/core';
6
- import { manual } from '@twin.org/nameof-transformer';
7
- import * as glob from 'glob';
8
- import * as ts from 'typescript';
9
-
10
1
  // Copyright 2024 IOTA Stiftung.
11
2
  // SPDX-License-Identifier: Apache-2.0.
3
+ import { readFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { CLIDisplay, CLIUtils } from "@twin.org/cli-core";
6
+ import { GeneralError, I18n, Is, StringHelper } from "@twin.org/core";
7
+ import { manual } from "@twin.org/nameof-transformer";
8
+ import * as glob from "glob";
9
+ import * as ts from "typescript";
12
10
  const ERROR_TYPES = [
13
11
  { name: "GeneralError", dynamicPropertyIndex: 2 },
14
12
  { name: "GuardError", dynamicPropertyIndex: -1 },
@@ -33,7 +31,8 @@ const SKIP_LITERALS = [
33
31
  /\.lock$/i, // ending in .lock
34
32
  /\.toml$/i, // ending in .toml
35
33
  /\.{3}/i, // ...
36
- /@twin\.org/ // starting with @twin.org
34
+ /@twin\.org/, // starting with @twin.org
35
+ /^console.log/i // console.log
37
36
  ];
38
37
  const SKIP_METHODS = [/^generateRest/, /^generateSocket/];
39
38
  const CAPTURE_VARIABLES = [/ROUTES_SOURCE/];
@@ -41,7 +40,7 @@ const CAPTURE_VARIABLES = [/ROUTES_SOURCE/];
41
40
  * Build the root command to be consumed by the CLI.
42
41
  * @param program The command to build on.
43
42
  */
44
- function buildCommandValidateLocales(program) {
43
+ export function buildCommandValidateLocales(program) {
45
44
  program
46
45
  .option(I18n.formatMessage("commands.validate-locales.options.source.param"), I18n.formatMessage("commands.validate-locales.options.source.description"), "src/**/*.ts")
47
46
  .option(I18n.formatMessage("commands.validate-locales.options.locales.param"), I18n.formatMessage("commands.validate-locales.options.locales.description"), "locales/**/*.json")
@@ -57,7 +56,7 @@ function buildCommandValidateLocales(program) {
57
56
  * @param opts.locales The locales glob.
58
57
  * @param opts.ignoreFile The ignore file path.
59
58
  */
60
- async function actionCommandValidateLocales(opts) {
59
+ export async function actionCommandValidateLocales(opts) {
61
60
  opts.source = path.resolve(opts.source);
62
61
  opts.locales = path.resolve(opts.locales);
63
62
  opts.ignoreFile = path.resolve(opts.ignoreFile);
@@ -196,7 +195,7 @@ function visit(sourceFile, node, localeEntries, failures, captureVariables) {
196
195
  handled = processCallExpression(sourceFile, node, localeEntries, failures, captureVariables);
197
196
  }
198
197
  else if (ts.isFunctionDeclaration(node)) {
199
- handled = processFunctionDeclaration(sourceFile, node);
198
+ handled = processFunctionDeclaration(sourceFile, node, localeEntries, failures);
200
199
  }
201
200
  else if (ts.isVariableDeclaration(node)) {
202
201
  handled = processVariableDeclaration(sourceFile, node, localeEntries, failures, captureVariables);
@@ -344,7 +343,10 @@ function processTemplateExpression(sourceFile, node, localeEntries, failures) {
344
343
  function processCallExpression(sourceFile, node, localeEntries, failures, captureVariables) {
345
344
  if (ts.isPropertyAccessExpression(node.expression)) {
346
345
  const functionName = node.expression.name.getText();
347
- if (functionName === "log" &&
346
+ if (node.expression.getText() === "console.log") {
347
+ return true;
348
+ }
349
+ else if (functionName === "log" &&
348
350
  node.arguments.length === 1 &&
349
351
  ts.isObjectLiteralExpression(node.arguments[0])) {
350
352
  let level;
@@ -743,38 +745,4 @@ function localeFromClassAndMessage(sourceFile, classNode, messageNode, prefix, f
743
745
  }
744
746
  return undefined;
745
747
  }
746
-
747
- // Copyright 2024 IOTA Stiftung.
748
- // SPDX-License-Identifier: Apache-2.0.
749
- /**
750
- * The main entry point for the CLI.
751
- */
752
- class CLI extends CLIBase {
753
- /**
754
- * Run the app.
755
- * @param argv The process arguments.
756
- * @param localesDirectory The directory for the locales, default to relative to the script.
757
- * @param options Additional options.
758
- * @param options.overrideOutputWidth Override the output width.
759
- * @returns The exit code.
760
- */
761
- async run(argv, localesDirectory, options) {
762
- return this.execute({
763
- title: "TWIN Validate Locales",
764
- appName: "validate-locales",
765
- version: "0.0.2-next.22", // x-release-please-version
766
- icon: "⚙️ ",
767
- supportsEnvFiles: false,
768
- overrideOutputWidth: options?.overrideOutputWidth
769
- }, localesDirectory ?? path.join(path.dirname(fileURLToPath(import.meta.url)), "../locales"), argv);
770
- }
771
- /**
772
- * Configure any options or actions at the root program level.
773
- * @param program The root program command.
774
- */
775
- configureRoot(program) {
776
- buildCommandValidateLocales(program);
777
- }
778
- }
779
-
780
- export { CLI, actionCommandValidateLocales, buildCommandValidateLocales };
748
+ //# sourceMappingURL=validateLocales.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateLocales.js","sourceRoot":"","sources":["../../../src/commands/validateLocales.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { CLIDisplay, CLIUtils } from \"@twin.org/cli-core\";\nimport { GeneralError, I18n, Is, StringHelper, type ILocaleDictionary } from \"@twin.org/core\";\nimport { manual } from \"@twin.org/nameof-transformer\";\nimport type { Command } from \"commander\";\nimport * as glob from \"glob\";\nimport * as ts from \"typescript\";\nimport type { ILocaleDictionaryEntry } from \"../models/ILocaleDictionaryEntry.js\";\nimport type { ILocaleFailure } from \"../models/ILocaleFailure.js\";\n\nconst ERROR_TYPES = [\n\t{ name: \"GeneralError\", dynamicPropertyIndex: 2 },\n\t{ name: \"GuardError\", dynamicPropertyIndex: -1 },\n\t{ name: \"ValidationError\", dynamicPropertyIndex: -1 },\n\t{ name: \"FetchError\", dynamicPropertyIndex: 3, inbuiltProperties: [\"httpStatus\"] },\n\t{ name: \"NotFoundError\", dynamicPropertyIndex: 3, inbuiltProperties: [\"notFoundId\"] },\n\t{ name: \"AlreadyExistsError\", dynamicPropertyIndex: 3, inbuiltProperties: [\"existingId\"] },\n\t{ name: \"UnauthorizedError\", dynamicPropertyIndex: 2 },\n\t{ name: \"NotSupportedError\", dynamicPropertyIndex: 3, inbuiltProperties: [\"methodName\"] },\n\t{ name: \"UnprocessableError\", dynamicPropertyIndex: 2 },\n\t{ name: \"ConflictError\", dynamicPropertyIndex: 4, inbuiltProperties: [\"conflictId\", \"conflicts\"] }\n];\n\nconst SKIP_FILES = [\"**/models/**/*.ts\"];\n\nconst SKIP_LITERALS = [\n\t/^\\d+\\.\\d+\\.\\d+(-\\w+(\\.\\w+)*)?(-\\d)?$/, // Version string\n\t/^[^@]+@[^@]+\\.[^@]+$/, // Email string\n\t/\\.json$/i, // ending in .json\n\t/\\.js$/i, // ending in .js\n\t/\\.ts$/i, // ending in .ts\n\t/\\.env$/i, // ending in .env\n\t/\\.png$/i, // ending in .png\n\t/\\.lock$/i, // ending in .lock\n\t/\\.toml$/i, // ending in .toml\n\t/\\.{3}/i, // ...\n\t/@twin\\.org/, // starting with @twin.org\n\t/^console.log/i // console.log\n];\n\nconst SKIP_METHODS = [/^generateRest/, /^generateSocket/];\n\nconst CAPTURE_VARIABLES = [/ROUTES_SOURCE/];\n\n/**\n * Build the root command to be consumed by the CLI.\n * @param program The command to build on.\n */\nexport function buildCommandValidateLocales(program: Command): void {\n\tprogram\n\t\t.option(\n\t\t\tI18n.formatMessage(\"commands.validate-locales.options.source.param\"),\n\t\t\tI18n.formatMessage(\"commands.validate-locales.options.source.description\"),\n\t\t\t\"src/**/*.ts\"\n\t\t)\n\t\t.option(\n\t\t\tI18n.formatMessage(\"commands.validate-locales.options.locales.param\"),\n\t\t\tI18n.formatMessage(\"commands.validate-locales.options.locales.description\"),\n\t\t\t\"locales/**/*.json\"\n\t\t)\n\t\t.option(\n\t\t\tI18n.formatMessage(\"commands.validate-locales.options.ignoreFile.param\"),\n\t\t\tI18n.formatMessage(\"commands.validate-locales.options.ignoreFile.description\"),\n\t\t\t\"locales/.validate-ignore\"\n\t\t)\n\t\t.action(async opts => {\n\t\t\tawait actionCommandValidateLocales(opts);\n\t\t});\n}\n\n/**\n * Action the root command.\n * @param opts The options for the command.\n * @param opts.source The source glob.\n * @param opts.locales The locales glob.\n * @param opts.ignoreFile The ignore file path.\n */\nexport async function actionCommandValidateLocales(opts: {\n\tsource: string;\n\tlocales: string;\n\tignoreFile: string;\n}): Promise<void> {\n\topts.source = path.resolve(opts.source);\n\topts.locales = path.resolve(opts.locales);\n\topts.ignoreFile = path.resolve(opts.ignoreFile);\n\n\tCLIDisplay.value(I18n.formatMessage(\"commands.validate-locales.labels.source\"), opts.source);\n\tCLIDisplay.value(I18n.formatMessage(\"commands.validate-locales.labels.locales\"), opts.locales);\n\tlet ignore: string[] = [];\n\n\tif (await CLIUtils.fileExists(opts.ignoreFile)) {\n\t\tCLIDisplay.value(\n\t\t\tI18n.formatMessage(\"commands.validate-locales.labels.ignoreFile\"),\n\t\t\topts.ignoreFile\n\t\t);\n\t\tignore = (await CLIUtils.readLinesFile(opts.ignoreFile)) ?? [];\n\t}\n\n\tCLIDisplay.break();\n\n\tconst sources = glob.sync(opts.source.replace(/\\\\/g, \"/\"), { ignore: SKIP_FILES });\n\tconst locales = glob.sync(opts.locales.replace(/\\\\/g, \"/\"));\n\n\tif (sources.length === 0) {\n\t\tCLIDisplay.warning(I18n.formatMessage(\"commands.validate-locales.warnings.noSourceFiles\"));\n\t} else if (locales.length === 0) {\n\t\tCLIDisplay.warning(I18n.formatMessage(\"commands.validate-locales.warnings.noLocaleFiles\"));\n\t} else {\n\t\tawait validateLocales(\n\t\t\tsources,\n\t\t\tlocales,\n\t\t\tignore.filter(i => Is.stringValue(i) && !i.startsWith(\"#\")).map(i => new RegExp(i))\n\t\t);\n\t}\n\n\tCLIDisplay.break();\n\tCLIDisplay.done();\n}\n\n/**\n * Validate the locales.\n * @param sourceFiles The source files.\n * @param localeFiles The locale files.\n * @param ignore The ignore list.\n */\nasync function validateLocales(\n\tsourceFiles: string[],\n\tlocaleFiles: string[],\n\tignore: RegExp[]\n): Promise<void> {\n\tconst localeEntries: ILocaleDictionaryEntry[] = [];\n\n\tlet hasQuoteError = false;\n\tlet hasUnused = false;\n\tlet hasFailures = false;\n\n\tfor (const localeFile of localeFiles) {\n\t\tconst dictionary = await CLIUtils.readJsonFile<ILocaleDictionary>(localeFile);\n\t\tconst locale = path.basename(localeFile, path.extname(localeFile));\n\n\t\tCLIDisplay.task(\n\t\t\tI18n.formatMessage(\"commands.validate-locales.progress.validatingLocale\", { localeFile })\n\t\t);\n\t\tCLIDisplay.break();\n\n\t\tif (Is.object<ILocaleDictionary>(dictionary)) {\n\t\t\tconst mergedKeys: { [key: string]: string } = {};\n\t\t\tI18n.flattenTranslationKeys(dictionary, \"\", mergedKeys);\n\n\t\t\tfor (const flatKey of Object.keys(mergedKeys)) {\n\t\t\t\tif (/'{.*?}'/.test(mergedKeys[flatKey])) {\n\t\t\t\t\tCLIDisplay.errorMessage(\n\t\t\t\t\t\tI18n.formatMessage(\"error.validateLocales.usesSingleQuotes\", {\n\t\t\t\t\t\t\tkey: flatKey\n\t\t\t\t\t\t})\n\t\t\t\t\t);\n\t\t\t\t\thasQuoteError = true;\n\t\t\t\t}\n\n\t\t\t\tlocaleEntries.push({\n\t\t\t\t\tlocale,\n\t\t\t\t\tkey: flatKey,\n\t\t\t\t\tvalue: mergedKeys[flatKey],\n\t\t\t\t\tpropertyNames: I18n.getPropertyNames(mergedKeys[flatKey]),\n\t\t\t\t\treferenced: false\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tlet failures: ILocaleFailure[] = [];\n\t\tconst captureVariables: { [name: string]: ts.Node } = {};\n\n\t\tfor (const sourceFile of sourceFiles) {\n\t\t\tconst source = await readFile(sourceFile, \"utf8\");\n\n\t\t\tconst sourceTs = ts.createSourceFile(\n\t\t\t\tsourceFile,\n\t\t\t\tsource,\n\t\t\t\tts.ScriptTarget.ESNext,\n\t\t\t\ttrue,\n\t\t\t\tts.ScriptKind.TS\n\t\t\t);\n\n\t\t\tvisit(sourceTs, sourceTs, localeEntries, failures, captureVariables);\n\t\t}\n\n\t\tfailures = failures.filter(mr => !ignore.some(pattern => pattern.test(mr.key)));\n\n\t\tif (failures.length === 0) {\n\t\t\tCLIDisplay.write(\n\t\t\t\tI18n.formatMessage(\"commands.validate-locales.labels.noMissingLocaleEntries\")\n\t\t\t);\n\t\t\tCLIDisplay.break();\n\t\t} else {\n\t\t\thasFailures = true;\n\t\t\tfor (const failureRef of failures) {\n\t\t\t\tif (failureRef.type === \"key\") {\n\t\t\t\t\tCLIDisplay.errorMessage(\n\t\t\t\t\t\tI18n.formatMessage(\"error.validateLocales.missingLocaleEntry\", {\n\t\t\t\t\t\t\tkey: failureRef.key,\n\t\t\t\t\t\t\tsource: failureRef.source,\n\t\t\t\t\t\t\tline: failureRef.line,\n\t\t\t\t\t\t\tcolumn: failureRef.column\n\t\t\t\t\t\t})\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tCLIDisplay.break();\n\n\t\tfor (const localeEntry of localeEntries) {\n\t\t\tif (ignore.some(pattern => pattern.test(localeEntry.key))) {\n\t\t\t\tlocaleEntry.referenced = true;\n\t\t\t}\n\t\t}\n\n\t\tif (localeEntries.filter(le => !le.referenced).length === 0) {\n\t\t\tCLIDisplay.write(\n\t\t\t\tI18n.formatMessage(\"commands.validate-locales.labels.noUnusedLocaleEntries\")\n\t\t\t);\n\t\t\tCLIDisplay.break();\n\t\t} else {\n\t\t\thasUnused = true;\n\t\t\tfor (const localeEntry of localeEntries) {\n\t\t\t\tif (!localeEntry.referenced) {\n\t\t\t\t\tCLIDisplay.errorMessage(\n\t\t\t\t\t\tI18n.formatMessage(\"error.validateLocales.unusedLocaleEntry\", {\n\t\t\t\t\t\t\tkey: localeEntry.key\n\t\t\t\t\t\t})\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (hasFailures || hasUnused || hasQuoteError) {\n\t\tthrow new GeneralError(\"validateLocales\", \"validationFailed\");\n\t}\n}\n\n/**\n * Visit the node.\n * @param sourceFile The TypeScript source file for position calculations.\n * @param node The node to visit.\n * @param localeEntries The locale entries.\n * @param failures The failure entries.\n * @param captureVariables The capture variables.\n */\nfunction visit(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.Node,\n\tlocaleEntries: ILocaleDictionaryEntry[],\n\tfailures: ILocaleFailure[],\n\tcaptureVariables: { [name: string]: ts.Node }\n): void {\n\tlet handled = false;\n\tif (\n\t\tts.isNewExpression(node) &&\n\t\tts.isIdentifier(node.expression) &&\n\t\tERROR_TYPES.some(errorType => errorType.name === node.expression.getText())\n\t) {\n\t\tprocessErrorType(sourceFile, node, node.expression.text, localeEntries, failures);\n\t\thandled = true;\n\t} else if (ts.isStringLiteral(node)) {\n\t\tprocessStringLiteral(sourceFile, node, localeEntries, failures);\n\t\thandled = true;\n\t} else if (ts.isTemplateExpression(node)) {\n\t\tprocessTemplateExpression(sourceFile, node, localeEntries, failures);\n\t\thandled = true;\n\t} else if (ts.isCallExpression(node)) {\n\t\thandled = processCallExpression(sourceFile, node, localeEntries, failures, captureVariables);\n\t} else if (ts.isFunctionDeclaration(node)) {\n\t\thandled = processFunctionDeclaration(sourceFile, node, localeEntries, failures);\n\t} else if (ts.isVariableDeclaration(node)) {\n\t\thandled = processVariableDeclaration(\n\t\t\tsourceFile,\n\t\t\tnode,\n\t\t\tlocaleEntries,\n\t\t\tfailures,\n\t\t\tcaptureVariables\n\t\t);\n\t} else if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {\n\t\t// Don't care about string in imports/exports\n\t\thandled = true;\n\t} else if (ts.isPropertyAssignment(node)) {\n\t\thandled = processPropertyAssignment(sourceFile, node, localeEntries, failures);\n\t}\n\n\tif (!handled) {\n\t\tts.forEachChild(node, child =>\n\t\t\tvisit(sourceFile, child, localeEntries, failures, captureVariables)\n\t\t);\n\t}\n}\n\n/**\n * Process an error type node.\n * @param sourceFile The TypeScript source file for position calculations.\n * @param node The node to process.\n * @param errorType The error type.\n * @param localeEntries The locale entries.\n * @param failures The failure entries.\n */\nfunction processErrorType(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.NewExpression,\n\terrorType: string,\n\tlocaleEntries: ILocaleDictionaryEntry[],\n\tfailures: ILocaleFailure[]\n): void {\n\tconst errType = ERROR_TYPES.find(e => e.name === errorType);\n\n\tif (Is.object(errType)) {\n\t\tconst localeKey = localeFromClassAndMessage(\n\t\t\tsourceFile,\n\t\t\tnode.arguments?.[0],\n\t\t\tnode.arguments?.[1],\n\t\t\t\"error\",\n\t\t\tfailures\n\t\t);\n\n\t\tif (Is.stringValue(localeKey)) {\n\t\t\tconst localeEntry = findAndReferenceLocale(localeEntries, localeKey);\n\n\t\t\tif (Is.object(localeEntry)) {\n\t\t\t\tif (errType.dynamicPropertyIndex !== -1 || Is.arrayValue(errType.inbuiltProperties)) {\n\t\t\t\t\tconst usedProperties = errType.inbuiltProperties?.slice() ?? [];\n\n\t\t\t\t\tif (errType.dynamicPropertyIndex !== -1) {\n\t\t\t\t\t\tusedProperties.push(\n\t\t\t\t\t\t\t...getPropertiesFromNode(node.arguments?.[errType.dynamicPropertyIndex])\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tcheckPropertyUsage(sourceFile, node, localeEntry, localeKey, usedProperties, failures);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfailures.push({\n\t\t\t\t\ttype: \"key\",\n\t\t\t\t\tkey: localeKey,\n\t\t\t\t\tsource: path.resolve(sourceFile.fileName),\n\t\t\t\t\t...getSourcePosition(sourceFile, node)\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\n\tCLIDisplay.errorMessage(\n\t\tI18n.formatMessage(\"error.validateLocales.unableToProcessContent\", { content: node.getText() })\n\t);\n}\n\n/**\n * Find the locale entry if it exists.\n * @param localeEntries The locale entries.\n * @param entryToMatch The full key to check.\n * @returns The item if found, undefined otherwise.\n */\nfunction findAndReferenceLocale(\n\tlocaleEntries: ILocaleDictionaryEntry[],\n\tentryToMatch: string\n): ILocaleDictionaryEntry | undefined {\n\tif (Is.stringValue(entryToMatch)) {\n\t\tconst found = localeEntries.find(le => le.key === entryToMatch);\n\t\tif (found) {\n\t\t\tfound.referenced = true;\n\t\t\treturn found;\n\t\t}\n\t}\n}\n\n/**\n * Process a string literal node.\n * @param sourceFile The TypeScript source file for position calculations.\n * @param node The node to process.\n * @param localeEntries The locale entries.\n * @param failures The failure entries.\n */\nfunction processStringLiteral(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.StringLiteral,\n\tlocaleEntries: ILocaleDictionaryEntry[],\n\tfailures: ILocaleFailure[]\n): void {\n\tif (\n\t\tnode.text.length > 3 &&\n\t\tnode.text.includes(\".\") &&\n\t\t!/[ ()/]/.test(node.text) &&\n\t\t!/^\\.|\\.$/.test(node.text) &&\n\t\t!isSkipLiteral(node.text)\n\t) {\n\t\tconst parts = node.text.split(\".\");\n\t\tif (parts.length > 1) {\n\t\t\t// First try and match the string as-is\n\t\t\tlet localeEntry = findAndReferenceLocale(localeEntries, node.text);\n\t\t\tif (localeEntry) {\n\t\t\t\tlocaleEntry.referenced = true;\n\n\t\t\t\tconst usedProperties = getPropertiesFromNode(node);\n\t\t\t\tcheckPropertyUsage(sourceFile, node, localeEntry, node.text, usedProperties, failures);\n\t\t\t}\n\n\t\t\tif (!localeEntry && [\"validation.\", \"common.\"].some(t => node.text.startsWith(t))) {\n\t\t\t\tlocaleEntry = findAndReferenceLocale(localeEntries, `error.${node.text}`);\n\t\t\t\tif (localeEntry) {\n\t\t\t\t\tlocaleEntry.referenced = true;\n\t\t\t\t\tconst usedProperties = getPropertiesFromNode(node);\n\t\t\t\t\tcheckPropertyUsage(\n\t\t\t\t\t\tsourceFile,\n\t\t\t\t\t\tnode,\n\t\t\t\t\t\tlocaleEntry,\n\t\t\t\t\t\t`error.${node.text}`,\n\t\t\t\t\t\tusedProperties,\n\t\t\t\t\t\tfailures\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!localeEntry) {\n\t\t\t\tfailures.push({\n\t\t\t\t\ttype: \"key\",\n\t\t\t\t\tkey: node.text,\n\t\t\t\t\tsource: path.resolve(sourceFile.fileName),\n\t\t\t\t\t...getSourcePosition(sourceFile, node)\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Process a template expression node.\n * @param sourceFile The TypeScript source file for position calculations.\n * @param node The node to process.\n * @param localeEntries The locale entries.\n * @param failures The failure entries.\n */\nfunction processTemplateExpression(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.TemplateExpression,\n\tlocaleEntries: ILocaleDictionaryEntry[],\n\tfailures: ILocaleFailure[]\n): void {\n\t// This case handles templates like `error.${nameof(Class)}.message`\n\tconst templateParts = extractTemplatePartsWithExpressions(node);\n\n\t// Join all literal text parts to form a potential locale key\n\tif (hasValidTemplateContent(templateParts)) {\n\t\tconst key = expandTemplateParts(templateParts);\n\n\t\tlet localeEntry = findAndReferenceLocale(localeEntries, key);\n\t\tif (localeEntry) {\n\t\t\tconst usedProperties = getPropertiesFromNode(node);\n\t\t\tcheckPropertyUsage(sourceFile, node, localeEntry, localeEntry.key, usedProperties, failures);\n\t\t} else if ([\"validation.\", \"common.\"].some(t => key.startsWith(t))) {\n\t\t\tlocaleEntry = findAndReferenceLocale(localeEntries, `error.${key}`);\n\n\t\t\tif (localeEntry) {\n\t\t\t\tlocaleEntry.referenced = true;\n\t\t\t\tconst usedProperties = getPropertiesFromNode(node.parent.parent);\n\n\t\t\t\tcheckPropertyUsage(sourceFile, node, localeEntry, `error.${key}`, usedProperties, failures);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Process a call expression node.\n * @param sourceFile The TypeScript source file for position calculations.\n * @param node The node to process.\n * @param localeEntries The locale entries.\n * @param failures The failure entries.\n * @param captureVariables The capture variables.\n * @returns True if processed, false otherwise.\n */\nfunction processCallExpression(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.CallExpression,\n\tlocaleEntries: ILocaleDictionaryEntry[],\n\tfailures: ILocaleFailure[],\n\tcaptureVariables: { [name: string]: ts.Node }\n): boolean {\n\tif (ts.isPropertyAccessExpression(node.expression)) {\n\t\tconst functionName = node.expression.name.getText();\n\t\tif (node.expression.getText() === \"console.log\") {\n\t\t\treturn true;\n\t\t} else if (\n\t\t\tfunctionName === \"log\" &&\n\t\t\tnode.arguments.length === 1 &&\n\t\t\tts.isObjectLiteralExpression(node.arguments[0])\n\t\t) {\n\t\t\tlet level;\n\t\t\tlet source;\n\t\t\tlet message;\n\t\t\tlet dataNames;\n\t\t\tfor (const prop of node.arguments[0].properties) {\n\t\t\t\tif (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {\n\t\t\t\t\tif (prop.name.text === \"source\") {\n\t\t\t\t\t\tif (ts.isIdentifier(prop.initializer) && captureVariables[prop.initializer.text]) {\n\t\t\t\t\t\t\tsource = captureVariables[prop.initializer.text];\n\t\t\t\t\t\t} else if (ts.isStringLiteral(prop.initializer)) {\n\t\t\t\t\t\t\tsource = prop.initializer;\n\t\t\t\t\t\t} else if (ts.isPropertyAccessExpression(prop.initializer)) {\n\t\t\t\t\t\t\tsource = prop.initializer;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (prop.name.text === \"message\") {\n\t\t\t\t\t\tmessage = prop.initializer;\n\t\t\t\t\t} else if (prop.name.text === \"data\") {\n\t\t\t\t\t\tdataNames = getPropertiesFromNode(prop.initializer);\n\t\t\t\t\t} else if (prop.name.text === \"level\") {\n\t\t\t\t\t\tlevel = getExpandedText(prop.initializer);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst localeKey = localeFromClassAndMessage(sourceFile, source, message, level, failures);\n\n\t\t\tif (Is.stringValue(localeKey)) {\n\t\t\t\tconst localeEntry = findAndReferenceLocale(localeEntries, localeKey);\n\n\t\t\t\tif (Is.object(localeEntry)) {\n\t\t\t\t\tcheckPropertyUsage(sourceFile, node, localeEntry, localeKey, dataNames ?? [], failures);\n\t\t\t\t} else {\n\t\t\t\t\tfailures.push({\n\t\t\t\t\t\ttype: \"key\",\n\t\t\t\t\t\tkey: localeKey,\n\t\t\t\t\t\tsource: path.resolve(sourceFile.fileName),\n\t\t\t\t\t\t...getSourcePosition(sourceFile, node)\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t} else if (\n\t\t\tfunctionName === \"formatMessage\" &&\n\t\t\tnode.arguments.length === 2 &&\n\t\t\tts.isTemplateExpression(node.arguments[0]) &&\n\t\t\tts.isObjectLiteralExpression(node.arguments[1])\n\t\t) {\n\t\t\tconst localeKey = getExpandedText(node.arguments[0]);\n\n\t\t\tif (Is.stringValue(localeKey)) {\n\t\t\t\tconst dataNames = getPropertiesFromNode(node.arguments[1]);\n\t\t\t\tconst localeEntry = findAndReferenceLocale(localeEntries, localeKey);\n\n\t\t\t\tif (Is.object(localeEntry)) {\n\t\t\t\t\tcheckPropertyUsage(sourceFile, node, localeEntry, localeKey, dataNames ?? [], failures);\n\t\t\t\t} else {\n\t\t\t\t\tfailures.push({\n\t\t\t\t\t\ttype: \"key\",\n\t\t\t\t\t\tkey: localeKey,\n\t\t\t\t\t\tsource: path.resolve(sourceFile.fileName),\n\t\t\t\t\t\t...getSourcePosition(sourceFile, node)\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Process a function declaration node.\n * @param sourceFile The TypeScript source file for position calculations.\n * @param node The node to process.\n * @param localeEntries The locale entries.\n * @param failures The failure entries.\n * @returns True if processed, false otherwise.\n */\nfunction processFunctionDeclaration(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.FunctionDeclaration,\n\tlocaleEntries: ILocaleDictionaryEntry[],\n\tfailures: ILocaleFailure[]\n): boolean {\n\tif (\n\t\tIs.object(node.name) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tSKIP_METHODS.some(re => re.test(node.name?.text ?? \"\"))\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Process a variable statement declaration node.\n * @param sourceFile The TypeScript source file for position calculations.\n * @param node The node to process.\n * @param localeEntries The locale entries.\n * @param failures The failure entries.\n * @param captureVariables The capture variables.\n * @returns True if processed, false otherwise.\n */\nfunction processVariableDeclaration(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.VariableDeclaration,\n\tlocaleEntries: ILocaleDictionaryEntry[],\n\tfailures: ILocaleFailure[],\n\tcaptureVariables: { [name: string]: ts.Node }\n): boolean {\n\tif (\n\t\tIs.object(node.name) &&\n\t\tIs.object(node.initializer) &&\n\t\tts.isIdentifier(node.name) &&\n\t\tts.isStringLiteral(node.initializer) &&\n\t\tCAPTURE_VARIABLES.some(re => re.test(node.name.getText()))\n\t) {\n\t\tcaptureVariables[node.name.getText()] = node.initializer;\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Process a property assignment node.\n * @param sourceFile The TypeScript source file for position calculations.\n * @param node The node to process.\n * @param localeEntries The locale entries.\n * @param failures The failure entries.\n * @returns True if processed, false otherwise.\n */\nfunction processPropertyAssignment(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.PropertyAssignment,\n\tlocaleEntries: ILocaleDictionaryEntry[],\n\tfailures: ILocaleFailure[]\n): boolean {\n\tif (Is.object(node.name) && ts.isIdentifier(node.name) && node.name.getText() === \"message\") {\n\t\tconst localeKey = getExpandedText(node.initializer);\n\n\t\tif (Is.stringValue(localeKey)) {\n\t\t\tlet localeEntry = findAndReferenceLocale(localeEntries, localeKey);\n\n\t\t\tif (!Is.object(localeEntry)) {\n\t\t\t\tlocaleEntry = findAndReferenceLocale(localeEntries, `error.${localeKey}`);\n\t\t\t}\n\n\t\t\tif (!Is.object(localeEntry)) {\n\t\t\t\tfailures.push({\n\t\t\t\t\ttype: \"key\",\n\t\t\t\t\tkey: localeKey,\n\t\t\t\t\tsource: path.resolve(sourceFile.fileName),\n\t\t\t\t\t...getSourcePosition(sourceFile, node)\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Get the expanded text from a node.\n * @param node The node to get the text from.\n * @returns The expanded text.\n */\nfunction getExpandedText(node: ts.Node): string {\n\tif (ts.isTemplateExpression(node)) {\n\t\tconst templateParts = extractTemplatePartsWithExpressions(node);\n\t\tif (hasValidTemplateContent(templateParts)) {\n\t\t\treturn expandTemplateParts(templateParts);\n\t\t}\n\t} else if (ts.isStringLiteral(node)) {\n\t\tif (hasValidTemplateContent([node.text])) {\n\t\t\treturn node.text;\n\t\t}\n\t}\n\n\treturn \"\";\n}\n\n/**\n * Extract parts from a template literal, splitting by ${} expressions.\n * @param node The template expression node.\n * @returns Array of parts including expressions and literal text.\n */\nfunction extractTemplatePartsWithExpressions(node: ts.TemplateExpression): string[] {\n\tconst parts: string[] = [];\n\n\t// Start with the head (text before first ${})\n\tif (node.head.text) {\n\t\tparts.push(node.head.text);\n\t}\n\n\t// Process each template span (${expression}text)\n\tfor (const span of node.templateSpans) {\n\t\t// Add the expression part as text\n\t\tparts.push(span.expression.getText());\n\n\t\t// Add the literal text after the expression\n\t\tif (span.literal.text) {\n\t\t\tparts.push(span.literal.text);\n\t\t}\n\t}\n\n\treturn parts.filter(part => part.length > 0);\n}\n\n/**\n * Check if the content has any parts which contain elements which determine is is not an locale key.\n * @param templateParts The template parts to check.\n * @returns True if the template parts are valid, false otherwise.\n */\nfunction hasValidTemplateContent(templateParts: string[]): boolean {\n\treturn !templateParts.some(part => /[#,/:=?|]/.test(part));\n}\n\n/**\n * Expand template parts into a single string, processing nameof expressions.\n * @param templateParts The template parts to expand.\n * @returns The expanded template string.\n */\nfunction expandTemplateParts(templateParts: string[]): string {\n\tfor (let i = 0; i < templateParts.length; i++) {\n\t\ttemplateParts[i] = expandTemplatePart(templateParts[i]);\n\t}\n\n\treturn templateParts.join(\"\");\n}\n\n/**\n * Expand template part into a single string, processing nameof expressions.\n * @param templatePart The template part to expand.\n * @returns The expanded template string.\n */\nfunction expandTemplatePart(templatePart: string): string {\n\tif (templatePart.startsWith(\"nameof\")) {\n\t\tconst stripped = manual(templatePart).replace(/[\"']/g, \"\");\n\t\ttemplatePart = StringHelper.camelCase(stripped, true);\n\t} else if (templatePart.startsWith(\"StringHelper.\")) {\n\t\ttemplatePart = templatePart.replace(/\\.CLASS_NAME/g, \"\");\n\n\t\ttemplatePart = templatePart.replace(/StringHelper\\.camelCase\\((.*?)\\)/, (_, i) =>\n\t\t\tStringHelper.camelCase(i)\n\t\t);\n\t\ttemplatePart = templatePart.replace(/StringHelper\\.titleCase\\((.*?)\\)/, (_, i) =>\n\t\t\tStringHelper.titleCase(i)\n\t\t);\n\t\ttemplatePart = templatePart.replace(/StringHelper\\.pascalCase\\((.*?)\\)/, (_, i) =>\n\t\t\tStringHelper.pascalCase(i)\n\t\t);\n\t\ttemplatePart = templatePart.replace(/StringHelper\\.kebabCase\\((.*?)\\)/, (_, i) =>\n\t\t\tStringHelper.kebabCase(i)\n\t\t);\n\t\ttemplatePart = templatePart.replace(/StringHelper\\.snakeCase\\((.*?)\\)/, (_, i) =>\n\t\t\tStringHelper.snakeCase(i)\n\t\t);\n\t} else if (templatePart.includes(\".CLASS_NAME\")) {\n\t\ttemplatePart = StringHelper.camelCase(templatePart.replace(/\\.CLASS_NAME/g, \"\"));\n\t}\n\ttemplatePart = templatePart.replace(/[\"'`]/g, \"\"); // Remove quotes\n\n\treturn templatePart;\n}\n\n/**\n * Check the property usage in a locale entry against the used properties.\n * @param sourceFile The TypeScript source file for position calculations.\n * @param node The node to check.\n * @param localeEntry The locale entry to check against.\n * @param key The key in the locale entry.\n * @param usedProperties The properties used in the code.\n * @param failures The failure entries.\n */\nfunction checkPropertyUsage(\n\tsourceFile: ts.SourceFile,\n\tnode: ts.Node,\n\tlocaleEntry: ILocaleDictionaryEntry,\n\tkey: string,\n\tusedProperties: string[],\n\tfailures: ILocaleFailure[]\n): void {\n\tfor (const propName of localeEntry.propertyNames) {\n\t\tconst propIndex = usedProperties.indexOf(propName);\n\t\tif (propIndex === -1) {\n\t\t\tconst position = getSourcePosition(sourceFile, node);\n\t\t\tCLIDisplay.errorMessage(\n\t\t\t\tI18n.formatMessage(\"error.validateLocales.missingPropertyInLocale\", {\n\t\t\t\t\tkey,\n\t\t\t\t\tproperty: propName,\n\t\t\t\t\tsource: path.resolve(sourceFile.fileName),\n\t\t\t\t\tline: position.line,\n\t\t\t\t\tcolumn: position.column\n\t\t\t\t})\n\t\t\t);\n\t\t\tfailures.push({\n\t\t\t\ttype: \"property\",\n\t\t\t\tkey: localeEntry.key,\n\t\t\t\tsource: path.resolve(sourceFile.fileName),\n\t\t\t\t...position\n\t\t\t});\n\t\t}\n\t}\n\n\t// We often pass additional properties in the error details to better inform the logging\n\t// so we don't want to perform the opposite check for parameters in the call but not in the locale entry\n}\n\n/**\n * Helper to get line and column position from a node.\n * @param sourceFile The TypeScript source file for position calculations.\n * @param n The node to get position for.\n * @returns Line and column (1-based).\n */\nfunction getSourcePosition(\n\tsourceFile: ts.SourceFile,\n\tn: ts.Node\n): { line: number; column: number } {\n\tconst { line, character } = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile));\n\treturn { line: line + 1, column: character + 1 };\n}\n\n/**\n * Get property names from a node.\n * @param node The node to get property names from.\n * @returns The property names.\n */\nfunction getPropertiesFromNode(node?: ts.Node): string[] {\n\tif (!node) {\n\t\treturn [];\n\t}\n\n\tconst props: string[] = [];\n\n\t// If this is an object literal then we can get the property names\n\tif (ts.isObjectLiteralExpression(node)) {\n\t\t// Get the properties of the object literal\n\t\tconst properties = node.properties;\n\t\tfor (const prop of properties) {\n\t\t\tif (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {\n\t\t\t\t// { property: value }\n\t\t\t\tprops.push(prop.name.text);\n\n\t\t\t\t// { property: { nestedProperty: value } }\n\t\t\t\tif (ts.isObjectLiteralExpression(prop.initializer)) {\n\t\t\t\t\t// Recursively get properties from nested object literals\n\t\t\t\t\tconst nestedProps = getPropertiesFromNode(prop.initializer);\n\t\t\t\t\tprops.push(...nestedProps);\n\t\t\t\t}\n\t\t\t} else if (ts.isShorthandPropertyAssignment(prop)) {\n\t\t\t\t// { property }\n\t\t\t\tprops.push(prop.getText());\n\t\t\t}\n\t\t}\n\t} else if (ts.isStringLiteral(node)) {\n\t\t// If this is a string literal then there are no properties\n\t\t// so we need to look in the surrounding context\n\t\tconst parent = node.parent;\n\t\tif (ts.isCallExpression(parent) || ts.isNewExpression(parent)) {\n\t\t\t// Let's see if they are the next argument in the call\n\t\t\tconst args = parent.arguments;\n\t\t\tif (args && args.length > 0) {\n\t\t\t\tconst index = args.findIndex(a => a === node);\n\t\t\t\tif (index !== -1 && index + 1 < args.length) {\n\t\t\t\t\treturn getPropertiesFromNode(args[index + 1]);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (ts.isPropertyAssignment(parent)) {\n\t\t\t// This is part of a property assignment in an object\n\t\t\t// so we can check the parent object for other properties\n\t\t\tif (ts.isObjectLiteralExpression(parent.parent)) {\n\t\t\t\treturn getPropertiesFromNode(parent.parent);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn props;\n}\n\n/**\n * Check if a value is a file operation.\n * @param value The value to check.\n * @returns True if the value is a file operation.\n */\nfunction isSkipLiteral(value: string): boolean {\n\treturn SKIP_LITERALS.some(regex => regex.test(value));\n}\n\n/**\n * Get the locale from the class and message nodes.\n * @param sourceFile The TypeScript source file for position calculations.\n * @param classNode The class node.\n * @param messageNode The message node.\n * @param prefix The prefix for the locale key.\n * @param failures The failure entries.\n * @returns The locale entry or undefined.\n */\nfunction localeFromClassAndMessage(\n\tsourceFile: ts.SourceFile,\n\tclassNode: ts.Node | undefined,\n\tmessageNode: ts.Node | undefined,\n\tprefix: string | undefined,\n\tfailures: ILocaleFailure[]\n): string | undefined {\n\tif (!classNode || !messageNode) {\n\t\treturn undefined;\n\t}\n\n\tconst classNameParam = classNode.getText();\n\tconst classNameParamParts = classNameParam?.split(\".\");\n\n\tif (Is.array(classNameParamParts)) {\n\t\tconst messageKey = getExpandedText(messageNode);\n\n\t\tif (Is.stringValue(messageKey)) {\n\t\t\tif (messageKey.includes(\" \")) {\n\t\t\t\t// If the message contains spaces then it is not a key\n\t\t\t\t// but should be replaced by one\n\t\t\t\tconst position = getSourcePosition(sourceFile, messageNode);\n\t\t\t\tCLIDisplay.errorMessage(\n\t\t\t\t\tI18n.formatMessage(\"error.validateLocales.shouldBeKey\", {\n\t\t\t\t\t\tvalue: messageKey,\n\t\t\t\t\t\tsource: path.resolve(sourceFile.fileName),\n\t\t\t\t\t\tline: position.line,\n\t\t\t\t\t\tcolumn: position.column\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t\tfailures.push({\n\t\t\t\t\ttype: \"noKey\",\n\t\t\t\t\tkey: messageKey,\n\t\t\t\t\tsource: path.resolve(sourceFile.fileName),\n\t\t\t\t\t...position\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconst finalKeyParts = [];\n\n\t\t\t\tconst classNameExpanded = expandTemplatePart(classNameParam);\n\t\t\t\tconst messageKeyParts = messageKey.split(\".\");\n\n\t\t\t\tif (messageKeyParts.length === 2 && classNameExpanded === messageKeyParts[0]) {\n\t\t\t\t\t// But if it is fully qualified with exactly two segments and starts with the class name\n\t\t\t\t\t// then the class name is redundant and should be removed in the source\n\t\t\t\t\tconst position = getSourcePosition(sourceFile, messageNode);\n\t\t\t\t\tCLIDisplay.errorMessage(\n\t\t\t\t\t\tI18n.formatMessage(\"error.validateLocales.noNeedToQualify\", {\n\t\t\t\t\t\t\tkey: messageKey,\n\t\t\t\t\t\t\tproperty: classNameParam,\n\t\t\t\t\t\t\tsource: path.resolve(sourceFile.fileName),\n\t\t\t\t\t\t\tline: position.line,\n\t\t\t\t\t\t\tcolumn: position.column\n\t\t\t\t\t\t})\n\t\t\t\t\t);\n\t\t\t\t\tfailures.push({\n\t\t\t\t\t\ttype: \"qualify\",\n\t\t\t\t\t\tkey: messageKey,\n\t\t\t\t\t\tsource: path.resolve(sourceFile.fileName),\n\t\t\t\t\t\t...position\n\t\t\t\t\t});\n\t\t\t\t} else if (!messageKey.includes(\".\")) {\n\t\t\t\t\t// If the key is not fully qualified then add the class name\n\t\t\t\t\t// to the final key\n\t\t\t\t\tfinalKeyParts.push(classNameExpanded);\n\t\t\t\t}\n\n\t\t\t\tfinalKeyParts.push(messageKey);\n\n\t\t\t\tif (Is.stringValue(prefix)) {\n\t\t\t\t\tfinalKeyParts.unshift(prefix);\n\t\t\t\t}\n\n\t\t\t\treturn finalKeyParts.join(\".\");\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n"]}
@@ -0,0 +1,5 @@
1
+ // Copyright 2024 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ export * from "./cli.js";
4
+ export * from "./commands/validateLocales.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,UAAU,CAAC;AACzB,cAAc,+BAA+B,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./cli.js\";\nexport * from \"./commands/validateLocales.js\";\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ILocaleDictionaryEntry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ILocaleDictionaryEntry.js","sourceRoot":"","sources":["../../../src/models/ILocaleDictionaryEntry.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n/**\n * Model for a locale dictionary entry.\n */\nexport interface ILocaleDictionaryEntry {\n\t/**\n\t * The locale.\n\t */\n\tlocale: string;\n\n\t/**\n\t * The key.\n\t */\n\tkey: string;\n\n\t/**\n\t * The value.\n\t */\n\tvalue: string;\n\n\t/**\n\t * The property names.\n\t */\n\tpropertyNames: string[];\n\n\t/**\n\t * Whether the entry is referenced in the source code.\n\t */\n\treferenced: boolean;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ILocaleFailure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ILocaleFailure.js","sourceRoot":"","sources":["../../../src/models/ILocaleFailure.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n/**\n * Model for a locale failure reference.\n */\nexport interface ILocaleFailure {\n\t/**\n\t * The type of the failure reference.\n\t */\n\ttype: string;\n\n\t/**\n\t * The key of the failure reference.\n\t */\n\tkey: string;\n\n\t/**\n\t * The source file for the failure reference.\n\t */\n\tsource: string;\n\n\t/**\n\t * The line number for the failure reference.\n\t */\n\tline: number;\n\n\t/**\n\t * The column number for the failure reference.\n\t */\n\tcolumn: number;\n}\n"]}
@@ -1,2 +1,2 @@
1
- export * from "./cli";
2
- export * from "./commands/validateLocales";
1
+ export * from "./cli.js";
2
+ export * from "./commands/validateLocales.js";
package/docs/changelog.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.3-next.1](https://github.com/twinfoundation/framework/compare/validate-locales-v0.0.3-next.0...validate-locales-v0.0.3-next.1) (2025-11-10)
4
+
5
+
6
+ ### Features
7
+
8
+ * add context id features ([#206](https://github.com/twinfoundation/framework/issues/206)) ([ef0d4ee](https://github.com/twinfoundation/framework/commit/ef0d4ee11a4f5fc6cc6f52a4958ce905c04ee13b))
9
+ * detect unused prefixes and non i18 key messages ([a357810](https://github.com/twinfoundation/framework/commit/a357810754e25478496c2e6fd71ddc49dee9f747))
10
+ * locales validation ([#197](https://github.com/twinfoundation/framework/issues/197)) ([55fdadb](https://github.com/twinfoundation/framework/commit/55fdadb13595ce0047f787bd1d4135d429a99f12))
11
+
12
+
13
+ ### Dependencies
14
+
15
+ * The following workspace dependencies were updated
16
+ * dependencies
17
+ * @twin.org/cli-core bumped from 0.0.3-next.0 to 0.0.3-next.1
18
+ * @twin.org/core bumped from 0.0.3-next.0 to 0.0.3-next.1
19
+ * @twin.org/nameof bumped from 0.0.3-next.0 to 0.0.3-next.1
20
+ * @twin.org/nameof-transformer bumped from 0.0.3-next.0 to 0.0.3-next.1
21
+ * devDependencies
22
+ * @twin.org/merge-locales bumped from 0.0.3-next.0 to 0.0.3-next.1
23
+ * @twin.org/nameof-vitest-plugin bumped from 0.0.3-next.0 to 0.0.3-next.1
24
+
3
25
  ## [0.0.2-next.22](https://github.com/twinfoundation/framework/compare/validate-locales-v0.0.2-next.21...validate-locales-v0.0.2-next.22) (2025-10-10)
4
26
 
5
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/validate-locales",
3
- "version": "0.0.2-next.22",
3
+ "version": "0.0.3-next.1",
4
4
  "description": "Tool to validate source files against the locales",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,28 +14,26 @@
14
14
  "node": ">=20.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@twin.org/cli-core": "0.0.2-next.22",
18
- "@twin.org/core": "0.0.2-next.22",
19
- "@twin.org/nameof": "0.0.2-next.22",
20
- "@twin.org/nameof-transformer": "0.0.2-next.22",
21
- "commander": "14.0.1",
17
+ "@twin.org/cli-core": "0.0.3-next.1",
18
+ "@twin.org/core": "0.0.3-next.1",
19
+ "@twin.org/nameof": "0.0.3-next.1",
20
+ "@twin.org/nameof-transformer": "0.0.3-next.1",
21
+ "commander": "14.0.2",
22
22
  "glob": "11.0.3",
23
23
  "typescript": "5.9.3"
24
24
  },
25
- "main": "./dist/cjs/index.cjs",
26
- "module": "./dist/esm/index.mjs",
25
+ "main": "./dist/es/index.js",
27
26
  "types": "./dist/types/index.d.ts",
28
27
  "exports": {
29
28
  ".": {
30
29
  "types": "./dist/types/index.d.ts",
31
- "require": "./dist/cjs/index.cjs",
32
- "import": "./dist/esm/index.mjs"
30
+ "import": "./dist/es/index.js",
31
+ "default": "./dist/es/index.js"
33
32
  }
34
33
  },
35
34
  "files": [
36
35
  "bin",
37
- "dist/cjs",
38
- "dist/esm",
36
+ "dist/es",
39
37
  "dist/locales",
40
38
  "dist/types",
41
39
  "locales",
@@ -1,805 +0,0 @@
1
- 'use strict';
2
-
3
- var path = require('node:path');
4
- var node_url = require('node:url');
5
- var cliCore = require('@twin.org/cli-core');
6
- var promises = require('node:fs/promises');
7
- var core = require('@twin.org/core');
8
- var nameofTransformer = require('@twin.org/nameof-transformer');
9
- var glob = require('glob');
10
- var ts = require('typescript');
11
-
12
- var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
13
- function _interopNamespaceDefault(e) {
14
- var n = Object.create(null);
15
- if (e) {
16
- Object.keys(e).forEach(function (k) {
17
- if (k !== 'default') {
18
- var d = Object.getOwnPropertyDescriptor(e, k);
19
- Object.defineProperty(n, k, d.get ? d : {
20
- enumerable: true,
21
- get: function () { return e[k]; }
22
- });
23
- }
24
- });
25
- }
26
- n.default = e;
27
- return Object.freeze(n);
28
- }
29
-
30
- var glob__namespace = /*#__PURE__*/_interopNamespaceDefault(glob);
31
- var ts__namespace = /*#__PURE__*/_interopNamespaceDefault(ts);
32
-
33
- // Copyright 2024 IOTA Stiftung.
34
- // SPDX-License-Identifier: Apache-2.0.
35
- const ERROR_TYPES = [
36
- { name: "GeneralError", dynamicPropertyIndex: 2 },
37
- { name: "GuardError", dynamicPropertyIndex: -1 },
38
- { name: "ValidationError", dynamicPropertyIndex: -1 },
39
- { name: "FetchError", dynamicPropertyIndex: 3, inbuiltProperties: ["httpStatus"] },
40
- { name: "NotFoundError", dynamicPropertyIndex: 3, inbuiltProperties: ["notFoundId"] },
41
- { name: "AlreadyExistsError", dynamicPropertyIndex: 3, inbuiltProperties: ["existingId"] },
42
- { name: "UnauthorizedError", dynamicPropertyIndex: 2 },
43
- { name: "NotSupportedError", dynamicPropertyIndex: 3, inbuiltProperties: ["methodName"] },
44
- { name: "UnprocessableError", dynamicPropertyIndex: 2 },
45
- { name: "ConflictError", dynamicPropertyIndex: 4, inbuiltProperties: ["conflictId", "conflicts"] }
46
- ];
47
- const SKIP_FILES = ["**/models/**/*.ts"];
48
- const SKIP_LITERALS = [
49
- /^\d+\.\d+\.\d+(-\w+(\.\w+)*)?(-\d)?$/, // Version string
50
- /^[^@]+@[^@]+\.[^@]+$/, // Email string
51
- /\.json$/i, // ending in .json
52
- /\.js$/i, // ending in .js
53
- /\.ts$/i, // ending in .ts
54
- /\.env$/i, // ending in .env
55
- /\.png$/i, // ending in .png
56
- /\.lock$/i, // ending in .lock
57
- /\.toml$/i, // ending in .toml
58
- /\.{3}/i, // ...
59
- /@twin\.org/ // starting with @twin.org
60
- ];
61
- const SKIP_METHODS = [/^generateRest/, /^generateSocket/];
62
- const CAPTURE_VARIABLES = [/ROUTES_SOURCE/];
63
- /**
64
- * Build the root command to be consumed by the CLI.
65
- * @param program The command to build on.
66
- */
67
- function buildCommandValidateLocales(program) {
68
- program
69
- .option(core.I18n.formatMessage("commands.validate-locales.options.source.param"), core.I18n.formatMessage("commands.validate-locales.options.source.description"), "src/**/*.ts")
70
- .option(core.I18n.formatMessage("commands.validate-locales.options.locales.param"), core.I18n.formatMessage("commands.validate-locales.options.locales.description"), "locales/**/*.json")
71
- .option(core.I18n.formatMessage("commands.validate-locales.options.ignoreFile.param"), core.I18n.formatMessage("commands.validate-locales.options.ignoreFile.description"), "locales/.validate-ignore")
72
- .action(async (opts) => {
73
- await actionCommandValidateLocales(opts);
74
- });
75
- }
76
- /**
77
- * Action the root command.
78
- * @param opts The options for the command.
79
- * @param opts.source The source glob.
80
- * @param opts.locales The locales glob.
81
- * @param opts.ignoreFile The ignore file path.
82
- */
83
- async function actionCommandValidateLocales(opts) {
84
- opts.source = path.resolve(opts.source);
85
- opts.locales = path.resolve(opts.locales);
86
- opts.ignoreFile = path.resolve(opts.ignoreFile);
87
- cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.validate-locales.labels.source"), opts.source);
88
- cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.validate-locales.labels.locales"), opts.locales);
89
- let ignore = [];
90
- if (await cliCore.CLIUtils.fileExists(opts.ignoreFile)) {
91
- cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.validate-locales.labels.ignoreFile"), opts.ignoreFile);
92
- ignore = (await cliCore.CLIUtils.readLinesFile(opts.ignoreFile)) ?? [];
93
- }
94
- cliCore.CLIDisplay.break();
95
- const sources = glob__namespace.sync(opts.source.replace(/\\/g, "/"), { ignore: SKIP_FILES });
96
- const locales = glob__namespace.sync(opts.locales.replace(/\\/g, "/"));
97
- if (sources.length === 0) {
98
- cliCore.CLIDisplay.warning(core.I18n.formatMessage("commands.validate-locales.warnings.noSourceFiles"));
99
- }
100
- else if (locales.length === 0) {
101
- cliCore.CLIDisplay.warning(core.I18n.formatMessage("commands.validate-locales.warnings.noLocaleFiles"));
102
- }
103
- else {
104
- await validateLocales(sources, locales, ignore.filter(i => core.Is.stringValue(i) && !i.startsWith("#")).map(i => new RegExp(i)));
105
- }
106
- cliCore.CLIDisplay.break();
107
- cliCore.CLIDisplay.done();
108
- }
109
- /**
110
- * Validate the locales.
111
- * @param sourceFiles The source files.
112
- * @param localeFiles The locale files.
113
- * @param ignore The ignore list.
114
- */
115
- async function validateLocales(sourceFiles, localeFiles, ignore) {
116
- const localeEntries = [];
117
- let hasQuoteError = false;
118
- let hasUnused = false;
119
- let hasFailures = false;
120
- for (const localeFile of localeFiles) {
121
- const dictionary = await cliCore.CLIUtils.readJsonFile(localeFile);
122
- const locale = path.basename(localeFile, path.extname(localeFile));
123
- cliCore.CLIDisplay.task(core.I18n.formatMessage("commands.validate-locales.progress.validatingLocale", { localeFile }));
124
- cliCore.CLIDisplay.break();
125
- if (core.Is.object(dictionary)) {
126
- const mergedKeys = {};
127
- core.I18n.flattenTranslationKeys(dictionary, "", mergedKeys);
128
- for (const flatKey of Object.keys(mergedKeys)) {
129
- if (/'{.*?}'/.test(mergedKeys[flatKey])) {
130
- cliCore.CLIDisplay.errorMessage(core.I18n.formatMessage("error.validateLocales.usesSingleQuotes", {
131
- key: flatKey
132
- }));
133
- hasQuoteError = true;
134
- }
135
- localeEntries.push({
136
- locale,
137
- key: flatKey,
138
- value: mergedKeys[flatKey],
139
- propertyNames: core.I18n.getPropertyNames(mergedKeys[flatKey]),
140
- referenced: false
141
- });
142
- }
143
- }
144
- let failures = [];
145
- const captureVariables = {};
146
- for (const sourceFile of sourceFiles) {
147
- const source = await promises.readFile(sourceFile, "utf8");
148
- const sourceTs = ts__namespace.createSourceFile(sourceFile, source, ts__namespace.ScriptTarget.ESNext, true, ts__namespace.ScriptKind.TS);
149
- visit(sourceTs, sourceTs, localeEntries, failures, captureVariables);
150
- }
151
- failures = failures.filter(mr => !ignore.some(pattern => pattern.test(mr.key)));
152
- if (failures.length === 0) {
153
- cliCore.CLIDisplay.write(core.I18n.formatMessage("commands.validate-locales.labels.noMissingLocaleEntries"));
154
- cliCore.CLIDisplay.break();
155
- }
156
- else {
157
- hasFailures = true;
158
- for (const failureRef of failures) {
159
- if (failureRef.type === "key") {
160
- cliCore.CLIDisplay.errorMessage(core.I18n.formatMessage("error.validateLocales.missingLocaleEntry", {
161
- key: failureRef.key,
162
- source: failureRef.source,
163
- line: failureRef.line,
164
- column: failureRef.column
165
- }));
166
- }
167
- }
168
- }
169
- cliCore.CLIDisplay.break();
170
- for (const localeEntry of localeEntries) {
171
- if (ignore.some(pattern => pattern.test(localeEntry.key))) {
172
- localeEntry.referenced = true;
173
- }
174
- }
175
- if (localeEntries.filter(le => !le.referenced).length === 0) {
176
- cliCore.CLIDisplay.write(core.I18n.formatMessage("commands.validate-locales.labels.noUnusedLocaleEntries"));
177
- cliCore.CLIDisplay.break();
178
- }
179
- else {
180
- hasUnused = true;
181
- for (const localeEntry of localeEntries) {
182
- if (!localeEntry.referenced) {
183
- cliCore.CLIDisplay.errorMessage(core.I18n.formatMessage("error.validateLocales.unusedLocaleEntry", {
184
- key: localeEntry.key
185
- }));
186
- }
187
- }
188
- }
189
- }
190
- if (hasFailures || hasUnused || hasQuoteError) {
191
- throw new core.GeneralError("validateLocales", "validationFailed");
192
- }
193
- }
194
- /**
195
- * Visit the node.
196
- * @param sourceFile The TypeScript source file for position calculations.
197
- * @param node The node to visit.
198
- * @param localeEntries The locale entries.
199
- * @param failures The failure entries.
200
- * @param captureVariables The capture variables.
201
- */
202
- function visit(sourceFile, node, localeEntries, failures, captureVariables) {
203
- let handled = false;
204
- if (ts__namespace.isNewExpression(node) &&
205
- ts__namespace.isIdentifier(node.expression) &&
206
- ERROR_TYPES.some(errorType => errorType.name === node.expression.getText())) {
207
- processErrorType(sourceFile, node, node.expression.text, localeEntries, failures);
208
- handled = true;
209
- }
210
- else if (ts__namespace.isStringLiteral(node)) {
211
- processStringLiteral(sourceFile, node, localeEntries, failures);
212
- handled = true;
213
- }
214
- else if (ts__namespace.isTemplateExpression(node)) {
215
- processTemplateExpression(sourceFile, node, localeEntries, failures);
216
- handled = true;
217
- }
218
- else if (ts__namespace.isCallExpression(node)) {
219
- handled = processCallExpression(sourceFile, node, localeEntries, failures, captureVariables);
220
- }
221
- else if (ts__namespace.isFunctionDeclaration(node)) {
222
- handled = processFunctionDeclaration(sourceFile, node);
223
- }
224
- else if (ts__namespace.isVariableDeclaration(node)) {
225
- handled = processVariableDeclaration(sourceFile, node, localeEntries, failures, captureVariables);
226
- }
227
- else if (ts__namespace.isImportDeclaration(node) || ts__namespace.isExportDeclaration(node)) {
228
- // Don't care about string in imports/exports
229
- handled = true;
230
- }
231
- else if (ts__namespace.isPropertyAssignment(node)) {
232
- handled = processPropertyAssignment(sourceFile, node, localeEntries, failures);
233
- }
234
- if (!handled) {
235
- ts__namespace.forEachChild(node, child => visit(sourceFile, child, localeEntries, failures, captureVariables));
236
- }
237
- }
238
- /**
239
- * Process an error type node.
240
- * @param sourceFile The TypeScript source file for position calculations.
241
- * @param node The node to process.
242
- * @param errorType The error type.
243
- * @param localeEntries The locale entries.
244
- * @param failures The failure entries.
245
- */
246
- function processErrorType(sourceFile, node, errorType, localeEntries, failures) {
247
- const errType = ERROR_TYPES.find(e => e.name === errorType);
248
- if (core.Is.object(errType)) {
249
- const localeKey = localeFromClassAndMessage(sourceFile, node.arguments?.[0], node.arguments?.[1], "error", failures);
250
- if (core.Is.stringValue(localeKey)) {
251
- const localeEntry = findAndReferenceLocale(localeEntries, localeKey);
252
- if (core.Is.object(localeEntry)) {
253
- if (errType.dynamicPropertyIndex !== -1 || core.Is.arrayValue(errType.inbuiltProperties)) {
254
- const usedProperties = errType.inbuiltProperties?.slice() ?? [];
255
- if (errType.dynamicPropertyIndex !== -1) {
256
- usedProperties.push(...getPropertiesFromNode(node.arguments?.[errType.dynamicPropertyIndex]));
257
- }
258
- checkPropertyUsage(sourceFile, node, localeEntry, localeKey, usedProperties, failures);
259
- }
260
- }
261
- else {
262
- failures.push({
263
- type: "key",
264
- key: localeKey,
265
- source: path.resolve(sourceFile.fileName),
266
- ...getSourcePosition(sourceFile, node)
267
- });
268
- }
269
- }
270
- return;
271
- }
272
- cliCore.CLIDisplay.errorMessage(core.I18n.formatMessage("error.validateLocales.unableToProcessContent", { content: node.getText() }));
273
- }
274
- /**
275
- * Find the locale entry if it exists.
276
- * @param localeEntries The locale entries.
277
- * @param entryToMatch The full key to check.
278
- * @returns The item if found, undefined otherwise.
279
- */
280
- function findAndReferenceLocale(localeEntries, entryToMatch) {
281
- if (core.Is.stringValue(entryToMatch)) {
282
- const found = localeEntries.find(le => le.key === entryToMatch);
283
- if (found) {
284
- found.referenced = true;
285
- return found;
286
- }
287
- }
288
- }
289
- /**
290
- * Process a string literal node.
291
- * @param sourceFile The TypeScript source file for position calculations.
292
- * @param node The node to process.
293
- * @param localeEntries The locale entries.
294
- * @param failures The failure entries.
295
- */
296
- function processStringLiteral(sourceFile, node, localeEntries, failures) {
297
- if (node.text.length > 3 &&
298
- node.text.includes(".") &&
299
- !/[ ()/]/.test(node.text) &&
300
- !/^\.|\.$/.test(node.text) &&
301
- !isSkipLiteral(node.text)) {
302
- const parts = node.text.split(".");
303
- if (parts.length > 1) {
304
- // First try and match the string as-is
305
- let localeEntry = findAndReferenceLocale(localeEntries, node.text);
306
- if (localeEntry) {
307
- localeEntry.referenced = true;
308
- const usedProperties = getPropertiesFromNode(node);
309
- checkPropertyUsage(sourceFile, node, localeEntry, node.text, usedProperties, failures);
310
- }
311
- if (!localeEntry && ["validation.", "common."].some(t => node.text.startsWith(t))) {
312
- localeEntry = findAndReferenceLocale(localeEntries, `error.${node.text}`);
313
- if (localeEntry) {
314
- localeEntry.referenced = true;
315
- const usedProperties = getPropertiesFromNode(node);
316
- checkPropertyUsage(sourceFile, node, localeEntry, `error.${node.text}`, usedProperties, failures);
317
- }
318
- }
319
- if (!localeEntry) {
320
- failures.push({
321
- type: "key",
322
- key: node.text,
323
- source: path.resolve(sourceFile.fileName),
324
- ...getSourcePosition(sourceFile, node)
325
- });
326
- }
327
- }
328
- }
329
- }
330
- /**
331
- * Process a template expression node.
332
- * @param sourceFile The TypeScript source file for position calculations.
333
- * @param node The node to process.
334
- * @param localeEntries The locale entries.
335
- * @param failures The failure entries.
336
- */
337
- function processTemplateExpression(sourceFile, node, localeEntries, failures) {
338
- // This case handles templates like `error.${nameof(Class)}.message`
339
- const templateParts = extractTemplatePartsWithExpressions(node);
340
- // Join all literal text parts to form a potential locale key
341
- if (hasValidTemplateContent(templateParts)) {
342
- const key = expandTemplateParts(templateParts);
343
- let localeEntry = findAndReferenceLocale(localeEntries, key);
344
- if (localeEntry) {
345
- const usedProperties = getPropertiesFromNode(node);
346
- checkPropertyUsage(sourceFile, node, localeEntry, localeEntry.key, usedProperties, failures);
347
- }
348
- else if (["validation.", "common."].some(t => key.startsWith(t))) {
349
- localeEntry = findAndReferenceLocale(localeEntries, `error.${key}`);
350
- if (localeEntry) {
351
- localeEntry.referenced = true;
352
- const usedProperties = getPropertiesFromNode(node.parent.parent);
353
- checkPropertyUsage(sourceFile, node, localeEntry, `error.${key}`, usedProperties, failures);
354
- }
355
- }
356
- }
357
- }
358
- /**
359
- * Process a call expression node.
360
- * @param sourceFile The TypeScript source file for position calculations.
361
- * @param node The node to process.
362
- * @param localeEntries The locale entries.
363
- * @param failures The failure entries.
364
- * @param captureVariables The capture variables.
365
- * @returns True if processed, false otherwise.
366
- */
367
- function processCallExpression(sourceFile, node, localeEntries, failures, captureVariables) {
368
- if (ts__namespace.isPropertyAccessExpression(node.expression)) {
369
- const functionName = node.expression.name.getText();
370
- if (functionName === "log" &&
371
- node.arguments.length === 1 &&
372
- ts__namespace.isObjectLiteralExpression(node.arguments[0])) {
373
- let level;
374
- let source;
375
- let message;
376
- let dataNames;
377
- for (const prop of node.arguments[0].properties) {
378
- if (ts__namespace.isPropertyAssignment(prop) && ts__namespace.isIdentifier(prop.name)) {
379
- if (prop.name.text === "source") {
380
- if (ts__namespace.isIdentifier(prop.initializer) && captureVariables[prop.initializer.text]) {
381
- source = captureVariables[prop.initializer.text];
382
- }
383
- else if (ts__namespace.isStringLiteral(prop.initializer)) {
384
- source = prop.initializer;
385
- }
386
- else if (ts__namespace.isPropertyAccessExpression(prop.initializer)) {
387
- source = prop.initializer;
388
- }
389
- }
390
- else if (prop.name.text === "message") {
391
- message = prop.initializer;
392
- }
393
- else if (prop.name.text === "data") {
394
- dataNames = getPropertiesFromNode(prop.initializer);
395
- }
396
- else if (prop.name.text === "level") {
397
- level = getExpandedText(prop.initializer);
398
- }
399
- }
400
- }
401
- const localeKey = localeFromClassAndMessage(sourceFile, source, message, level, failures);
402
- if (core.Is.stringValue(localeKey)) {
403
- const localeEntry = findAndReferenceLocale(localeEntries, localeKey);
404
- if (core.Is.object(localeEntry)) {
405
- checkPropertyUsage(sourceFile, node, localeEntry, localeKey, dataNames ?? [], failures);
406
- }
407
- else {
408
- failures.push({
409
- type: "key",
410
- key: localeKey,
411
- source: path.resolve(sourceFile.fileName),
412
- ...getSourcePosition(sourceFile, node)
413
- });
414
- }
415
- }
416
- return true;
417
- }
418
- else if (functionName === "formatMessage" &&
419
- node.arguments.length === 2 &&
420
- ts__namespace.isTemplateExpression(node.arguments[0]) &&
421
- ts__namespace.isObjectLiteralExpression(node.arguments[1])) {
422
- const localeKey = getExpandedText(node.arguments[0]);
423
- if (core.Is.stringValue(localeKey)) {
424
- const dataNames = getPropertiesFromNode(node.arguments[1]);
425
- const localeEntry = findAndReferenceLocale(localeEntries, localeKey);
426
- if (core.Is.object(localeEntry)) {
427
- checkPropertyUsage(sourceFile, node, localeEntry, localeKey, dataNames ?? [], failures);
428
- }
429
- else {
430
- failures.push({
431
- type: "key",
432
- key: localeKey,
433
- source: path.resolve(sourceFile.fileName),
434
- ...getSourcePosition(sourceFile, node)
435
- });
436
- }
437
- }
438
- return true;
439
- }
440
- }
441
- return false;
442
- }
443
- /**
444
- * Process a function declaration node.
445
- * @param sourceFile The TypeScript source file for position calculations.
446
- * @param node The node to process.
447
- * @param localeEntries The locale entries.
448
- * @param failures The failure entries.
449
- * @returns True if processed, false otherwise.
450
- */
451
- function processFunctionDeclaration(sourceFile, node, localeEntries, failures) {
452
- if (core.Is.object(node.name) &&
453
- ts__namespace.isIdentifier(node.name) &&
454
- SKIP_METHODS.some(re => re.test(node.name?.text ?? ""))) {
455
- return true;
456
- }
457
- return false;
458
- }
459
- /**
460
- * Process a variable statement declaration node.
461
- * @param sourceFile The TypeScript source file for position calculations.
462
- * @param node The node to process.
463
- * @param localeEntries The locale entries.
464
- * @param failures The failure entries.
465
- * @param captureVariables The capture variables.
466
- * @returns True if processed, false otherwise.
467
- */
468
- function processVariableDeclaration(sourceFile, node, localeEntries, failures, captureVariables) {
469
- if (core.Is.object(node.name) &&
470
- core.Is.object(node.initializer) &&
471
- ts__namespace.isIdentifier(node.name) &&
472
- ts__namespace.isStringLiteral(node.initializer) &&
473
- CAPTURE_VARIABLES.some(re => re.test(node.name.getText()))) {
474
- captureVariables[node.name.getText()] = node.initializer;
475
- return true;
476
- }
477
- return false;
478
- }
479
- /**
480
- * Process a property assignment node.
481
- * @param sourceFile The TypeScript source file for position calculations.
482
- * @param node The node to process.
483
- * @param localeEntries The locale entries.
484
- * @param failures The failure entries.
485
- * @returns True if processed, false otherwise.
486
- */
487
- function processPropertyAssignment(sourceFile, node, localeEntries, failures) {
488
- if (core.Is.object(node.name) && ts__namespace.isIdentifier(node.name) && node.name.getText() === "message") {
489
- const localeKey = getExpandedText(node.initializer);
490
- if (core.Is.stringValue(localeKey)) {
491
- let localeEntry = findAndReferenceLocale(localeEntries, localeKey);
492
- if (!core.Is.object(localeEntry)) {
493
- localeEntry = findAndReferenceLocale(localeEntries, `error.${localeKey}`);
494
- }
495
- if (!core.Is.object(localeEntry)) {
496
- failures.push({
497
- type: "key",
498
- key: localeKey,
499
- source: path.resolve(sourceFile.fileName),
500
- ...getSourcePosition(sourceFile, node)
501
- });
502
- }
503
- return true;
504
- }
505
- }
506
- return false;
507
- }
508
- /**
509
- * Get the expanded text from a node.
510
- * @param node The node to get the text from.
511
- * @returns The expanded text.
512
- */
513
- function getExpandedText(node) {
514
- if (ts__namespace.isTemplateExpression(node)) {
515
- const templateParts = extractTemplatePartsWithExpressions(node);
516
- if (hasValidTemplateContent(templateParts)) {
517
- return expandTemplateParts(templateParts);
518
- }
519
- }
520
- else if (ts__namespace.isStringLiteral(node)) {
521
- if (hasValidTemplateContent([node.text])) {
522
- return node.text;
523
- }
524
- }
525
- return "";
526
- }
527
- /**
528
- * Extract parts from a template literal, splitting by ${} expressions.
529
- * @param node The template expression node.
530
- * @returns Array of parts including expressions and literal text.
531
- */
532
- function extractTemplatePartsWithExpressions(node) {
533
- const parts = [];
534
- // Start with the head (text before first ${})
535
- if (node.head.text) {
536
- parts.push(node.head.text);
537
- }
538
- // Process each template span (${expression}text)
539
- for (const span of node.templateSpans) {
540
- // Add the expression part as text
541
- parts.push(span.expression.getText());
542
- // Add the literal text after the expression
543
- if (span.literal.text) {
544
- parts.push(span.literal.text);
545
- }
546
- }
547
- return parts.filter(part => part.length > 0);
548
- }
549
- /**
550
- * Check if the content has any parts which contain elements which determine is is not an locale key.
551
- * @param templateParts The template parts to check.
552
- * @returns True if the template parts are valid, false otherwise.
553
- */
554
- function hasValidTemplateContent(templateParts) {
555
- return !templateParts.some(part => /[#,/:=?|]/.test(part));
556
- }
557
- /**
558
- * Expand template parts into a single string, processing nameof expressions.
559
- * @param templateParts The template parts to expand.
560
- * @returns The expanded template string.
561
- */
562
- function expandTemplateParts(templateParts) {
563
- for (let i = 0; i < templateParts.length; i++) {
564
- templateParts[i] = expandTemplatePart(templateParts[i]);
565
- }
566
- return templateParts.join("");
567
- }
568
- /**
569
- * Expand template part into a single string, processing nameof expressions.
570
- * @param templatePart The template part to expand.
571
- * @returns The expanded template string.
572
- */
573
- function expandTemplatePart(templatePart) {
574
- if (templatePart.startsWith("nameof")) {
575
- const stripped = nameofTransformer.manual(templatePart).replace(/["']/g, "");
576
- templatePart = core.StringHelper.camelCase(stripped, true);
577
- }
578
- else if (templatePart.startsWith("StringHelper.")) {
579
- templatePart = templatePart.replace(/\.CLASS_NAME/g, "");
580
- templatePart = templatePart.replace(/StringHelper\.camelCase\((.*?)\)/, (_, i) => core.StringHelper.camelCase(i));
581
- templatePart = templatePart.replace(/StringHelper\.titleCase\((.*?)\)/, (_, i) => core.StringHelper.titleCase(i));
582
- templatePart = templatePart.replace(/StringHelper\.pascalCase\((.*?)\)/, (_, i) => core.StringHelper.pascalCase(i));
583
- templatePart = templatePart.replace(/StringHelper\.kebabCase\((.*?)\)/, (_, i) => core.StringHelper.kebabCase(i));
584
- templatePart = templatePart.replace(/StringHelper\.snakeCase\((.*?)\)/, (_, i) => core.StringHelper.snakeCase(i));
585
- }
586
- else if (templatePart.includes(".CLASS_NAME")) {
587
- templatePart = core.StringHelper.camelCase(templatePart.replace(/\.CLASS_NAME/g, ""));
588
- }
589
- templatePart = templatePart.replace(/["'`]/g, ""); // Remove quotes
590
- return templatePart;
591
- }
592
- /**
593
- * Check the property usage in a locale entry against the used properties.
594
- * @param sourceFile The TypeScript source file for position calculations.
595
- * @param node The node to check.
596
- * @param localeEntry The locale entry to check against.
597
- * @param key The key in the locale entry.
598
- * @param usedProperties The properties used in the code.
599
- * @param failures The failure entries.
600
- */
601
- function checkPropertyUsage(sourceFile, node, localeEntry, key, usedProperties, failures) {
602
- for (const propName of localeEntry.propertyNames) {
603
- const propIndex = usedProperties.indexOf(propName);
604
- if (propIndex === -1) {
605
- const position = getSourcePosition(sourceFile, node);
606
- cliCore.CLIDisplay.errorMessage(core.I18n.formatMessage("error.validateLocales.missingPropertyInLocale", {
607
- key,
608
- property: propName,
609
- source: path.resolve(sourceFile.fileName),
610
- line: position.line,
611
- column: position.column
612
- }));
613
- failures.push({
614
- type: "property",
615
- key: localeEntry.key,
616
- source: path.resolve(sourceFile.fileName),
617
- ...position
618
- });
619
- }
620
- }
621
- // We often pass additional properties in the error details to better inform the logging
622
- // so we don't want to perform the opposite check for parameters in the call but not in the locale entry
623
- }
624
- /**
625
- * Helper to get line and column position from a node.
626
- * @param sourceFile The TypeScript source file for position calculations.
627
- * @param n The node to get position for.
628
- * @returns Line and column (1-based).
629
- */
630
- function getSourcePosition(sourceFile, n) {
631
- const { line, character } = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile));
632
- return { line: line + 1, column: character + 1 };
633
- }
634
- /**
635
- * Get property names from a node.
636
- * @param node The node to get property names from.
637
- * @returns The property names.
638
- */
639
- function getPropertiesFromNode(node) {
640
- if (!node) {
641
- return [];
642
- }
643
- const props = [];
644
- // If this is an object literal then we can get the property names
645
- if (ts__namespace.isObjectLiteralExpression(node)) {
646
- // Get the properties of the object literal
647
- const properties = node.properties;
648
- for (const prop of properties) {
649
- if (ts__namespace.isPropertyAssignment(prop) && ts__namespace.isIdentifier(prop.name)) {
650
- // { property: value }
651
- props.push(prop.name.text);
652
- // { property: { nestedProperty: value } }
653
- if (ts__namespace.isObjectLiteralExpression(prop.initializer)) {
654
- // Recursively get properties from nested object literals
655
- const nestedProps = getPropertiesFromNode(prop.initializer);
656
- props.push(...nestedProps);
657
- }
658
- }
659
- else if (ts__namespace.isShorthandPropertyAssignment(prop)) {
660
- // { property }
661
- props.push(prop.getText());
662
- }
663
- }
664
- }
665
- else if (ts__namespace.isStringLiteral(node)) {
666
- // If this is a string literal then there are no properties
667
- // so we need to look in the surrounding context
668
- const parent = node.parent;
669
- if (ts__namespace.isCallExpression(parent) || ts__namespace.isNewExpression(parent)) {
670
- // Let's see if they are the next argument in the call
671
- const args = parent.arguments;
672
- if (args && args.length > 0) {
673
- const index = args.findIndex(a => a === node);
674
- if (index !== -1 && index + 1 < args.length) {
675
- return getPropertiesFromNode(args[index + 1]);
676
- }
677
- }
678
- }
679
- else if (ts__namespace.isPropertyAssignment(parent)) {
680
- // This is part of a property assignment in an object
681
- // so we can check the parent object for other properties
682
- if (ts__namespace.isObjectLiteralExpression(parent.parent)) {
683
- return getPropertiesFromNode(parent.parent);
684
- }
685
- }
686
- }
687
- return props;
688
- }
689
- /**
690
- * Check if a value is a file operation.
691
- * @param value The value to check.
692
- * @returns True if the value is a file operation.
693
- */
694
- function isSkipLiteral(value) {
695
- return SKIP_LITERALS.some(regex => regex.test(value));
696
- }
697
- /**
698
- * Get the locale from the class and message nodes.
699
- * @param sourceFile The TypeScript source file for position calculations.
700
- * @param classNode The class node.
701
- * @param messageNode The message node.
702
- * @param prefix The prefix for the locale key.
703
- * @param failures The failure entries.
704
- * @returns The locale entry or undefined.
705
- */
706
- function localeFromClassAndMessage(sourceFile, classNode, messageNode, prefix, failures) {
707
- if (!classNode || !messageNode) {
708
- return undefined;
709
- }
710
- const classNameParam = classNode.getText();
711
- const classNameParamParts = classNameParam?.split(".");
712
- if (core.Is.array(classNameParamParts)) {
713
- const messageKey = getExpandedText(messageNode);
714
- if (core.Is.stringValue(messageKey)) {
715
- if (messageKey.includes(" ")) {
716
- // If the message contains spaces then it is not a key
717
- // but should be replaced by one
718
- const position = getSourcePosition(sourceFile, messageNode);
719
- cliCore.CLIDisplay.errorMessage(core.I18n.formatMessage("error.validateLocales.shouldBeKey", {
720
- value: messageKey,
721
- source: path.resolve(sourceFile.fileName),
722
- line: position.line,
723
- column: position.column
724
- }));
725
- failures.push({
726
- type: "noKey",
727
- key: messageKey,
728
- source: path.resolve(sourceFile.fileName),
729
- ...position
730
- });
731
- }
732
- else {
733
- const finalKeyParts = [];
734
- const classNameExpanded = expandTemplatePart(classNameParam);
735
- const messageKeyParts = messageKey.split(".");
736
- if (messageKeyParts.length === 2 && classNameExpanded === messageKeyParts[0]) {
737
- // But if it is fully qualified with exactly two segments and starts with the class name
738
- // then the class name is redundant and should be removed in the source
739
- const position = getSourcePosition(sourceFile, messageNode);
740
- cliCore.CLIDisplay.errorMessage(core.I18n.formatMessage("error.validateLocales.noNeedToQualify", {
741
- key: messageKey,
742
- property: classNameParam,
743
- source: path.resolve(sourceFile.fileName),
744
- line: position.line,
745
- column: position.column
746
- }));
747
- failures.push({
748
- type: "qualify",
749
- key: messageKey,
750
- source: path.resolve(sourceFile.fileName),
751
- ...position
752
- });
753
- }
754
- else if (!messageKey.includes(".")) {
755
- // If the key is not fully qualified then add the class name
756
- // to the final key
757
- finalKeyParts.push(classNameExpanded);
758
- }
759
- finalKeyParts.push(messageKey);
760
- if (core.Is.stringValue(prefix)) {
761
- finalKeyParts.unshift(prefix);
762
- }
763
- return finalKeyParts.join(".");
764
- }
765
- }
766
- }
767
- return undefined;
768
- }
769
-
770
- // Copyright 2024 IOTA Stiftung.
771
- // SPDX-License-Identifier: Apache-2.0.
772
- /**
773
- * The main entry point for the CLI.
774
- */
775
- class CLI extends cliCore.CLIBase {
776
- /**
777
- * Run the app.
778
- * @param argv The process arguments.
779
- * @param localesDirectory The directory for the locales, default to relative to the script.
780
- * @param options Additional options.
781
- * @param options.overrideOutputWidth Override the output width.
782
- * @returns The exit code.
783
- */
784
- async run(argv, localesDirectory, options) {
785
- return this.execute({
786
- title: "TWIN Validate Locales",
787
- appName: "validate-locales",
788
- version: "0.0.2-next.22", // x-release-please-version
789
- icon: "⚙️ ",
790
- supportsEnvFiles: false,
791
- overrideOutputWidth: options?.overrideOutputWidth
792
- }, localesDirectory ?? path.join(path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))), "../locales"), argv);
793
- }
794
- /**
795
- * Configure any options or actions at the root program level.
796
- * @param program The root program command.
797
- */
798
- configureRoot(program) {
799
- buildCommandValidateLocales(program);
800
- }
801
- }
802
-
803
- exports.CLI = CLI;
804
- exports.actionCommandValidateLocales = actionCommandValidateLocales;
805
- exports.buildCommandValidateLocales = buildCommandValidateLocales;