@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 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` | warn | — | Discourages building i18next keys from template strings (`t(`a.${x}`)`). Map values to explicit keys with a `switch`/`Record`. |
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.101-alpha-d0f54ff2967.0",
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.7-alpha-d0f54ff2967.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 configs: {
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;
@@ -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": "warn",
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 && isInterpolatedTemplate(firstArg) && hasWidenedInterpolation(context, firstArg)) {
79
- context.report({ node: firstArg, messageId: "dynamicKey" });
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 === utils_1.AST_NODE_TYPES.JSXExpressionContainer &&
88
- isInterpolatedTemplate(value.expression) &&
89
- hasWidenedInterpolation(context, value.expression)) {
90
- context.report({ node: value.expression, messageId: "dynamicKey" });
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 = "\n" + buildImportStatement(sourceCode, packageName, specifiers, lastImport);
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) + "\n\n";
96
+ const importStatement = `${buildImportStatement(sourceCode, packageName, specifiers)}\n\n`;
97
97
  fixes.push(fixer.insertTextBefore(firstNode, importStatement));
98
98
  }
99
99
  }