@trackunit/eslint-plugin-trackunit 0.6.101-alpha-d0f54ff2967.0 → 0.6.102
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 +23 -0
- package/README.md +1 -1
- package/package.json +2 -2
- package/src/lib/config/fragments/no-direct-storage-access.js +1 -0
- package/src/lib/config/index.d.ts +1 -0
- package/src/lib/config/presets/base.d.ts +1 -0
- package/src/lib/config/presets/base.js +2 -1
- package/src/lib/rules/no-dynamic-translation-key/no-dynamic-translation-key.js +32 -6
- package/src/lib/utils/import-utils.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
## 0.6.102 (2026-08-13)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- **react-components:** export non-hook localStorage accessors ([31ad79ac14d](https://github.com/Trackunit/manager/commit/31ad79ac14d))
|
|
6
|
+
|
|
7
|
+
### 🧱 Updated Dependencies
|
|
8
|
+
|
|
9
|
+
- Updated css-classname-utils to 0.0.8
|
|
10
|
+
- Updated shared-utils to 1.16.5
|
|
11
|
+
|
|
12
|
+
### ❤️ Thank You
|
|
13
|
+
|
|
14
|
+
- Cursor @cursoragent
|
|
15
|
+
- Mikkel Thorbjørn Andersen
|
|
16
|
+
|
|
17
|
+
## 0.6.101 (2026-08-12)
|
|
18
|
+
|
|
19
|
+
### 🧱 Updated Dependencies
|
|
20
|
+
|
|
21
|
+
- Updated css-classname-utils to 0.0.7
|
|
22
|
+
- Updated shared-utils to 1.16.4
|
|
23
|
+
|
|
1
24
|
## 0.6.100 (2026-08-11)
|
|
2
25
|
|
|
3
26
|
### 🩹 Fixes
|
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ All custom rules are enabled automatically through the presets under the `@track
|
|
|
94
94
|
|
|
95
95
|
| Rule | Severity | Auto-fix | Description |
|
|
96
96
|
| --------------------------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
97
|
-
| `@trackunit/no-dynamic-translation-key` |
|
|
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
98
|
| `@trackunit/no-unused-translation-key` | warn | — | Reports keys in a library's `locales/en/translation.json` that are never referenced in that library's source. |
|
|
99
99
|
|
|
100
100
|
## Utilities
|
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.102",
|
|
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.8",
|
|
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",
|
|
@@ -43,6 +43,7 @@ const storageHookImplementationFiles = [
|
|
|
43
43
|
"libs/react/components/src/hooks/localStorage/useLocalStorageReducer.ts",
|
|
44
44
|
"libs/react/components/src/hooks/localStorage/useSessionStorage.ts",
|
|
45
45
|
"libs/react/components/src/hooks/localStorage/useSessionStorageReducer.ts",
|
|
46
|
+
"libs/shared/utils/src/localStorage/localStorageAccessors.ts",
|
|
46
47
|
];
|
|
47
48
|
exports.noDirectStoragePropertyExceptionOverrides = {
|
|
48
49
|
basePath: (0, utils_1.findMonorepoRoot)(),
|
|
@@ -179,6 +179,7 @@ export declare const base: (import("eslint").Linter.FlatConfig<import("eslint").
|
|
|
179
179
|
"no-console": string;
|
|
180
180
|
"no-var": string;
|
|
181
181
|
"prefer-const": string;
|
|
182
|
+
"prefer-template": string;
|
|
182
183
|
"no-template-curly-in-string": string;
|
|
183
184
|
eqeqeq: string[];
|
|
184
185
|
"no-undef-init": string;
|
|
@@ -93,6 +93,7 @@ exports.base = [
|
|
|
93
93
|
"no-console": "error",
|
|
94
94
|
"no-var": "error",
|
|
95
95
|
"prefer-const": "error",
|
|
96
|
+
"prefer-template": "error",
|
|
96
97
|
"no-template-curly-in-string": "error",
|
|
97
98
|
eqeqeq: ["error", "always"],
|
|
98
99
|
"no-undef-init": "error",
|
|
@@ -107,7 +108,7 @@ exports.base = [
|
|
|
107
108
|
"no-restricted-globals": no_direct_storage_access_1.noDirectStorageGlobalsRule,
|
|
108
109
|
"@trackunit/no-internal-barrel-files": "error",
|
|
109
110
|
"@trackunit/no-typescript-assertion": "warn",
|
|
110
|
-
"@trackunit/no-dynamic-translation-key": "
|
|
111
|
+
"@trackunit/no-dynamic-translation-key": "error",
|
|
111
112
|
...import_rules_1.importRules,
|
|
112
113
|
...typescript_rules_1.typescriptRules,
|
|
113
114
|
},
|
|
@@ -25,6 +25,26 @@ const translation_key_types_1 = require("../../utils/translation-key-types");
|
|
|
25
25
|
const createRule = utils_1.ESLintUtils.RuleCreator(name => `https://github.com/trackunit/manager/blob/main/libs/eslint/plugin-trackunit/src/lib/rules/${name}/${name}.ts`);
|
|
26
26
|
/** A template literal that actually interpolates a value, e.g. `foo.${bar}` (not a plain `foo`). */
|
|
27
27
|
const isInterpolatedTemplate = (node) => node.type === utils_1.AST_NODE_TYPES.TemplateLiteral && node.expressions.length > 0;
|
|
28
|
+
/**
|
|
29
|
+
* Strips `as`/`satisfies`/non-null/`<T>`-assertion wrappers to reach the underlying expression.
|
|
30
|
+
*
|
|
31
|
+
* `` t(`a.${x}` as never) `` parses as a `TSAsExpression` whose `.expression` is the real
|
|
32
|
+
* `TemplateLiteral`, so without this the wrapper silently hides the dynamic key from the rule.
|
|
33
|
+
* That is an escape hatch we must not offer: casting a widened interpolation is exactly what this
|
|
34
|
+
* rule exists to forbid, and the sibling `no-unused-translation-key` already unwraps the same
|
|
35
|
+
* wrappers (see its `expand-dynamic-key.ts#unwrapToTemplateExpression`), so leaving them here means
|
|
36
|
+
* one rule sees the key and the other does not.
|
|
37
|
+
*/
|
|
38
|
+
const unwrapTypeWrappers = (node) => {
|
|
39
|
+
let current = node;
|
|
40
|
+
while (current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
41
|
+
current.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
|
|
42
|
+
current.type === utils_1.AST_NODE_TYPES.TSNonNullExpression ||
|
|
43
|
+
current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion) {
|
|
44
|
+
current = current.expression;
|
|
45
|
+
}
|
|
46
|
+
return current;
|
|
47
|
+
};
|
|
28
48
|
/**
|
|
29
49
|
* Whether a template literal's interpolations should be flagged: true unless every
|
|
30
50
|
* interpolated expression's TypeScript type is a finite string-literal union (in which
|
|
@@ -75,8 +95,12 @@ exports.noDynamicTranslationKey = createRule({
|
|
|
75
95
|
return;
|
|
76
96
|
}
|
|
77
97
|
const [firstArg] = node.arguments;
|
|
78
|
-
if (firstArg
|
|
79
|
-
|
|
98
|
+
if (!firstArg) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const key = unwrapTypeWrappers(firstArg);
|
|
102
|
+
if (isInterpolatedTemplate(key) && hasWidenedInterpolation(context, key)) {
|
|
103
|
+
context.report({ node: key, messageId: "dynamicKey" });
|
|
80
104
|
}
|
|
81
105
|
},
|
|
82
106
|
JSXAttribute(node) {
|
|
@@ -84,10 +108,12 @@ exports.noDynamicTranslationKey = createRule({
|
|
|
84
108
|
return;
|
|
85
109
|
}
|
|
86
110
|
const { value } = node;
|
|
87
|
-
if (value?.type
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
if (value?.type !== utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const key = unwrapTypeWrappers(value.expression);
|
|
115
|
+
if (isInterpolatedTemplate(key) && hasWidenedInterpolation(context, key)) {
|
|
116
|
+
context.report({ node: key, messageId: "dynamicKey" });
|
|
91
117
|
}
|
|
92
118
|
},
|
|
93
119
|
};
|
|
@@ -86,14 +86,14 @@ const addImportSpecifiers = (options) => {
|
|
|
86
86
|
const lastImport = sourceCode.ast.body.find((node) => node.type === utils_1.AST_NODE_TYPES.ImportDeclaration);
|
|
87
87
|
if (lastImport) {
|
|
88
88
|
// Add after existing imports with leading newline
|
|
89
|
-
const importStatement =
|
|
89
|
+
const importStatement = `\n${buildImportStatement(sourceCode, packageName, specifiers, lastImport)}`;
|
|
90
90
|
fixes.push(fixer.insertTextAfter(lastImport, importStatement));
|
|
91
91
|
}
|
|
92
92
|
else {
|
|
93
93
|
// No imports exist - add at the beginning with trailing newlines
|
|
94
94
|
const firstNode = sourceCode.ast.body[0];
|
|
95
95
|
if (firstNode) {
|
|
96
|
-
const importStatement = buildImportStatement(sourceCode, packageName, specifiers)
|
|
96
|
+
const importStatement = `${buildImportStatement(sourceCode, packageName, specifiers)}\n\n`;
|
|
97
97
|
fixes.push(fixer.insertTextBefore(firstNode, importStatement));
|
|
98
98
|
}
|
|
99
99
|
}
|