@trackunit/eslint-plugin-trackunit 0.6.165 → 0.6.167

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,17 @@
1
+ ## 0.6.167 (2026-10-07)
2
+
3
+ ### 🧱 Updated Dependencies
4
+
5
+ - Updated css-classname-utils to 0.0.68
6
+ - Updated shared-utils to 1.19.2
7
+
8
+ ## 0.6.166 (2026-10-07)
9
+
10
+ ### 🧱 Updated Dependencies
11
+
12
+ - Updated css-classname-utils to 0.0.67
13
+ - Updated shared-utils to 1.19.1
14
+
1
15
  ## 0.6.165 (2026-10-06)
2
16
 
3
17
  ### 🧱 Updated Dependencies
package/README.md CHANGED
@@ -97,7 +97,7 @@ project.
97
97
  | ----------------------------------------------------- | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
98
98
  | `@trackunit/storybook/require-folder-landing-page` | error | no | Requires a `*.docs.mdx` landing page (`<Meta title="...">`) for every ancestor folder implied by a story's `title` — including titles read from CSF factory `<binding>.meta({...})` calls. Without a landing page, Storybook renders an empty grouping node in the sidebar. |
99
99
  | `@trackunit/require-storybook-folder-landing-page` | not enabled by default | no | Deprecated alias of `@trackunit/storybook/require-folder-landing-page` — same rule implementation, kept only so configs still referencing the pre-namespace id keep working. Neither the published `react` preset nor the internal `storybook` preset enables it; a consumer must re-enable it explicitly. |
100
- | `@trackunit/storybook/require-csf-factory` | warn | no | Detection-only: flags story files in the legacy `export default {...}` shape (`oldFormat`), files mixing that shape with a factory `.meta({...})` call (`mixedFormat`), and files with neither signal (`missingMeta`). Stays silent on files already in the CSF **factory** format (`<binding>.meta({...})` + `<meta>.story({...})`). |
100
+ | `@trackunit/storybook/require-csf-factory` | error | no | Detection-only: flags story files in the legacy `export default {...}` shape (`oldFormat`), files mixing that shape with a factory `.meta({...})` call (`mixedFormat`), and files with neither signal (`missingMeta`). Stays silent on files already in the CSF **factory** format (`<binding>.meta({...})` + `<meta>.story({...})`). |
101
101
  | `@trackunit/storybook/no-local-meta-component` | error | no | Detection-only: flags a meta `component` (in `export default {...}`, `const meta = {...}; export default meta`, or any `<binding>.meta({...})`) that resolves to a function/class declared in the story file, an inline component, a wrapper call around a local (`memo(Local)`), or an import from another `*.stories` module. Docgen skips `*.stories.*`, so Controls loses JSDoc prop descriptions and "Show code" names a story-only identifier. Fix by pointing at the real component (`component: MultiSelectField<Option>` for generics) or moving the demo into a sibling `*.demo.tsx`. |
102
102
  | `storybook/*` (upstream `eslint-plugin-storybook@^10.6.0` `flat/recommended`) | per-rule (upstream defaults), with `storybook/default-exports` forced `off` | per-rule | Upstream Storybook lint rules, spread only by the internal `storybook` preset — never by the published `react` preset. `storybook/default-exports` is turned back off immediately after the spread because it contradicts the CSF factory format's implicit default export via the `meta` object; `require-csf-factory` above already reports a story file with no meta/default export at all, so no detection guarantee is lost by disabling it. |
103
103
 
