@trackunit/eslint-plugin-trackunit 0.6.157 → 0.6.159

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.159 (2026-09-30)
2
+
3
+ ### 🧱 Updated Dependencies
4
+
5
+ - Updated css-classname-utils to 0.0.60
6
+ - Updated shared-utils to 1.17.4
7
+
8
+ ## 0.6.158 (2026-09-30)
9
+
10
+ ### 🧱 Updated Dependencies
11
+
12
+ - Updated css-classname-utils to 0.0.59
13
+ - Updated shared-utils to 1.17.3
14
+
1
15
  ## 0.6.157 (2026-09-29)
2
16
 
3
17
  ### 🧱 Updated Dependencies
package/README.md CHANGED
@@ -98,6 +98,7 @@ project.
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
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({...})`). |
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`. |
101
102
  | `storybook/*` (upstream `eslint-plugin-storybook@^10.3.5` `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. |
102
103
 
103
104
  **External consumers opt in themselves.** The published `react` preset registers the
@@ -113,14 +114,12 @@ module.exports = [
113
114
  rules: {
114
115
  "@trackunit/storybook/require-folder-landing-page": "error",
115
116
  "@trackunit/storybook/require-csf-factory": "warn",
117
+ "@trackunit/storybook/no-local-meta-component": "error",
116
118
  },
117
119
  },
118
120
  ];
119
121
  ```
120
122
 
121
- > **Planned:** A `no-local-meta-component` rule is expected to join the
122
- > `@trackunit/storybook/*` namespace once it's written; it does not exist yet.
123
-
124
123
  ### Localization
125
124
 
126
125
  | Rule | Severity | Auto-fix | Description |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/eslint-plugin-trackunit",
3
- "version": "0.6.157",
3
+ "version": "0.6.159",
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.58",
11
+ "@trackunit/css-classname-utils": "0.0.60",
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",
@@ -611,6 +611,9 @@ export declare const configs: {
611
611
  "require-csf-factory": import("@typescript-eslint/utils/ts-eslint").RuleModule<"oldFormat" | "mixedFormat" | "missingMeta", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
612
612
  name: string;
613
613
  };
614
+ "no-local-meta-component": import("@typescript-eslint/utils/ts-eslint").RuleModule<"localMetaComponent", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
615
+ name: string;
616
+ };
614
617
  };
615
618
  };
616
619
  "@typescript-eslint": {
@@ -138,6 +138,9 @@ export declare const storybookPlugin: {
138
138
  "require-csf-factory": import("@typescript-eslint/utils/ts-eslint").RuleModule<"oldFormat" | "mixedFormat" | "missingMeta", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
139
139
  name: string;
140
140
  };
141
+ "no-local-meta-component": import("@typescript-eslint/utils/ts-eslint").RuleModule<"localMetaComponent", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
142
+ name: string;
143
+ };
141
144
  };
142
145
  };
143
146
  export { eslintConfigPrettier, globals, importPlugin, jest, jsdoc, jsoncParser, noNull, nx, react, reactHooks, testingLibrary, tsParser, typescriptEslint, };
@@ -37,9 +37,10 @@ const globals = tslib_1.__importStar(require("globals"));
37
37
  exports.globals = globals;
38
38
  const jsoncParser = tslib_1.__importStar(require("jsonc-eslint-parser"));
39
39
  exports.jsoncParser = jsoncParser;
40
+ const rules_map_1 = require("../rules-map");
41
+ const no_local_meta_component_1 = require("../rules/no-local-meta-component/no-local-meta-component");
40
42
  const require_csf_factory_1 = require("../rules/require-csf-factory/require-csf-factory");
41
43
  const require_storybook_folder_landing_page_1 = require("../rules/require-storybook-folder-landing-page/require-storybook-folder-landing-page");
42
- const rules_map_1 = require("../rules-map");
43
44
  exports.localRulesPlugin = { rules: rules_map_1.rulesMap };
