@trackunit/eslint-plugin-trackunit 0.6.98-alpha-7bd3cf90b54.0 → 0.6.98

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.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * ESLint rule that discourages building i18next translation keys dynamically
3
+ * with template strings (e.g. `t(`pages.categories.${category}`)`).
4
+ *
5
+ * Dynamic keys are impossible to analyse statically: they defeat the typed
6
+ * translation-key safety and make it impossible to tell which keys in a
7
+ * `translation.json` are actually used. Mapping the value to an explicit key
8
+ * with a `switch` statement or a `Record` keeps keys statically analysable and
9
+ * lets `no-unused-translation-key` detect orphaned translations.
10
+ *
11
+ * @example
12
+ * // Bad
13
+ * t(`pages.categories.${category}`);
14
+ * <Trans i18nKey={`pages.categories.${category}`} />;
15
+ *
16
+ * // Good
17
+ * const categoryKey: Record<Category, TranslationKeys> = { ... };
18
+ * t(categoryKey[category]);
19
+ */
20
+ import { ESLintUtils } from "@typescript-eslint/utils";
21
+ export declare const noDynamicTranslationKey: ESLintUtils.RuleModule<"dynamicKey", [], unknown, ESLintUtils.RuleListener> & {
22
+ name: string;
23
+ };
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noDynamicTranslationKey = void 0;
4
+ /**
5
+ * ESLint rule that discourages building i18next translation keys dynamically
6
+ * with template strings (e.g. `t(`pages.categories.${category}`)`).
7
+ *
8
+ * Dynamic keys are impossible to analyse statically: they defeat the typed
9
+ * translation-key safety and make it impossible to tell which keys in a
10
+ * `translation.json` are actually used. Mapping the value to an explicit key
11
+ * with a `switch` statement or a `Record` keeps keys statically analysable and
12
+ * lets `no-unused-translation-key` detect orphaned translations.
13
+ *
14
+ * @example
15
+ * // Bad
16
+ * t(`pages.categories.${category}`);
17
+ * <Trans i18nKey={`pages.categories.${category}`} />;
18
+ *
19
+ * // Good
20
+ * const categoryKey: Record<Category, TranslationKeys> = { ... };
21
+ * t(categoryKey[category]);
22
+ */
23
+ const utils_1 = require("@typescript-eslint/utils");
24
+ const createRule = utils_1.ESLintUtils.RuleCreator(name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}/${name}.ts`);
25
+ /** A template literal that actually interpolates a value, e.g. `foo.${bar}` (not a plain `foo`). */
26
+ const isInterpolatedTemplate = (node) => node.type === utils_1.AST_NODE_TYPES.TemplateLiteral && node.expressions.length > 0;
27
+ /** A call to the translation function: either a bare `t(...)` or a member call like `i18n.t(...)`. */
28
+ const isTranslationCallee = (callee) => {
29
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
30
+ return callee.name === "t";
31
+ }
32
+ return (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
33
+ !callee.computed &&
34
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
35
+ callee.property.name === "t");
36
+ };
37
+ exports.noDynamicTranslationKey = createRule({
38
+ name: "no-dynamic-translation-key",
39
+ meta: {
40
+ type: "suggestion",
41
+ docs: {
42
+ description: "Disallow building i18next translation keys dynamically with template strings; map values to explicit keys with a switch or Record instead.",
43
+ },
44
+ messages: {
45
+ dynamicKey: "Avoid building translation keys dynamically with template strings. Map the value to an explicit key using a switch statement or a Record so keys stay statically analysable and unused keys can be detected.",
46
+ },
47
+ schema: [],
48
+ },
49
+ defaultOptions: [],
50
+ create(context) {
51
+ return {
52
+ CallExpression(node) {
53
+ if (!isTranslationCallee(node.callee)) {
54
+ return;
55
+ }
56
+ const [firstArg] = node.arguments;
57
+ if (firstArg && isInterpolatedTemplate(firstArg)) {
58
+ context.report({ node: firstArg, messageId: "dynamicKey" });
59
+ }
60
+ },
61
+ JSXAttribute(node) {
62
+ if (node.name.type !== utils_1.AST_NODE_TYPES.JSXIdentifier || node.name.name !== "i18nKey") {
63
+ return;
64
+ }
65
+ const { value } = node;
66
+ if (value?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer && isInterpolatedTemplate(value.expression)) {
67
+ context.report({ node: value.expression, messageId: "dynamicKey" });
68
+ }
69
+ },
70
+ };
71
+ },
72
+ });
73
+ //# sourceMappingURL=no-dynamic-translation-key.js.map
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noTemplateStringsInClassName = void 0;
4
+ const css_classname_utils_1 = require("@trackunit/css-classname-utils");
4
5
  const utils_1 = require("@typescript-eslint/utils");
5
- const classname_utils_1 = require("../../utils/classname-utils");
6
6
  const createRule = utils_1.ESLintUtils.RuleCreator(name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}.ts`);