@@ -113,7 +113,7 @@ module.exports = [
113
113
  {
114
114
  rules: {
115
115
  "@trackunit/storybook/require-folder-landing-page": "error",
116
- "@trackunit/storybook/require-csf-factory": "warn",
116
+ "@trackunit/storybook/require-csf-factory": "error",
117
117
  "@trackunit/storybook/no-local-meta-component": "error",
118
118
  },
119
119
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/eslint-plugin-trackunit",
3
- "version": "0.6.165",
3
+ "version": "0.6.167",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "repository": "https://github.com/Trackunit/manager",
6
6
  "engines": {
@@ -8,9 +8,9 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@nx/eslint-plugin": "23.1.0",
11
- "@trackunit/css-classname-utils": "0.0.66",
12
- "@typescript-eslint/eslint-plugin": "8.58.1",
13
- "@typescript-eslint/utils": "8.58.1",
11
+ "@trackunit/css-classname-utils": "0.0.68",
12
+ "@typescript-eslint/eslint-plugin": "8.70.1",
13
+ "@typescript-eslint/utils": "8.70.1",
14
14
  "eslint-config-prettier": "^10.1.8",
15
15
  "eslint-plugin-import": "2.32.0",
16
16
  "eslint-plugin-jest": "^29.15.2",
@@ -5,7 +5,8 @@ exports.moduleBoundaryRules = {
5
5
  "@nx/enforce-module-boundaries": [
6
6
  "error",
7
7
  {
8
- allow: [],
8
+ // Ambient alias for the loading Storybook's preview (`typings/storybook-preview.d.ts`), not a project.
9
+ allow: ["@trackunit/storybook/preview"],
9
10
  depConstraints: [
10
11
  {
11
12
  sourceTag: "type:app",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Warn when a `*.stories.*` file is written in the legacy CSF (Component Story Format)
2
+ * Report a `*.stories.*` file written in the legacy CSF (Component Story Format)
3
3
  * default-export shape instead of Storybook's CSF **factory** format.
4
4
  *
5
5
  * Architecture: per-story-file rule, syntax-only (no TypeScript type information — see
@@ -11,7 +11,8 @@
11
11
  * meta;` variable), and no factory `.meta({...})` call anywhere in the file. Reported with
12
12
  * `oldFormat`, pointing at the `export default` node.
13
13
  * - **Factory format** (silent) — a call `<importedBinding>.meta({...})` (e.g.
14
- * `const meta = preview.meta({ component, title });`), where `<importedBinding>` resolves to
14
+ * `const meta = preview.meta({ component, title });`) or
15
+ * `<importedBinding>.type<{ args }>().meta({...})`, where `<importedBinding>` resolves to
15
16
  * an imported identifier, and NEITHER an `export default` NOR an old-style named
16
17
  * object-literal story export (`export const Foo = { args: {...} };`, including a `satisfies` or
17
18
  * `as` wrapper around that object) is present. Individual
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Warn when a `*.stories.*` file is written in the legacy CSF (Component Story Format)
3
+ * Report a `*.stories.*` file written in the legacy CSF (Component Story Format)
4
4
  * default-export shape instead of Storybook's CSF **factory** format.
5
5
  *
6
6
  * Architecture: per-story-file rule, syntax-only (no TypeScript type information — see
@@ -12,7 +12,8 @@
12
12
  * meta;` variable), and no factory `.meta({...})` call anywhere in the file. Reported with
13
13
  * `oldFormat`, pointing at the `export default` node.
14
14
  * - **Factory format** (silent) — a call `<importedBinding>.meta({...})` (e.g.
15
- * `const meta = preview.meta({ component, title });`), where `<importedBinding>` resolves to
15
+ * `const meta = preview.meta({ component, title });`) or
16
+ * `<importedBinding>.type<{ args }>().meta({...})`, where `<importedBinding>` resolves to
16
17
  * an imported identifier, and NEITHER an `export default` NOR an old-style named
17
18
  * object-literal story export (`export const Foo = { args: {...} };`, including a `satisfies` or
18
19
  * `as` wrapper around that object) is present. Individual
@@ -75,28 +76,48 @@ const isAnalyzableStoryFile = (filename) => {
75
76
  return STORY_FILE_REGEX.test(filename);
76
77
  };
77
78
  /**
78
- * True for a call `<identifier>.meta({...})` where `<identifier>` resolves to an imported
79
- * binding. Only the call *shape* is inspected — the object argument's properties (e.g. a
80
- * generic/instantiation `component:` value) are never type-checked or destructured, so this
81
- * rule stays syntax-only.
79
+ * The `<identifier>` a `.meta(...)` call is made on: `preview` in `preview.meta(...)` and in the
80
+ * typed escape hatch `preview.type<{ args }>().meta(...)`. Null for any other receiver.
81
+ */
82
+ const getMetaReceiver = (receiver) => {
83
+ if (receiver.type === utils_1.AST_NODE_TYPES.Identifier) {
84
+ return receiver;
85
+ }
86
+ if (receiver.type === utils_1.AST_NODE_TYPES.CallExpression &&
87
+ receiver.arguments.length === 0 &&
88
+ receiver.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
89
+ !receiver.callee.computed &&
90
+ receiver.callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
91
+ receiver.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
92
+ receiver.callee.property.name === "type") {
93
+ return receiver.callee.object;
94
+ }
95
+ return null;
96
+ };
97
+ /**
98
+ * True for a call `<identifier>.meta({...})` or `<identifier>.type<…>().meta({...})` where
99
+ * `<identifier>` resolves to an imported binding. Only the call *shape* is inspected — the object
100
+ * argument's properties (e.g. a generic/instantiation `component:` value) are never type-checked
101
+ * or destructured, so this rule stays syntax-only.
82
102
  */
83
103
  const isFactoryMetaCall = (node, context) => {
84
104
  const { callee } = node;
85
105
  if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
86
106
  return false;
87
107
  }
88
- if (callee.property.type !== utils_1.AST_NODE_TYPES.Identifier || callee.property.name !== "meta") {
108
+ if (callee.computed || callee.property.type !== utils_1.AST_NODE_TYPES.Identifier || callee.property.name !== "meta") {
89
109
  return false;
90
110
  }
91
- if (callee.object.type !== utils_1.AST_NODE_TYPES.Identifier) {
111
+ const receiver = getMetaReceiver(callee.object);
112
+ if (receiver === null) {
92
113
  return false;
93
114
  }
94
115
  const [firstArgument] = node.arguments;
95
116
  if (firstArgument === undefined || firstArgument.type !== utils_1.AST_NODE_TYPES.ObjectExpression) {
96
117
  return false;
97
118
  }
98
- const scope = context.sourceCode.getScope(callee.object);
99
- const variable = scope.references.find(ref => ref.identifier === callee.object)?.resolved;
119
+ const scope = context.sourceCode.getScope(receiver);
120
+ const variable = scope.references.find(ref => ref.identifier === receiver)?.resolved;
100
121
  if (!variable) {
101
122
  return false;
102
123
  }
@@ -170,7 +191,7 @@ exports.requireCsfFactory = createRule({
170
191
  meta: {
171
192
  type: "problem",
172
193
  docs: {
173
- description: "Warn on Storybook story files that are not written in CSF factory format",
194
+ description: "Report Storybook story files that are not written in CSF factory format",
174
195
  },
175
196
  schema: [],
176
197
  messages: {