44
45
  /**
45
46
  * Shared plugin object for the `@trackunit/storybook` namespace. Rule code stays in this
@@ -54,6 +55,7 @@ exports.storybookPlugin = {
54
55
  rules: {
55
56
  "require-folder-landing-page": require_storybook_folder_landing_page_1.requireFolderLandingPage,
56
57
  "require-csf-factory": require_csf_factory_1.requireCsfFactory,
58
+ "no-local-meta-component": no_local_meta_component_1.noLocalMetaComponent,
57
59
  },
58
60
  };
59
61
  //# sourceMappingURL=plugins.js.map
@@ -110,6 +110,9 @@ export declare const reactPreset: (import("eslint").Linter.Config<import("eslint
110
110
  "require-csf-factory": import("@typescript-eslint/utils/ts-eslint").RuleModule<"oldFormat" | "mixedFormat" | "missingMeta", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
111
111
  name: string;
112
112
  };
113
+ "no-local-meta-component": import("@typescript-eslint/utils/ts-eslint").RuleModule<"localMetaComponent", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
114
+ name: string;
115
+ };
113
116
  };
114
117
  };
115
118
  "@typescript-eslint": {
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Error when a Storybook meta's `component` is declared inside a `*.stories.*` file.
3
+ *
4
+ * Architecture: per-story-file rule, syntax-only AST + ESLint scope analysis (no TypeScript type
5
+ * information). The meta object is read from any of:
6
+ * - `export default { component, ... }` (including a `satisfies` / `as` wrapper),
7
+ * - `const meta = { component, ... } satisfies Meta<...>; export default meta;` (or `as`, or a
8
+ * plain type annotation: `const meta: Meta<...> = { component, ... };`),
9
+ * - any `<x>.meta({ component, ... })` member call (covers `preview.meta({...})` and
10
+ * `preview.type<...>().meta({...})`).
11
+ *
12
+ * The `component` value is unwrapped (`satisfies`, `as`, instantiation expressions such as
13
+ * `MultiSelectField<Option>`) and resolved through scope. It is reported when it resolves to:
14
+ * - a function/arrow/class expression written inline or declared in this file,
15
+ * - a call (`memo(Local)`, `forwardRef(Local)`, any call) whose wrapped component resolves locally.
16
+ * The wrapped component is the first traceable argument, skipping literal options and inline
17
+ * functions after the first position — `memo(Widget, areEqual)` wraps `Widget`,
18
+ * `memo(Local, isEqual)` wraps `Local`,
19
+ * - member access on, destructuring from, or a `const` alias of, something that resolves locally
20
+ * (for an object literal, only the accessed or destructured property is checked, following
21
+ * spreads into their source; a computed member or a nested/unresolvable destructuring pattern
22
+ * is untraced rather than checked against every sibling),
23
+ * - an import from another `*.stories` module.
24
+ * Imports from non-story modules (and aliases / member access / instantiations of them) pass.
25
+ *
26
+ * Metas without `component`, non-story files, and locally declared components that are not the
27
+ * meta component (e.g. example-only demos used in `render`) are never checked.
28
+ *
29
+ * This is a **detection-only** rule: no autofix and no suggestions — the fix is either importing
30
+ * the real component or moving the demo into a sibling `*.demo.tsx` file, both of which need
31
+ * developer intent and cross-file edits.
32
+ *
33
+ * See README.md for incorrect/correct examples.
34
+ */
35
+ import { ESLintUtils } from "@typescript-eslint/utils";
36
+ /** Canonical permalink to this rule's README, embedded in the message. */
37
+ export declare const NO_LOCAL_META_COMPONENT_DOCS_URL = "https://github.com/Trackunit/manager/blob/master/libs/eslint/plugin-trackunit/src/lib/rules/no-local-meta-component/README.md";
38
+ export declare const noLocalMetaComponent: ESLintUtils.RuleModule<"localMetaComponent", [], unknown, ESLintUtils.RuleListener> & {
39
+ name: string;
40
+ };
@@ -0,0 +1,352 @@
1
+ "use strict";
2
+ /**
3
+ * Error when a Storybook meta's `component` is declared inside a `*.stories.*` file.
4
+ *
5
+ * Architecture: per-story-file rule, syntax-only AST + ESLint scope analysis (no TypeScript type
6
+ * information). The meta object is read from any of:
7
+ * - `export default { component, ... }` (including a `satisfies` / `as` wrapper),
8
+ * - `const meta = { component, ... } satisfies Meta<...>; export default meta;` (or `as`, or a
9
+ * plain type annotation: `const meta: Meta<...> = { component, ... };`),
10
+ * - any `<x>.meta({ component, ... })` member call (covers `preview.meta({...})` and
11
+ * `preview.type<...>().meta({...})`).
12
+ *
13
+ * The `component` value is unwrapped (`satisfies`, `as`, instantiation expressions such as
14
+ * `MultiSelectField<Option>`) and resolved through scope. It is reported when it resolves to:
15
+ * - a function/arrow/class expression written inline or declared in this file,
16
+ * - a call (`memo(Local)`, `forwardRef(Local)`, any call) whose wrapped component resolves locally.
17
+ * The wrapped component is the first traceable argument, skipping literal options and inline
18
+ * functions after the first position — `memo(Widget, areEqual)` wraps `Widget`,
19
+ * `memo(Local, isEqual)` wraps `Local`,
20
+ * - member access on, destructuring from, or a `const` alias of, something that resolves locally
21
+ * (for an object literal, only the accessed or destructured property is checked, following
22
+ * spreads into their source; a computed member or a nested/unresolvable destructuring pattern
23
+ * is untraced rather than checked against every sibling),
24
+ * - an import from another `*.stories` module.
25
+ * Imports from non-story modules (and aliases / member access / instantiations of them) pass.
26
+ *
27
+ * Metas without `component`, non-story files, and locally declared components that are not the
28
+ * meta component (e.g. example-only demos used in `render`) are never checked.
29
+ *
30
+ * This is a **detection-only** rule: no autofix and no suggestions — the fix is either importing
31
+ * the real component or moving the demo into a sibling `*.demo.tsx` file, both of which need
32
+ * developer intent and cross-file edits.
33
+ *
34
+ * See README.md for incorrect/correct examples.
35
+ */
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.noLocalMetaComponent = exports.NO_LOCAL_META_COMPONENT_DOCS_URL = void 0;
38
+ const utils_1 = require("@typescript-eslint/utils");
39
+ const { DefinitionType } = utils_1.TSESLint.Scope;
40
+ const createRule = utils_1.ESLintUtils.RuleCreator(name => `https://github.com/trackunit/manager/blob/master/libs/eslint/plugin-trackunit/src/lib/rules/${name}/${name}.ts`);
41
+ /** Canonical permalink to this rule's README, embedded in the message. */
42
+ exports.NO_LOCAL_META_COMPONENT_DOCS_URL = "https://github.com/Trackunit/manager/blob/master/libs/eslint/plugin-trackunit/src/lib/rules/no-local-meta-component/README.md";
43
+ const STORY_FILE_REGEX = /\.stories\.(js|jsx|ts|tsx)$/;
44
+ // Import sources are usually extensionless (`./Other.stories`), so the extension is optional.
45
+ const STORY_IMPORT_SOURCE_REGEX = /\.stories(\.(js|jsx|ts|tsx))?$/;
46
+ /** Unwrap `satisfies` / `as` wrappers and instantiation expressions (`MultiSelectField<Option>`). */
47
+ const unwrapExpression = (node) => {
48
+ let current = node;
49
+ while (current.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
50
+ current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
51
+ current.type === utils_1.AST_NODE_TYPES.TSInstantiationExpression) {
52
+ current = current.expression;
53
+ }
54
+ return current;
55
+ };
56
+ const getComponentValue = (object) => {
57
+ for (const property of object.properties) {
58
+ if (property.type === utils_1.AST_NODE_TYPES.Property &&
59
+ !property.computed &&
60
+ property.key.type === utils_1.AST_NODE_TYPES.Identifier &&
61
+ property.key.name === "component") {
62
+ return property.value;
63
+ }
64
+ }
65
+ return null;
66
+ };
67
+ const isInlineComponent = (node) => node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
68
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
69
+ node.type === utils_1.AST_NODE_TYPES.ClassExpression;
70
+ const create = context => {
71
+ if (!STORY_FILE_REGEX.test(context.filename)) {
72
+ return {};
73
+ }
74
+ /**
75
+ * `name`'s key and default in `const { key: name = default } = ...`. `null` when `name` isn't a
76
+ * direct property of `pattern` — a nested pattern (`{ a: { b: name } }`) or a computed key
77
+ * (`{ [k]: name }`) — since which property that is can't be read syntactically.
78
+ */
79
+ const getDestructuredKey = (pattern, name) => {
80
+ for (const property of pattern.properties) {
81
+ if (property.type !== utils_1.AST_NODE_TYPES.Property ||
82
+ property.computed ||
83
+ property.key.type !== utils_1.AST_NODE_TYPES.Identifier) {
84
+ continue;
85
+ }
86
+ if (property.value.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
87
+ if (property.value.left === name) {
88
+ return { key: property.key.name, default: property.value.right };
89
+ }
90
+ continue;
91
+ }
92
+ if (property.value === name) {
93
+ return { key: property.key.name, default: null };
94
+ }
95
+ }
96
+ return null;
97
+ };
98
+ /**
99
+ * The object literal `node` evaluates to, following `const` aliases (`alias = widgets`), member
100
+ * access (`ns.widgets`) and destructuring (`const { inner } = ...`); `null` when it can't be traced.
101
+ *
102
+ * `path` holds the nodes currently being resolved, so only a real cycle (`const A = B; const B = A;`)
103
+ * is cut; a node reached again by another route is resolved normally.
104
+ */
105
+ const resolveObjectLiteral = (node, path) => {
106
+ const expression = unwrapExpression(node);
107
+ if (expression.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
108
+ return expression;
109
+ }
110
+ if (path.has(expression)) {
111
+ return null;
112
+ }
113
+ path.add(expression);
114
+ try {
115
+ switch (expression.type) {
116
+ case utils_1.AST_NODE_TYPES.MemberExpression: {
117
+ if (expression.computed || expression.property.type !== utils_1.AST_NODE_TYPES.Identifier) {
118
+ return null;
119
+ }
120
+ const value = findPropertyValue(expression.object, expression.property.name, path);
121
+ return value ? resolveObjectLiteral(value, path) : null;
122
+ }
123
+ case utils_1.AST_NODE_TYPES.Identifier: {
124
+ const definition = utils_1.ASTUtils.findVariable(context.sourceCode.getScope(expression), expression)?.defs[0];
125
+ if (definition?.type !== DefinitionType.Variable || definition.node.init === null) {
126
+ return null;
127
+ }
128
+ if (definition.node.id.type !== utils_1.AST_NODE_TYPES.ObjectPattern) {
129
+ return resolveObjectLiteral(definition.node.init, path);
130
+ }
131
+ const destructured = getDestructuredKey(definition.node.id, definition.name);
132
+ if (destructured === null) {
133
+ return null;
134
+ }
135
+ const value = findPropertyValue(definition.node.init, destructured.key, path);
136
+ if (value === undefined) {
137
+ return null;
138
+ }
139
+ const resolved = value ?? destructured.default;
140
+ return resolved ? resolveObjectLiteral(resolved, path) : null;
141
+ }
142
+ default:
143
+ return null;
144
+ }
145
+ }
146
+ finally {
147
+ path.delete(expression);
148
+ }
149
+ };
150
+ /**
151
+ * The node that provides `object[key]` when `object` traces to an object literal: the property
152
+ * value, or the source of an untraceable spread that may provide it. `null` when the literal has
153
+ * no such key; `undefined` when `object` isn't a traceable object literal.
154
+ *
155
+ * Properties are read last to first, so a later key overrides an earlier spread; a spread whose
156
+ * source is a literal without the key falls through to earlier properties. A literal already on
157
+ * `path` is a spread cycle (`const a = { ...b }; const b = { ...a };`) and provides nothing.
158
+ */
159
+ const findPropertyValue = (object, key, path) => {
160
+ const literal = resolveObjectLiteral(object, path);
161
+ if (literal === null) {
162
+ return undefined;
163
+ }
164
+ if (path.has(literal)) {
165
+ return null;
166
+ }
167
+ path.add(literal);
168
+ try {
169
+ for (const property of [...literal.properties].reverse()) {
170
+ if (property.type === utils_1.AST_NODE_TYPES.SpreadElement) {
171
+ const spreadValue = findPropertyValue(property.argument, key, path);
172
+ if (spreadValue === undefined) {
173
+ return property.argument;
174
+ }
175
+ if (spreadValue !== null) {
176
+ return spreadValue;
177
+ }
178
+ continue;
179
+ }
180
+ if (!property.computed && property.key.type === utils_1.AST_NODE_TYPES.Identifier && property.key.name === key) {
181
+ return property.value;
182
+ }
183
+ }
184
+ return null;
185
+ }
186
+ finally {
187
+ path.delete(literal);
188
+ }
189
+ };
190
+ /**
191
+ * Classifies `object[key]`; checks only the accessed property when `object` traces to an object
192
+ * literal, falling back to `defaultNode` (a destructuring default) when the literal lacks `key`.
193
+ *
194
+ * `key === null` means the accessed property can't be read syntactically — a computed member
195
+ * (`bag[k]`) or an unresolvable destructuring pattern. When `object` traces to an object literal,
196
+ * this is untraced (`null`) rather than classifying every sibling, which would flag e.g. an
197
+ * imported `widgets.Widget` merely because `widgets` also has a local `Demo`. When `object` isn't
198
+ * a traceable literal (e.g. a namespace import, `Ns.Widget`), `object` itself is classified.
199
+ */
200
+ const classifyProperty = (object, key, defaultNode, visited) => {
201
+ if (key === null) {
202
+ return resolveObjectLiteral(object, new Set()) === null ? classify(object, visited) : null;
203
+ }
204
+ const value = findPropertyValue(object, key, new Set());
205
+ if (value === undefined) {
206
+ return classify(object, visited);
207
+ }
208
+ if (value === null) {
209
+ return defaultNode ? classify(defaultNode, visited) : null;
210
+ }
211
+ return classify(value, visited);
212
+ };
213
+ /**
214
+ * Arguments that can't be the wrapped component: literal options and, after the first position,
215
+ * inline callbacks (`memo`'s comparator).
216
+ */
217
+ const isNonComponentArgument = (argument, index) => {
218
+ const expression = unwrapExpression(argument);
219
+ switch (expression.type) {
220
+ case utils_1.AST_NODE_TYPES.ObjectExpression:
221
+ case utils_1.AST_NODE_TYPES.ArrayExpression:
222
+ case utils_1.AST_NODE_TYPES.Literal:
223
+ case utils_1.AST_NODE_TYPES.TemplateLiteral:
224
+ return true;
225
+ default:
226
+ return index > 0 && isInlineComponent(expression);
227
+ }
228
+ };
229
+ const classifyDefinition = (def, visited) => {
230
+ switch (def.type) {
231
+ case DefinitionType.FunctionName:
232
+ case DefinitionType.ClassName:
233
+ return "local";
234
+ case DefinitionType.Variable:
235
+ if (def.node.init === null) {
236
+ return null;
237
+ }
238
+ if (def.node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
239
+ const destructured = getDestructuredKey(def.node.id, def.name);
240
+ return classifyProperty(def.node.init, destructured?.key ?? null, destructured?.default ?? null, visited);
241
+ }
242
+ return classify(def.node.init, visited);
243
+ case DefinitionType.ImportBinding:
244
+ if (def.parent.type !== utils_1.AST_NODE_TYPES.ImportDeclaration) {
245
+ return null;
246
+ }
247
+ return STORY_IMPORT_SOURCE_REGEX.test(def.parent.source.value) ? "local" : "external";
248
+ default:
249
+ return null;
250
+ }
251
+ };
252
+ /**
253
+ * `"local"` when `node` resolves to something declared in a story file, `"external"` when it
254
+ * resolves to an import from a non-story module, `null` when it can't be traced.
255
+ */
256
+ const classify = (node, visited) => {
257
+ const expression = unwrapExpression(node);
258
+ // Guards alias cycles such as `const A = B; const B = A;`.
259
+ if (visited.has(expression)) {
260
+ return null;
261
+ }
262
+ visited.add(expression);
263
+ if (isInlineComponent(expression)) {
264
+ return "local";
265
+ }
266
+ switch (expression.type) {
267
+ case utils_1.AST_NODE_TYPES.CallExpression: {
268
+ // The wrapped component is the first traceable argument that could be one; later arguments
269
+ // are its callbacks and options, so they can't outvote it (`memo(Local, isEqual)`,
270
+ // `memo(Widget, areEqual)`, `withTheme({ strict: true }, Local)`).
271
+ for (const [index, argument] of expression.arguments.entries()) {
272
+ if (isNonComponentArgument(argument, index)) {
273
+ continue;
274
+ }
275
+ const origin = classify(argument, visited);
276
+ if (origin !== null) {
277
+ return origin;
278
+ }
279
+ }
280
+ return null;
281
+ }
282
+ case utils_1.AST_NODE_TYPES.MemberExpression:
283
+ return classifyProperty(expression.object, !expression.computed && expression.property.type === utils_1.AST_NODE_TYPES.Identifier
284
+ ? expression.property.name
285
+ : null, null, visited);
286
+ case utils_1.AST_NODE_TYPES.ObjectExpression:
287
+ return expression.properties.some(property => property.type === utils_1.AST_NODE_TYPES.Property && classify(property.value, visited) === "local")
288
+ ? "local"
289
+ : null;
290
+ case utils_1.AST_NODE_TYPES.Identifier: {
291
+ const variable = utils_1.ASTUtils.findVariable(context.sourceCode.getScope(expression), expression);
292
+ const origins = variable?.defs.map(def => classifyDefinition(def, visited)) ?? [];
293
+ return origins.includes("local") ? "local" : (origins.find(origin => origin !== null) ?? null);
294
+ }
295
+ default:
296
+ return null;
297
+ }
298
+ };
299
+ const checkMetaObject = (object) => {
300
+ const component = getComponentValue(object);
301
+ if (component && classify(component, new Set()) === "local") {
302
+ context.report({ node: component, messageId: "localMetaComponent" });
303
+ }
304
+ };
305
+ return {
306
+ ExportDefaultDeclaration(node) {
307
+ const declaration = unwrapExpression(node.declaration);
308
+ if (declaration.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
309
+ checkMetaObject(declaration);
310
+ return;
311
+ }
312
+ if (declaration.type === utils_1.AST_NODE_TYPES.Identifier) {
313
+ const variable = utils_1.ASTUtils.findVariable(context.sourceCode.getScope(node), declaration);
314
+ const definition = variable?.defs[0];
315
+ if (definition?.node.type === utils_1.AST_NODE_TYPES.VariableDeclarator && definition.node.init) {
316
+ const initializer = unwrapExpression(definition.node.init);
317
+ if (initializer.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
318
+ checkMetaObject(initializer);
319
+ }
320
+ }
321
+ }
322
+ },
323
+ CallExpression(node) {
324
+ const { callee } = node;
325
+ if (callee.type !== utils_1.AST_NODE_TYPES.MemberExpression ||
326
+ callee.property.type !== utils_1.AST_NODE_TYPES.Identifier ||
327
+ callee.property.name !== "meta") {
328
+ return;
329
+ }
330
+ const [firstArgument] = node.arguments;
331
+ if (firstArgument?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
332
+ checkMetaObject(firstArgument);
333
+ }
334
+ },
335
+ };
336
+ };
337
+ exports.noLocalMetaComponent = createRule({
338
+ name: "no-local-meta-component",
339
+ meta: {
340
+ type: "problem",
341
+ docs: {
342
+ description: "Disallow a Storybook meta `component` that is declared inside a story file",
343
+ },
344
+ schema: [],
345
+ messages: {
346
+ localMetaComponent: `[no-local-meta-component] The meta \`component\` is declared in a story file. Storybook's docgen enricher skips \`*.stories.*\` files, so Controls loses the component's JSDoc prop descriptions, and "Show code" names an identifier that only exists inside the story. Either point \`component\` at the real component (specialise a generic with an instantiation expression, e.g. \`component: MultiSelectField<Option>\`), or move the documented demo into a sibling \`*.demo.tsx\` file and import it from there. See ${exports.NO_LOCAL_META_COMPONENT_DOCS_URL}.`,
347
+ },
348
+ },
349
+ defaultOptions: [],
350
+ create,
351
+ });
352
+ //# sourceMappingURL=no-local-meta-component.js.map