7
7
  /**
8
8
  * ESLint rule to disallow template strings in className prop.
@@ -40,7 +40,7 @@ exports.noTemplateStringsInClassName = createRule({
40
40
  */
41
41
  JSXAttribute(node) {
42
42
  // Check if this is a classname attribute (className or class)
43
- if (node.name.type !== utils_1.AST_NODE_TYPES.JSXIdentifier || !(0, classname_utils_1.isClassnameAttribute)(node.name.name)) {
43
+ if (node.name.type !== utils_1.AST_NODE_TYPES.JSXIdentifier || !(0, css_classname_utils_1.isClassnameAttribute)(node.name.name)) {
44
44
  return;
45
45
  }
46
46
  // Check if the value is a template literal in a JSX expression container
@@ -50,7 +50,7 @@ exports.noTemplateStringsInClassName = createRule({
50
50
  }
51
51
  },
52
52
  VariableDeclarator(node) {
53
- const locations = (0, classname_utils_1.findClassnameStringsInVariable)(node);
53
+ const locations = (0, css_classname_utils_1.findClassnameStringsInVariable)(node);
54
54
  for (const location of locations) {
55
55
  if (location.fixNode.type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
56
56
  context.report({ node: location.reportNode, messageId: "templateStringInClassName" });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * ESLint rule that reports translation keys defined in a library's
3
+ * `locales/en/translation.json` that are never referenced anywhere in that
4
+ * library's source.
5
+ *
6
+ * Each library owns its own namespaced translations (see docs/tech/I18N.md), so
7
+ * a key is considered used when it appears quoted anywhere in the owning
8
+ * library's `.ts`/`.tsx` files ("conservative-anywhere" matching). Keys reached
9
+ * through a dynamic template (`` t(`prefix.${x}`) ``) are kept via the static
10
+ * text bookending the interpolation, and i18next plural forms are kept whenever
11
+ * their base key is referenced. A dynamic template keeps every key under its
12
+ * prefix, so keys behind one cannot be reported until the call site uses a
13
+ * static key — what the companion `no-dynamic-translation-key` rule asks for.
14
+ *
15
+ * Matching is a heuristic over source text: this rule is `warn`-only and has no
16
+ * fixer, so a key it reports is a candidate for review rather than for deletion.
17
+ *
18
+ * Runs only on `**\/locales/en/translation.json` — the English file is the
19
+ * source of truth; other languages are managed by Crowdin.
20
+ */
21
+ import { ESLintUtils } from "@typescript-eslint/utils";
22
+ export declare const noUnusedTranslationKey: ESLintUtils.RuleModule<"unusedKey", [], unknown, ESLintUtils.RuleListener> & {
23
+ name: string;
24
+ };
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noUnusedTranslationKey = void 0;
4
+ const tslib_1 = require("tslib");
5
+ /**
6
+ * ESLint rule that reports translation keys defined in a library's
7
+ * `locales/en/translation.json` that are never referenced anywhere in that
8
+ * library's source.
9
+ *
10
+ * Each library owns its own namespaced translations (see docs/tech/I18N.md), so
11
+ * a key is considered used when it appears quoted anywhere in the owning
12
+ * library's `.ts`/`.tsx` files ("conservative-anywhere" matching). Keys reached
13
+ * through a dynamic template (`` t(`prefix.${x}`) ``) are kept via the static
14
+ * text bookending the interpolation, and i18next plural forms are kept whenever
15
+ * their base key is referenced. A dynamic template keeps every key under its
16
+ * prefix, so keys behind one cannot be reported until the call site uses a
17
+ * static key — what the companion `no-dynamic-translation-key` rule asks for.
18
+ *
19
+ * Matching is a heuristic over source text: this rule is `warn`-only and has no
20
+ * fixer, so a key it reports is a candidate for review rather than for deletion.
21
+ *
22
+ * Runs only on `**\/locales/en/translation.json` — the English file is the
23
+ * source of truth; other languages are managed by Crowdin.
24
+ */
25
+ const utils_1 = require("@typescript-eslint/utils");
26
+ const path = tslib_1.__importStar(require("path"));
27
+ const file_utils_1 = require("../../utils/file-utils");
28
+ const used_strings_1 = require("./used-strings");
29
+ const createRule = utils_1.ESLintUtils.RuleCreator(name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}/${name}.ts`);
30
+ const TRANSLATION_FILE_SUFFIX = "/locales/en/translation.json";
31
+ const parseTranslationObject = (raw) => {
32
+ let parsed;
33
+ try {
34
+ parsed = JSON.parse(raw);
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
40
+ return null;
41
+ }
42
+ return { ...parsed };
43
+ };
44
+ exports.noUnusedTranslationKey = createRule({
45
+ name: "no-unused-translation-key",
46
+ meta: {
47
+ type: "suggestion",
48
+ docs: {
49
+ description: "Report translation keys in a library's locales/en/translation.json that are never referenced in that library's source.",
50
+ },
51
+ messages: {
52
+ unusedKey: 'Translation key "{{key}}" is not referenced anywhere in this library and can be removed.',
53
+ },
54
+ schema: [],
55
+ },
56
+ defaultOptions: [],
57
+ create(context) {
58
+ return {
59
+ Program: () => {
60
+ const filePath = context.filename.replace(/\\/g, "/");
61
+ if (!filePath.endsWith(TRANSLATION_FILE_SUFFIX)) {
62
+ return;
63
+ }
64
+ const projectJsonPath = (0, file_utils_1.findNearestFile)(context.filename, "project.json");
65
+ if (!projectJsonPath) {
66
+ return;
67
+ }
68
+ const raw = context.sourceCode.getText();
69
+ const translations = parseTranslationObject(raw);
70
+ if (!translations) {
71
+ return;
72
+ }
73
+ const used = (0, used_strings_1.getUsedTranslationStrings)(path.dirname(projectJsonPath));
74
+ const lines = raw.split("\n");
75
+ for (const key of Object.keys(translations)) {
76
+ if ((0, used_strings_1.isTranslationKeyUsed)(used, key)) {
77
+ continue;
78
+ }
79
+ const keyToken = `"${key}"`;
80
+ const lineIndex = lines.findIndex(text => text.includes(`${keyToken}:`));
81
+ const line = lineIndex >= 0 ? lineIndex + 1 : 1;
82
+ const startColumn = lineIndex >= 0 ? Math.max(lines[lineIndex]?.indexOf(keyToken) ?? 0, 0) : 0;
83
+ context.report({
84
+ messageId: "unusedKey",
85
+ data: { key },
86
+ loc: {
87
+ start: { line, column: startColumn },
88
+ end: { line, column: startColumn + keyToken.length },
89
+ },
90
+ });
91
+ }
92
+ },
93
+ };
94
+ },
95
+ });
96
+ //# sourceMappingURL=no-unused-translation-key.js.map
@@ -0,0 +1,43 @@
1
+ /** The static parts surrounding the interpolation(s) in a dynamically built key. */
2
+ export type DynamicPattern = {
3
+ /** Static text before the first `${...}` (empty for leading interpolation). */
4
+ prefix: string;
5
+ /** Static text after the last `${...}` (empty for trailing interpolation). */
6
+ suffix: string;
7
+ };
8
+ /**
9
+ * What a library's source says about the translation keys it uses.
10
+ *
11
+ * - `sourceText` — the library's `.ts`/`.tsx` files concatenated. A key is used when it appears in
12
+ * this text wrapped in quotes. Searching for the key itself puts no constraints on what a key may
13
+ * contain, so keys with spaces (`machine.types.Air Compressor`), empty segments (`a..b`) and
14
+ * punctuation (`p&pMigrated`) all match. Matching is deliberately broad ("conservative-anywhere"):
15
+ * a key counts as used wherever it appears, including as a prop passed to a child component
16
+ * (e.g. `titleKey="a.b"`).
17
+ * - `dynamicPatterns` — the static bookends of a dynamically built key, e.g.
18
+ * `` t(`pages.categories.${x}`) `` contributes `{ prefix: "pages.categories.", suffix: "" }` and
19
+ * `` t(`${root}.title`) `` contributes `{ prefix: "", suffix: ".title" }`. A key is treated as used
20
+ * when it both starts with the prefix and ends with the suffix of some pattern — the smallest set of
21
+ * keys the interpolation could possibly produce.
22
+ */
23
+ export type UsedStrings = {
24
+ sourceText: string;
25
+ dynamicPatterns: Array<DynamicPattern>;
26
+ };
27
+ /**
28
+ * Build the used-strings summary from already-read source file contents.
29
+ *
30
+ * Kept free of filesystem access so the ESLint rule and the
31
+ * `devtools/translations/remove-unused-translation-keys` script share one implementation of what
32
+ * "used" means, each doing its own IO.
33
+ */
34
+ export declare const buildUsedStrings: (contents: Array<string>) => UsedStrings;
35
+ /**
36
+ * Read a library's `.ts`/`.tsx` source, keeping the raw text plus any dynamic key patterns it builds.
37
+ */
38
+ export declare const getUsedTranslationStrings: (projectDir: string) => UsedStrings;
39
+ /**
40
+ * Whether a translation key is referenced by the scanned source — quoted verbatim, through a dynamic
41
+ * template's static bookends, or as the plural form of a referenced base key.
42
+ */
43
+ export declare const isTranslationKeyUsed: (used: UsedStrings, key: string) => boolean;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isTranslationKeyUsed = exports.getUsedTranslationStrings = exports.buildUsedStrings = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ const EXCLUDED_DIRS = new Set(["node_modules", "dist", "generated", "coverage", ".git"]);
8
+ const SCANNED_EXTENSIONS = [".ts", ".tsx"];
9
+ /**
10
+ * The plural (and optional ordinal) suffix i18next appends to a base key at runtime: `entityCount`
11
+ * resolves to `entityCount_one` or `entityCount_other` depending on the `count` option. Source code
12
+ * only ever mentions the base key, so a suffixed key is used whenever its base key is.
13
+ */
14
+ const PLURAL_SUFFIX_REGEX = /_(?:ordinal_)?(?:zero|one|two|few|many|other|plural)$/;
15
+ /** A template literal passed directly to `t(`/`.t(` or an `i18nKey={` attribute. */
16
+ const TRANSLATION_TEMPLATE_REGEX = /(?:\bt\(\s*|i18nKey=\{\s*)`([^`]*)`/g;
17
+ /** Any template literal — catches keys built through a variable, e.g. `const k = `a.b.${x}`; t(k)`. */
18
+ const ANY_TEMPLATE_REGEX = /`([^`]*)`/g;
19
+ /**
20
+ * Record the dynamic-key pattern for a template body, if applicable.
21
+ *
22
+ * `trusted` templates (written directly inside `t(...)`/`i18nKey={...}`) always contribute a pattern —
23
+ * including a bare `${x}` (empty prefix and suffix), which acts as a wildcard for libraries whose whole
24
+ * key is dynamic (e.g. `t(`${category}`)`). Untrusted templates (any other backtick string, e.g. one
25
+ * assigned to a variable then passed to `t`) only contribute when they look like a namespaced key — a
26
+ * dot in the prefix or suffix — so unrelated templates like `${count}` or `hello ${name}` don't
27
+ * wildcard-disable detection.
28
+ */
29
+ const recordTemplatePattern = (body, trusted, patterns) => {
30
+ const interpolationStart = body.indexOf("${");
31
+ if (interpolationStart === -1) {
32
+ // A backtick string with no interpolation is a plain key; sourceText already covers it.
33
+ return;
34
+ }
35
+ const prefix = body.slice(0, interpolationStart);
36
+ const lastInterpolationEnd = body.lastIndexOf("}");
37
+ const suffix = lastInterpolationEnd === -1 ? "" : body.slice(lastInterpolationEnd + 1);
38
+ if (!trusted && !prefix.includes(".") && !suffix.includes(".")) {
39
+ return;
40
+ }
41
+ patterns.set(JSON.stringify([prefix, suffix]), { prefix, suffix });
42
+ };
43
+ const collectSourceFiles = (dir, files = []) => {
44
+ let entries;
45
+ try {
46
+ entries = fs.readdirSync(dir, { withFileTypes: true });
47
+ }
48
+ catch {
49
+ return files;
50
+ }
51
+ for (const entry of entries) {
52
+ const fullPath = path.join(dir, entry.name);
53
+ if (entry.isDirectory()) {
54
+ if (!EXCLUDED_DIRS.has(entry.name)) {
55
+ collectSourceFiles(fullPath, files);
56
+ }
57
+ }
58
+ else if (SCANNED_EXTENSIONS.some(ext => entry.name.endsWith(ext))) {
59
+ files.push(fullPath);
60
+ }
61
+ }
62
+ return files;
63
+ };
64
+ /**
65
+ * Build the used-strings summary from already-read source file contents.
66
+ *
67
+ * Kept free of filesystem access so the ESLint rule and the
68
+ * `devtools/translations/remove-unused-translation-keys` script share one implementation of what
69
+ * "used" means, each doing its own IO.
70
+ */
71
+ const buildUsedStrings = (contents) => {
72
+ const patterns = new Map();
73
+ for (const content of contents) {
74
+ let translationTemplate;
75
+ while ((translationTemplate = TRANSLATION_TEMPLATE_REGEX.exec(content)) !== null) {
76
+ recordTemplatePattern(translationTemplate[1] ?? "", true, patterns);
77
+ }
78
+ let anyTemplate;
79
+ while ((anyTemplate = ANY_TEMPLATE_REGEX.exec(content)) !== null) {
80
+ recordTemplatePattern(anyTemplate[1] ?? "", false, patterns);
81
+ }
82
+ }
83
+ return { sourceText: contents.join("\n"), dynamicPatterns: [...patterns.values()] };
84
+ };
85
+ exports.buildUsedStrings = buildUsedStrings;
86
+ /**
87
+ * Read a library's `.ts`/`.tsx` source, keeping the raw text plus any dynamic key patterns it builds.
88
+ */
89
+ const getUsedTranslationStrings = (projectDir) => {
90
+ const contents = [];
91
+ for (const file of collectSourceFiles(projectDir)) {
92
+ try {
93
+ contents.push(fs.readFileSync(file, "utf8"));
94
+ }
95
+ catch {
96
+ continue;
97
+ }
98
+ }
99
+ return (0, exports.buildUsedStrings)(contents);
100
+ };
101
+ exports.getUsedTranslationStrings = getUsedTranslationStrings;
102
+ /** Whether the key appears in the source as a quoted string, in any of the three quote styles. */
103
+ const isQuotedInSource = (sourceText, key) => sourceText.includes(`"${key}"`) || sourceText.includes(`'${key}'`) || sourceText.includes(`\`${key}\``);
104
+ /**
105
+ * Whether a translation key is referenced by the scanned source — quoted verbatim, through a dynamic
106
+ * template's static bookends, or as the plural form of a referenced base key.
107
+ */
108
+ const isTranslationKeyUsed = (used, key) => {
109
+ const isReferenced = (candidate) => isQuotedInSource(used.sourceText, candidate) ||
110
+ used.dynamicPatterns.some(({ prefix, suffix }) => candidate.startsWith(prefix) && candidate.endsWith(suffix));
111
+ return isReferenced(key) || (PLURAL_SUFFIX_REGEX.test(key) && isReferenced(key.replace(PLURAL_SUFFIX_REGEX, "")));
112
+ };
113
+ exports.isTranslationKeyUsed = isTranslationKeyUsed;
114
+ //# sourceMappingURL=used-strings.js.map
@@ -1,26 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.requireClassnameAlternatives = void 0;
4
+ const css_classname_utils_1 = require("@trackunit/css-classname-utils");
4
5
  const utils_1 = require("@typescript-eslint/utils");
5
- const classname_utils_1 = require("../../utils/classname-utils");
6
6
  const createRule = utils_1.ESLintUtils.RuleCreator(name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}.ts`);
7
- /**
8
- * Replace a specific pattern in a string with its replacement.
9
- * Handles both prefix-based patterns (e.g., bg-red-500 -> bg-danger-500)
10
- * and exact matches.
11
- */
12
- function replacePattern(value, oldPattern, newPattern, hasPrefixes) {
13
- if (hasPrefixes) {
14
- // With prefixes, replace -oldPattern- with -newPattern-
15
- const pattern = new RegExp(`-${oldPattern}(-|$|/)`, "g");
16
- return value.replace(pattern, `-${newPattern}$1`);
17
- }
18
- else {
19
- // Without prefixes, replace exact word boundaries
20
- const pattern = new RegExp(`(^|\\s)${oldPattern}($|\\s)`, "g");
21
- return value.replace(pattern, `$1${newPattern}$2`);
22
- }
23
- }
24
7
  /**
25
8
  * Build a regex pattern that matches class names based on configuration.
26
9
  *
@@ -118,8 +101,8 @@ exports.requireClassnameAlternatives = createRule({
118
101
  let result = value;
119
102
  for (const [pattern, alts] of Object.entries(alternatives)) {
120
103
  const singleAlternative = alts.length === 1 ? alts[0] : undefined;
121
- if (singleAlternative) {
122
- result = replacePattern(result, pattern, singleAlternative, hasPrefixes);
104
+ if (singleAlternative !== undefined) {
105
+ result = (0, css_classname_utils_1.replaceClassToken)(result, pattern, singleAlternative, { prefixes });
123
106
  }
124
107
  }
125
108
  return result;
@@ -175,7 +158,7 @@ exports.requireClassnameAlternatives = createRule({
175
158
  if (typeof originalValue !== "string") {
176
159
  return null;
177
160
  }
178
- const fixedValue = replacePattern(originalValue, oldPattern, newPattern, hasPrefixes);
161
+ const fixedValue = (0, css_classname_utils_1.replaceClassToken)(originalValue, oldPattern, newPattern, { prefixes });
179
162
  const raw = stringLiteralNode.raw;
180
163
  const quote = raw.charAt(0);
181
164
  return fixer.replaceText(stringLiteralNode, `${quote}${fixedValue}${quote}`);
@@ -187,7 +170,7 @@ exports.requireClassnameAlternatives = createRule({
187
170
  ? quasi.value.raw.includes(`-${oldPattern}`)
188
171
  : quasi.value.raw.includes(oldPattern);
189
172
  if (containsPattern) {
190
- const fixedRaw = replacePattern(quasi.value.raw, oldPattern, newPattern, hasPrefixes);
173
+ const fixedRaw = (0, css_classname_utils_1.replaceClassToken)(quasi.value.raw, oldPattern, newPattern, { prefixes });
191
174
  const rangeStart = quasi.range[0] + (quasi.tail ? 1 : 0);
192
175
  const rangeEnd = quasi.range[1] - (quasi.tail ? 1 : 0);
193
176
  fixes.push(fixer.replaceTextRange([rangeStart, rangeEnd], quasi.tail ? fixedRaw : fixedRaw + (quasi.value.raw.endsWith(" ") ? "" : "")));
@@ -222,7 +205,7 @@ exports.requireClassnameAlternatives = createRule({
222
205
  if (!patternIsAutoFixable) {
223
206
  suggest = patternAlternatives.map(alternative => ({
224
207
  messageId: "suggestReplacement",
225
- data: { replacement: replacePattern(usage.match, usage.pattern, alternative, hasPrefixes) },
208
+ data: { replacement: (0, css_classname_utils_1.replaceClassToken)(usage.match, usage.pattern, alternative, { prefixes }) },
226
209
  fix: createSuggestionFixer(stringLiteralNode, usage.pattern, alternative),
227
210
  }));
228
211
  }
@@ -254,19 +237,19 @@ exports.requireClassnameAlternatives = createRule({
254
237
  }
255
238
  return {
256
239
  JSXAttribute(node) {
257
- const locations = (0, classname_utils_1.findClassnameStringsInAttribute)(node);
240
+ const locations = (0, css_classname_utils_1.findClassnameStringsInAttribute)(node);
258
241
  for (const location of locations) {
259
242
  checkAndReport(location.reportNode, location.value, location.fixNode);
260
243
  }
261
244
  },
262
245
  CallExpression(node) {
263
- const locations = (0, classname_utils_1.findClassnameStringsInCall)(node);
246
+ const locations = (0, css_classname_utils_1.findClassnameStringsInCall)(node);
264
247
  for (const location of locations) {
265
248
  checkAndReport(location.reportNode, location.value, location.fixNode);
266
249
  }
267
250
  },
268
251
  VariableDeclarator(node) {
269
- const locations = (0, classname_utils_1.findClassnameStringsInVariable)(node);
252
+ const locations = (0, css_classname_utils_1.findClassnameStringsInVariable)(node);
270
253
  for (const location of locations) {
271
254
  checkAndReport(location.reportNode, location.value, location.fixNode);
272
255
  }
@@ -25,6 +25,9 @@ export declare const rulesMap: {
25
25
  "no-deprecated-graphql-fields": ESLintUtils.RuleModule<"deprecatedFieldUsed", [], unknown, ESLintUtils.RuleListener> & {
26
26
  name: string;
27
27
  };
28
+ "no-dynamic-translation-key": ESLintUtils.RuleModule<"dynamicKey", [], unknown, ESLintUtils.RuleListener> & {
29
+ name: string;
30
+ };
28
31
  "no-internal-graphql-when-tagged-with-gql-public": ESLintUtils.RuleModule<"internalGraphqlFileFound", [], unknown, ESLintUtils.RuleListener> & {
29
32
  name: string;
30
33
  };
@@ -34,6 +37,9 @@ export declare const rulesMap: {
34
37
  "no-template-strings-in-classname-prop": ESLintUtils.RuleModule<"templateStringInClassName", [], unknown, ESLintUtils.RuleListener> & {
35
38
  name: string;
36
39
  };
40
+ "no-unused-translation-key": ESLintUtils.RuleModule<"unusedKey", [], unknown, ESLintUtils.RuleListener> & {
41
+ name: string;
42
+ };
37
43
  "require-classname-alternatives": ESLintUtils.RuleModule<"bannedClassAutoFix" | "bannedClassSuggest" | "suggestReplacement", [({
38
44
  alternatives: Record<string, Array<string>>;
39
45
  prefixes?: Array<string>;
@@ -5,11 +5,13 @@ const component_name_matches_filename_1 = require("./rules/component-name-matche
5
5
  const cva_merge_base_classes_as_array_1 = require("./rules/cva-merge-base-classes-as-array/cva-merge-base-classes-as-array");
6
6
  const design_guideline_button_icon_size_match_1 = require("./rules/design-guideline-button-icon-size-match/design-guideline-button-icon-size-match");
7
7
  const no_deprecated_graphql_fields_1 = require("./rules/no-deprecated-graphql-fields/no-deprecated-graphql-fields");
8
+ const no_dynamic_translation_key_1 = require("./rules/no-dynamic-translation-key/no-dynamic-translation-key");
8
9
  const no_internal_barrel_files_1 = require("./rules/no-internal-barrel-files/no-internal-barrel-files");
9
10
  const no_internal_graphql_when_tagged_with_gql_public_1 = require("./rules/no-internal-graphql-when-tagged-with-gql-public/no-internal-graphql-when-tagged-with-gql-public");
10
11
  const no_jest_mock_trackunit_react_core_hooks_1 = require("./rules/no-jest-mock-trackunit-react-core-hooks/no-jest-mock-trackunit-react-core-hooks");
11
12
  const no_template_strings_in_classname_prop_1 = require("./rules/no-template-strings-in-classname-prop/no-template-strings-in-classname-prop");
12
13
  const no_typescript_assertion_1 = require("./rules/no-typescript-assertion/no-typescript-assertion");
14
+ const no_unused_translation_key_1 = require("./rules/no-unused-translation-key/no-unused-translation-key");
13
15
  const one_component_per_file_1 = require("./rules/one-component-per-file/one-component-per-file");
14
16
  const prefer_destructured_imports_1 = require("./rules/prefer-destructured-imports/prefer-destructured-imports");
15
17
  const prefer_event_specific_callback_naming_1 = require("./rules/prefer-event-specific-callback-naming/prefer-event-specific-callback-naming");
@@ -27,9 +29,11 @@ exports.rulesMap = {
27
29
  "no-internal-barrel-files": no_internal_barrel_files_1.noInternalBarrelFiles,
28
30
  "no-typescript-assertion": no_typescript_assertion_1.noTypescriptAssertion,
29
31
  "no-deprecated-graphql-fields": no_deprecated_graphql_fields_1.noDeprecatedGraphqlFields,
32
+ "no-dynamic-translation-key": no_dynamic_translation_key_1.noDynamicTranslationKey,
30
33
  "no-internal-graphql-when-tagged-with-gql-public": no_internal_graphql_when_tagged_with_gql_public_1.noInternalGraphqlWhenTaggedWithGqlPublic,
31
34
  "design-guideline-button-icon-size-match": design_guideline_button_icon_size_match_1.designGuidelineButtonIconSizeMatch,
32
35
  "no-template-strings-in-classname-prop": no_template_strings_in_classname_prop_1.noTemplateStringsInClassName,
36
+ "no-unused-translation-key": no_unused_translation_key_1.noUnusedTranslationKey,
33
37
  "require-classname-alternatives": require_classname_alternatives_1.requireClassnameAlternatives,
34
38
  "no-jest-mock-trackunit-react-core-hooks": no_jest_mock_trackunit_react_core_hooks_1.noJestMockTrackunitReactCoreHooks,
35
39
  "prefer-destructured-imports": prefer_destructured_imports_1.preferDestructuredImports,