@trackunit/eslint-plugin-trackunit 0.6.103 → 0.6.105
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/package.json +2 -2
- package/src/lib/config/index.d.ts +4 -4
- package/src/lib/config/plugins.d.ts +1 -1
- package/src/lib/config/presets/base.d.ts +3 -3
- package/src/lib/config/presets/base.js +1 -1
- package/src/lib/config/presets/react.d.ts +1 -1
- package/src/lib/rules/no-unused-translation-key/no-unused-translation-key.d.ts +6 -3
- package/src/lib/rules/no-unused-translation-key/no-unused-translation-key.js +52 -40
- package/src/lib/rules-map.d.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## 0.6.105 (2026-08-13)
|
|
2
|
+
|
|
3
|
+
### 🧱 Updated Dependencies
|
|
4
|
+
|
|
5
|
+
- Updated css-classname-utils to 0.0.11
|
|
6
|
+
- Updated shared-utils to 1.16.8
|
|
7
|
+
|
|
8
|
+
## 0.6.104 (2026-08-13)
|
|
9
|
+
|
|
10
|
+
### 🧱 Updated Dependencies
|
|
11
|
+
|
|
12
|
+
- Updated css-classname-utils to 0.0.10
|
|
13
|
+
- Updated shared-utils to 1.16.7
|
|
14
|
+
|
|
1
15
|
## 0.6.103 (2026-08-13)
|
|
2
16
|
|
|
3
17
|
### 🧱 Updated Dependencies
|
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ All custom rules are enabled automatically through the presets under the `@track
|
|
|
95
95
|
| Rule | Severity | Auto-fix | Description |
|
|
96
96
|
| --------------------------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
97
97
|
| `@trackunit/no-dynamic-translation-key` | error | — | Discourages building i18next keys from template strings (`t(`a.${x}`)`). Map values to explicit keys with a `switch`/`Record`. |
|
|
98
|
-
| `@trackunit/no-unused-translation-key` |
|
|
98
|
+
| `@trackunit/no-unused-translation-key` | error | suggest | Reports keys in a library's `locales/en/translation.json` that are never referenced in that library's source. |
|
|
99
99
|
|
|
100
100
|
## Utilities
|
|
101
101
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/eslint-plugin-trackunit",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.105",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
5
5
|
"repository": "https://github.com/Trackunit/manager",
|
|
6
6
|
"engines": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@nx/eslint-plugin": "23.1.0",
|
|
11
|
-
"@trackunit/css-classname-utils": "0.0.
|
|
11
|
+
"@trackunit/css-classname-utils": "0.0.11",
|
|
12
12
|
"@typescript-eslint/eslint-plugin": "8.58.1",
|
|
13
13
|
"@typescript-eslint/utils": "8.58.1",
|
|
14
14
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -41,7 +41,7 @@ export declare const configs: {
|
|
|
41
41
|
"no-template-strings-in-classname-prop": import("@typescript-eslint/utils/ts-eslint").RuleModule<"templateStringInClassName", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
42
42
|
name: string;
|
|
43
43
|
};
|
|
44
|
-
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
44
|
+
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey" | "removeUnusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
45
45
|
name: string;
|
|
46
46
|
};
|
|
47
47
|
"require-classname-alternatives": import("@typescript-eslint/utils/ts-eslint").RuleModule<"bannedClassAutoFix" | "bannedClassSuggest" | "suggestReplacement", [({
|
|
@@ -245,7 +245,7 @@ export declare const configs: {
|
|
|
245
245
|
"no-template-strings-in-classname-prop": import("@typescript-eslint/utils/ts-eslint").RuleModule<"templateStringInClassName", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
246
246
|
name: string;
|
|
247
247
|
};
|
|
248
|
-
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
248
|
+
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey" | "removeUnusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
249
249
|
name: string;
|
|
250
250
|
};
|
|
251
251
|
"require-classname-alternatives": import("@typescript-eslint/utils/ts-eslint").RuleModule<"bannedClassAutoFix" | "bannedClassSuggest" | "suggestReplacement", [({
|
|
@@ -365,7 +365,7 @@ export declare const configs: {
|
|
|
365
365
|
"no-template-strings-in-classname-prop": import("@typescript-eslint/utils/ts-eslint").RuleModule<"templateStringInClassName", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
366
366
|
name: string;
|
|
367
367
|
};
|
|
368
|
-
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
368
|
+
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey" | "removeUnusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
369
369
|
name: string;
|
|
370
370
|
};
|
|
371
371
|
"require-classname-alternatives": import("@typescript-eslint/utils/ts-eslint").RuleModule<"bannedClassAutoFix" | "bannedClassSuggest" | "suggestReplacement", [({
|
|
@@ -501,7 +501,7 @@ export declare const configs: {
|
|
|
501
501
|
"no-template-strings-in-classname-prop": import("@typescript-eslint/utils/ts-eslint").RuleModule<"templateStringInClassName", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
502
502
|
name: string;
|
|
503
503
|
};
|
|
504
|
-
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
504
|
+
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey" | "removeUnusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
505
505
|
name: string;
|
|
506
506
|
};
|
|
507
507
|
"require-classname-alternatives": import("@typescript-eslint/utils/ts-eslint").RuleModule<"bannedClassAutoFix" | "bannedClassSuggest" | "suggestReplacement", [({
|
|
@@ -59,7 +59,7 @@ export declare const localRulesPlugin: {
|
|
|
59
59
|
"no-template-strings-in-classname-prop": import("@typescript-eslint/utils/ts-eslint").RuleModule<"templateStringInClassName", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
60
60
|
name: string;
|
|
61
61
|
};
|
|
62
|
-
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
62
|
+
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey" | "removeUnusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
63
63
|
name: string;
|
|
64
64
|
};
|
|
65
65
|
"require-classname-alternatives": import("@typescript-eslint/utils/ts-eslint").RuleModule<"bannedClassAutoFix" | "bannedClassSuggest" | "suggestReplacement", [({
|
|
@@ -41,7 +41,7 @@ export declare const base: (import("eslint").Linter.FlatConfig<import("eslint").
|
|
|
41
41
|
"no-template-strings-in-classname-prop": import("@typescript-eslint/utils/ts-eslint").RuleModule<"templateStringInClassName", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
42
42
|
name: string;
|
|
43
43
|
};
|
|
44
|
-
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
44
|
+
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey" | "removeUnusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
45
45
|
name: string;
|
|
46
46
|
};
|
|
47
47
|
"require-classname-alternatives": import("@typescript-eslint/utils/ts-eslint").RuleModule<"bannedClassAutoFix" | "bannedClassSuggest" | "suggestReplacement", [({
|
|
@@ -245,7 +245,7 @@ export declare const base: (import("eslint").Linter.FlatConfig<import("eslint").
|
|
|
245
245
|
"no-template-strings-in-classname-prop": import("@typescript-eslint/utils/ts-eslint").RuleModule<"templateStringInClassName", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
246
246
|
name: string;
|
|
247
247
|
};
|
|
248
|
-
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
248
|
+
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey" | "removeUnusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
249
249
|
name: string;
|
|
250
250
|
};
|
|
251
251
|
"require-classname-alternatives": import("@typescript-eslint/utils/ts-eslint").RuleModule<"bannedClassAutoFix" | "bannedClassSuggest" | "suggestReplacement", [({
|
|
@@ -365,7 +365,7 @@ export declare const base: (import("eslint").Linter.FlatConfig<import("eslint").
|
|
|
365
365
|
"no-template-strings-in-classname-prop": import("@typescript-eslint/utils/ts-eslint").RuleModule<"templateStringInClassName", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
366
366
|
name: string;
|
|
367
367
|
};
|
|
368
|
-
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
368
|
+
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey" | "removeUnusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
369
369
|
name: string;
|
|
370
370
|
};
|
|
371
371
|
"require-classname-alternatives": import("@typescript-eslint/utils/ts-eslint").RuleModule<"bannedClassAutoFix" | "bannedClassSuggest" | "suggestReplacement", [({
|
|
@@ -40,7 +40,7 @@ export declare const reactPreset: (import("eslint").Linter.Config<import("eslint
|
|
|
40
40
|
"no-template-strings-in-classname-prop": import("@typescript-eslint/utils/ts-eslint").RuleModule<"templateStringInClassName", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
41
41
|
name: string;
|
|
42
42
|
};
|
|
43
|
-
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
43
|
+
"no-unused-translation-key": import("@typescript-eslint/utils/ts-eslint").RuleModule<"unusedKey" | "removeUnusedKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
44
44
|
name: string;
|
|
45
45
|
};
|
|
46
46
|
"require-classname-alternatives": import("@typescript-eslint/utils/ts-eslint").RuleModule<"bannedClassAutoFix" | "bannedClassSuggest" | "suggestReplacement", [({
|
|
@@ -12,13 +12,16 @@
|
|
|
12
12
|
* prefix, so keys behind one cannot be reported until the call site uses a
|
|
13
13
|
* static key — what the companion `no-dynamic-translation-key` rule asks for.
|
|
14
14
|
*
|
|
15
|
-
* Matching is a heuristic over source text: this rule
|
|
16
|
-
*
|
|
15
|
+
* Matching is a heuristic over source text: this rule has no autofix, so a key
|
|
16
|
+
* it reports is still a candidate for review before applying the removal
|
|
17
|
+
* suggestion.
|
|
17
18
|
*
|
|
18
19
|
* Runs only on `**\/locales/en/translation.json` — the English file is the
|
|
19
20
|
* source of truth; other languages are managed by Crowdin.
|
|
20
21
|
*/
|
|
21
22
|
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
22
|
-
|
|
23
|
+
type MessageIds = "unusedKey" | "removeUnusedKey";
|
|
24
|
+
export declare const noUnusedTranslationKey: ESLintUtils.RuleModule<MessageIds, [], unknown, ESLintUtils.RuleListener> & {
|
|
23
25
|
name: string;
|
|
24
26
|
};
|
|
27
|
+
export {};
|
|
@@ -16,8 +16,9 @@ const tslib_1 = require("tslib");
|
|
|
16
16
|
* prefix, so keys behind one cannot be reported until the call site uses a
|
|
17
17
|
* static key — what the companion `no-dynamic-translation-key` rule asks for.
|
|
18
18
|
*
|
|
19
|
-
* Matching is a heuristic over source text: this rule
|
|
20
|
-
*
|
|
19
|
+
* Matching is a heuristic over source text: this rule has no autofix, so a key
|
|
20
|
+
* it reports is still a candidate for review before applying the removal
|
|
21
|
+
* suggestion.
|
|
21
22
|
*
|
|
22
23
|
* Runs only on `**\/locales/en/translation.json` — the English file is the
|
|
23
24
|
* source of truth; other languages are managed by Crowdin.
|
|
@@ -28,67 +29,78 @@ const file_utils_1 = require("../../utils/file-utils");
|
|
|
28
29
|
const used_strings_1 = require("./used-strings");
|
|
29
30
|
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
31
|
const TRANSLATION_FILE_SUFFIX = "/locales/en/translation.json";
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
const getKeyText = (key) => (key.type === "JSONIdentifier" ? key.name : String(key.value));
|
|
33
|
+
/**
|
|
34
|
+
* The source range to drop so the remaining object stays valid JSON: a property
|
|
35
|
+
* with a following sibling reaches up to that sibling (taking its own separating
|
|
36
|
+
* comma and indentation with it), a trailing property reaches back to the end of
|
|
37
|
+
* the preceding one, and a sole property leaves an empty object behind.
|
|
38
|
+
*/
|
|
39
|
+
const getRemovalRange = (property) => {
|
|
40
|
+
const siblings = property.parent.properties;
|
|
41
|
+
const position = siblings.indexOf(property);
|
|
42
|
+
const nextSibling = siblings[position + 1];
|
|
43
|
+
const previousSibling = siblings[position - 1];
|
|
44
|
+
if (nextSibling) {
|
|
45
|
+
return [property.range[0], nextSibling.range[0]];
|
|
38
46
|
}
|
|
39
|
-
if (
|
|
40
|
-
return
|
|
47
|
+
if (previousSibling) {
|
|
48
|
+
return [previousSibling.range[1], property.range[1]];
|
|
41
49
|
}
|
|
42
|
-
return
|
|
50
|
+
return [property.parent.range[0] + 1, property.parent.range[1] - 1];
|
|
43
51
|
};
|
|
44
52
|
exports.noUnusedTranslationKey = createRule({
|
|
45
53
|
name: "no-unused-translation-key",
|
|
46
54
|
meta: {
|
|
47
55
|
type: "suggestion",
|
|
56
|
+
hasSuggestions: true,
|
|
48
57
|
docs: {
|
|
49
58
|
description: "Report translation keys in a library's locales/en/translation.json that are never referenced in that library's source.",
|
|
50
59
|
},
|
|
51
60
|
messages: {
|
|
52
61
|
unusedKey: 'Translation key "{{key}}" is not referenced anywhere in this library and can be removed.',
|
|
62
|
+
removeUnusedKey: 'Remove translation key "{{key}}".',
|
|
53
63
|
},
|
|
54
64
|
schema: [],
|
|
55
65
|
},
|
|
56
66
|
defaultOptions: [],
|
|
57
67
|
create(context) {
|
|
68
|
+
const filePath = context.filename.replace(/\\/g, "/");
|
|
69
|
+
if (!filePath.endsWith(TRANSLATION_FILE_SUFFIX)) {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
const projectJsonPath = (0, file_utils_1.findNearestFile)(context.filename, "project.json");
|
|
73
|
+
if (!projectJsonPath) {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
// Scanning the whole library is expensive, so only pay for it once a key is actually inspected.
|
|
77
|
+
let usedStrings;
|
|
78
|
+
const getUsed = () => {
|
|
79
|
+
usedStrings ?? (usedStrings = (0, used_strings_1.getUsedTranslationStrings)(path.dirname(projectJsonPath)));
|
|
80
|
+
return usedStrings;
|
|
81
|
+
};
|
|
58
82
|
return {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (
|
|
83
|
+
JSONProperty: (property) => {
|
|
84
|
+
// Translation files are flat; anything nested belongs to a value, not to a key.
|
|
85
|
+
if (property.parent.parent.type !== "JSONExpressionStatement") {
|
|
62
86
|
return;
|
|
63
87
|
}
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
88
|
+
const key = getKeyText(property.key);
|
|
89
|
+
if ((0, used_strings_1.isTranslationKeyUsed)(getUsed(), key)) {
|
|
66
90
|
return;
|
|
67
91
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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 },
|
|
92
|
+
context.report({
|
|
93
|
+
messageId: "unusedKey",
|
|
94
|
+
data: { key },
|
|
95
|
+
loc: property.key.loc,
|
|
96
|
+
suggest: [
|
|
97
|
+
{
|
|
98
|
+
messageId: "removeUnusedKey",
|
|
99
|
+
data: { key },
|
|
100
|
+
fix: fixer => fixer.removeRange(getRemovalRange(property)),
|
|
89
101
|
},
|
|
90
|
-
|
|
91
|
-
}
|
|
102
|
+
],
|
|
103
|
+
});
|
|
92
104
|
},
|
|
93
105
|
};
|
|
94
106
|
},
|
package/src/lib/rules-map.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export declare const rulesMap: {
|
|
|
37
37
|
"no-template-strings-in-classname-prop": ESLintUtils.RuleModule<"templateStringInClassName", [], unknown, ESLintUtils.RuleListener> & {
|
|
38
38
|
name: string;
|
|
39
39
|
};
|
|
40
|
-
"no-unused-translation-key": ESLintUtils.RuleModule<"unusedKey", [], unknown, ESLintUtils.RuleListener> & {
|
|
40
|
+
"no-unused-translation-key": ESLintUtils.RuleModule<"unusedKey" | "removeUnusedKey", [], unknown, ESLintUtils.RuleListener> & {
|
|
41
41
|
name: string;
|
|
42
42
|
};
|
|
43
43
|
"require-classname-alternatives": ESLintUtils.RuleModule<"bannedClassAutoFix" | "bannedClassSuggest" | "suggestReplacement", [({
|