eslint-plugin-executable-stories-jest 2.1.2 → 2.1.3

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/dist/index.cjs CHANGED
@@ -26,9 +26,101 @@ __export(src_exports, {
26
26
  });
27
27
  module.exports = __toCommonJS(src_exports);
28
28
 
29
+ // src/rules/require-init-before-steps.ts
30
+ var STEP_NAMES = /* @__PURE__ */ new Set([
31
+ "given",
32
+ "when",
33
+ "then",
34
+ "and",
35
+ "but",
36
+ "arrange",
37
+ "act",
38
+ "assert",
39
+ "setup",
40
+ "context",
41
+ "execute",
42
+ "action",
43
+ "verify",
44
+ "fn",
45
+ "expect"
46
+ ]);
47
+ function isFunction(node) {
48
+ return node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration";
49
+ }
50
+ function isStoryInitCall(node) {
51
+ const { callee } = node;
52
+ if (callee.type !== "MemberExpression") return false;
53
+ const member = callee;
54
+ const { object, property } = member;
55
+ return object.type === "Identifier" && object.name === "story" && property.type === "Identifier" && property.name === "init";
56
+ }
57
+ function isStoryStepCall(node) {
58
+ const { callee } = node;
59
+ if (callee.type !== "MemberExpression") return false;
60
+ const member = callee;
61
+ const { object, property } = member;
62
+ return object.type === "Identifier" && object.name === "story" && property.type === "Identifier" && STEP_NAMES.has(property.name);
63
+ }
64
+ function getContainingFunction(node, context) {
65
+ const ancestors = context.sourceCode.getAncestors(node);
66
+ for (let i = ancestors.length - 1; i >= 0; i--) {
67
+ if (isFunction(ancestors[i])) {
68
+ return ancestors[i];
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+ var rule = {
74
+ meta: {
75
+ type: "problem",
76
+ docs: {
77
+ description: "Require story.init() to be called before any step markers (story.given/when/then/etc) in Jest.",
78
+ recommended: true
79
+ },
80
+ schema: [],
81
+ messages: {
82
+ requireInit: "story.init() must be called before using step markers like story.given(), story.when(), story.then()."
83
+ }
84
+ },
85
+ create(context) {
86
+ let hasRelevantImport = false;
87
+ const functionsWithInit = /* @__PURE__ */ new WeakSet();
88
+ const pendingStepCalls = [];
89
+ return {
90
+ ImportDeclaration(node) {
91
+ if (node.source.value === "executable-stories-jest") {
92
+ hasRelevantImport = true;
93
+ }
94
+ },
95
+ CallExpression(node) {
96
+ if (!hasRelevantImport) return;
97
+ if (isStoryInitCall(node)) {
98
+ const containingFunction = getContainingFunction(node, context);
99
+ if (containingFunction) {
100
+ functionsWithInit.add(containingFunction);
101
+ }
102
+ return;
103
+ }
104
+ if (isStoryStepCall(node)) {
105
+ const containingFunction = getContainingFunction(node, context);
106
+ pendingStepCalls.push({ node, containingFunction });
107
+ }
108
+ },
109
+ "Program:exit"() {
110
+ for (const { node, containingFunction } of pendingStepCalls) {
111
+ if (!containingFunction || !functionsWithInit.has(containingFunction)) {
112
+ context.report({ node, messageId: "requireInit" });
113
+ }
114
+ }
115
+ }
116
+ };
117
+ }
118
+ };
119
+ var require_init_before_steps_default = rule;
120
+
29
121
  // src/rules/require-story-context-for-steps.ts
30
122
  var V1_PACKAGE = "executable-stories-jest";
31
- var STEP_NAMES = /* @__PURE__ */ new Set([
123
+ var STEP_NAMES2 = /* @__PURE__ */ new Set([
32
124
  "given",
33
125
  "when",
34
126
  "then",
@@ -44,7 +136,7 @@ var STEP_NAMES = /* @__PURE__ */ new Set([
44
136
  "verify"
45
137
  ]);
46
138
  var STORY_MODIFIERS = /* @__PURE__ */ new Set(["skip", "only"]);
47
- function isFunction(node) {
139
+ function isFunction2(node) {
48
140
  return node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
49
141
  }
50
142
  function isFunctionNode(node) {
@@ -58,6 +150,11 @@ function isStoryCall(node) {
58
150
  return false;
59
151
  return callee.property.type === "Identifier" && STORY_MODIFIERS.has(callee.property.name);
60
152
  }
153
+ function isStoryInitCall2(node) {
154
+ const { callee } = node;
155
+ if (callee.type !== "MemberExpression") return false;
156
+ return callee.object.type === "Identifier" && callee.object.name === "story" && callee.property.type === "Identifier" && callee.property.name === "init";
157
+ }
61
158
  function isDocStoryCall(node) {
62
159
  const { callee } = node;
63
160
  if (callee.type !== "MemberExpression") return false;
@@ -65,16 +162,16 @@ function isDocStoryCall(node) {
65
162
  }
66
163
  function isStepCall(node) {
67
164
  const { callee } = node;
68
- if (callee.type === "Identifier") return STEP_NAMES.has(callee.name);
165
+ if (callee.type === "Identifier") return STEP_NAMES2.has(callee.name);
69
166
  if (callee.type !== "MemberExpression") return false;
70
167
  if (callee.object.type !== "Identifier") return false;
71
168
  if (callee.object.name !== "steps" && callee.object.name !== "step")
72
169
  return false;
73
- return callee.property.type === "Identifier" && STEP_NAMES.has(callee.property.name);
170
+ return callee.property.type === "Identifier" && STEP_NAMES2.has(callee.property.name);
74
171
  }
75
172
  function insideStoryCallback(node, context) {
76
173
  const ancestors = context.sourceCode.getAncestors(node);
77
- const functionAncestors = new Set(ancestors.filter(isFunction));
174
+ const functionAncestors = new Set(ancestors.filter(isFunction2));
78
175
  for (const ancestor of ancestors) {
79
176
  if (ancestor.type !== "CallExpression") continue;
80
177
  if (!isStoryCall(ancestor) && !isDocStoryCall(ancestor)) continue;
@@ -86,7 +183,7 @@ function insideStoryCallback(node, context) {
86
183
  }
87
184
  return false;
88
185
  }
89
- var rule = {
186
+ var rule2 = {
90
187
  meta: {
91
188
  type: "problem",
92
189
  docs: {
@@ -102,7 +199,17 @@ var rule = {
102
199
  let hasV1Import = false;
103
200
  const namedFunctions = /* @__PURE__ */ new Map();
104
201
  const storyCallbackNames = /* @__PURE__ */ new Set();
202
+ const functionsWithInit = /* @__PURE__ */ new WeakSet();
105
203
  const pendingStepCalls = [];
204
+ function getContainingFunction2(node) {
205
+ const ancestors = context.sourceCode.getAncestors(node);
206
+ for (let i = ancestors.length - 1; i >= 0; i--) {
207
+ if (isFunctionNode(ancestors[i]) || isFunction2(ancestors[i])) {
208
+ return ancestors[i];
209
+ }
210
+ }
211
+ return null;
212
+ }
106
213
  function getContainingFunctionName(node) {
107
214
  const ancestors = context.sourceCode.getAncestors(node);
108
215
  for (let i = ancestors.length - 1; i >= 0; i--) {
@@ -147,6 +254,13 @@ var rule = {
147
254
  },
148
255
  CallExpression(node) {
149
256
  if (!hasV1Import) return;
257
+ if (isStoryInitCall2(node)) {
258
+ const containingFunction2 = getContainingFunction2(node);
259
+ if (containingFunction2) {
260
+ functionsWithInit.add(containingFunction2);
261
+ }
262
+ return;
263
+ }
150
264
  if (isStoryCall(node) || isDocStoryCall(node)) {
151
265
  for (const arg of node.arguments) {
152
266
  if (arg.type === "Identifier") {
@@ -160,21 +274,25 @@ var rule = {
160
274
  }
161
275
  if (!isStepCall(node)) return;
162
276
  if (insideStoryCallback(node, context)) return;
277
+ const containingFunction = getContainingFunction2(node);
163
278
  const containingFunctionName = getContainingFunctionName(node);
164
- pendingStepCalls.push({ node, containingFunctionName });
279
+ pendingStepCalls.push({ node, containingFunction, containingFunctionName });
165
280
  },
166
281
  "Program:exit"() {
167
- for (const { node, containingFunctionName } of pendingStepCalls) {
282
+ for (const { node, containingFunction, containingFunctionName } of pendingStepCalls) {
168
283
  if (containingFunctionName && storyCallbackNames.has(containingFunctionName)) {
169
284
  continue;
170
285
  }
286
+ if (containingFunction && functionsWithInit.has(containingFunction)) {
287
+ continue;
288
+ }
171
289
  context.report({ node, messageId: "requireStory" });
172
290
  }
173
291
  }
174
292
  };
175
293
  }
176
294
  };
177
- var require_story_context_for_steps_default = rule;
295
+ var require_story_context_for_steps_default = rule2;
178
296
 
179
297
  // src/rules/require-test-context-for-doc-story.ts
180
298
  var TEST_MODIFIERS = /* @__PURE__ */ new Set([
@@ -184,7 +302,7 @@ var TEST_MODIFIERS = /* @__PURE__ */ new Set([
184
302
  "concurrent",
185
303
  "failing"
186
304
  ]);
187
- function isFunction2(node) {
305
+ function isFunction3(node) {
188
306
  return node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
189
307
  }
190
308
  function isFunctionNode2(node) {
@@ -220,7 +338,7 @@ function isTestCallExpression(node) {
220
338
  }
221
339
  function insideTestCallback(node, context) {
222
340
  const ancestors = context.sourceCode.getAncestors(node);
223
- const functionAncestors = new Set(ancestors.filter(isFunction2));
341
+ const functionAncestors = new Set(ancestors.filter(isFunction3));
224
342
  for (const ancestor of ancestors) {
225
343
  if (ancestor.type !== "CallExpression") continue;
226
344
  if (!isTestCallExpression(ancestor)) continue;
@@ -232,7 +350,7 @@ function insideTestCallback(node, context) {
232
350
  }
233
351
  return false;
234
352
  }
235
- var rule2 = {
353
+ var rule3 = {
236
354
  meta: {
237
355
  type: "problem",
238
356
  docs: {
@@ -325,10 +443,11 @@ var rule2 = {
325
443
  };
326
444
  }
327
445
  };
328
- var require_test_context_for_doc_story_default = rule2;
446
+ var require_test_context_for_doc_story_default = rule3;
329
447
 
330
448
  // src/index.ts
331
449
  var rules = {
450
+ "require-init-before-steps": require_init_before_steps_default,
332
451
  "require-story-context-for-steps": require_story_context_for_steps_default,
333
452
  "require-test-context-for-doc-story": require_test_context_for_doc_story_default
334
453
  };
@@ -339,6 +458,7 @@ var configs = {
339
458
  "executable-stories-jest": { rules }
340
459
  },
341
460
  rules: {
461
+ "executable-stories-jest/require-init-before-steps": "error",
342
462
  "executable-stories-jest/require-story-context-for-steps": "error",
343
463
  "executable-stories-jest/require-test-context-for-doc-story": "error"
344
464
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/rules/require-story-context-for-steps.ts","../src/rules/require-test-context-for-doc-story.ts"],"sourcesContent":["import type { ESLint, Linter } from 'eslint';\nimport requireStoryContextForSteps from './rules/require-story-context-for-steps.js';\nimport requireTestContextForDocStory from './rules/require-test-context-for-doc-story.js';\n\nconst rules = {\n 'require-story-context-for-steps': requireStoryContextForSteps,\n 'require-test-context-for-doc-story': requireTestContextForDocStory,\n};\n\nconst configs: Record<string, Linter.Config[]> = {\n recommended: [\n {\n plugins: {\n 'executable-stories-jest': { rules },\n },\n rules: {\n 'executable-stories-jest/require-story-context-for-steps': 'error',\n 'executable-stories-jest/require-test-context-for-doc-story': 'error',\n },\n },\n ],\n};\n\nconst plugin: ESLint.Plugin = {\n meta: {\n name: 'eslint-plugin-executable-stories-jest',\n version: '0.1.0',\n },\n rules,\n configs,\n};\n\nexport default plugin;\nexport { rules, configs };\n","import type { Rule } from 'eslint';\nimport type {\n ArrowFunctionExpression,\n CallExpression,\n FunctionDeclaration,\n FunctionExpression,\n Node,\n Property,\n VariableDeclarator,\n} from 'estree';\n\n/** Current API uses story.given() etc. inside test(); no top-level given/when/then. */\nconst V1_PACKAGE = 'executable-stories-jest';\n\nconst STEP_NAMES = new Set([\n 'given',\n 'when',\n 'then',\n 'and',\n 'but',\n 'arrange',\n 'act',\n 'assert',\n 'setup',\n 'context',\n 'execute',\n 'action',\n 'verify',\n]);\n\nconst STORY_MODIFIERS = new Set(['skip', 'only']);\n\nfunction isFunction(node: Node): boolean {\n return (\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\nfunction isFunctionNode(\n node: Node,\n): node is FunctionDeclaration | ArrowFunctionExpression | FunctionExpression {\n return (\n node.type === 'FunctionDeclaration' ||\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\nfunction isStoryCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type === 'Identifier') return callee.name === 'story';\n if (callee.type !== 'MemberExpression') return false;\n if (callee.object.type !== 'Identifier' || callee.object.name !== 'story')\n return false;\n return (\n callee.property.type === 'Identifier' &&\n STORY_MODIFIERS.has(callee.property.name)\n );\n}\n\nfunction isDocStoryCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type !== 'MemberExpression') return false;\n return (\n callee.object.type === 'Identifier' &&\n callee.object.name === 'doc' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'story'\n );\n}\n\nfunction isStepCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type === 'Identifier') return STEP_NAMES.has(callee.name);\n if (callee.type !== 'MemberExpression') return false;\n if (callee.object.type !== 'Identifier') return false;\n if (callee.object.name !== 'steps' && callee.object.name !== 'step')\n return false;\n return (\n callee.property.type === 'Identifier' &&\n STEP_NAMES.has(callee.property.name)\n );\n}\n\nfunction insideStoryCallback(\n node: CallExpression,\n context: Rule.RuleContext,\n): boolean {\n const ancestors = context.sourceCode.getAncestors(node);\n const functionAncestors = new Set(ancestors.filter(isFunction));\n\n for (const ancestor of ancestors) {\n if (ancestor.type !== 'CallExpression') continue;\n if (!isStoryCall(ancestor) && !isDocStoryCall(ancestor)) continue;\n for (const arg of ancestor.arguments) {\n if (arg && typeof arg === 'object' && functionAncestors.has(arg)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require step functions (given/when/then/and/but and aliases) to be called inside story() or doc.story(..., callback).',\n recommended: true,\n },\n schema: [],\n messages: {\n requireStory:\n 'Step functions must be called inside story(...) (or doc.story(..., callback)).',\n },\n },\n create(context) {\n let hasV1Import = false;\n const namedFunctions = new Map<string, Node>();\n const storyCallbackNames = new Set<string>();\n const pendingStepCalls: Array<{\n node: CallExpression;\n containingFunctionName: string | null;\n }> = [];\n\n function getContainingFunctionName(node: CallExpression): string | null {\n const ancestors = context.sourceCode.getAncestors(node);\n for (let i = ancestors.length - 1; i >= 0; i--) {\n const ancestor = ancestors[i];\n if (ancestor.type === 'FunctionDeclaration' && ancestor.id) {\n return ancestor.id.name;\n }\n if (ancestor.type === 'VariableDeclarator') {\n const declarator = ancestor as VariableDeclarator;\n if (\n declarator.id.type === 'Identifier' &&\n declarator.init &&\n isFunctionNode(declarator.init)\n ) {\n return declarator.id.name;\n }\n }\n // Check for object method definitions\n if (ancestor.type === 'Property') {\n const prop = ancestor as Property;\n if (\n prop.key.type === 'Identifier' &&\n prop.value &&\n isFunctionNode(prop.value)\n ) {\n return prop.key.name;\n }\n // Shorthand method syntax: { define() {} }\n if (prop.key.type === 'Identifier' && prop.method) {\n return prop.key.name;\n }\n }\n }\n return null;\n }\n\n return {\n ImportDeclaration(node) {\n const pkg = node.source.value;\n if (typeof pkg === 'string' && pkg === V1_PACKAGE) {\n hasV1Import = true;\n }\n },\n FunctionDeclaration(node: FunctionDeclaration) {\n if (node.id) {\n namedFunctions.set(node.id.name, node);\n }\n },\n VariableDeclarator(node: VariableDeclarator) {\n if (\n node.id.type === 'Identifier' &&\n node.init &&\n isFunctionNode(node.init)\n ) {\n namedFunctions.set(node.id.name, node.init);\n }\n },\n CallExpression(node: CallExpression) {\n if (!hasV1Import) return;\n\n if (isStoryCall(node) || isDocStoryCall(node)) {\n for (const arg of node.arguments) {\n if (arg.type === 'Identifier') {\n storyCallbackNames.add(arg.name);\n }\n // Handle member expression callbacks like handlers.define\n if (\n arg.type === 'MemberExpression' &&\n arg.property.type === 'Identifier'\n ) {\n storyCallbackNames.add(arg.property.name);\n }\n }\n return;\n }\n\n if (!isStepCall(node)) return;\n if (insideStoryCallback(node, context)) return;\n\n const containingFunctionName = getContainingFunctionName(node);\n pendingStepCalls.push({ node, containingFunctionName });\n },\n 'Program:exit'() {\n for (const { node, containingFunctionName } of pendingStepCalls) {\n if (\n containingFunctionName &&\n storyCallbackNames.has(containingFunctionName)\n ) {\n continue;\n }\n context.report({ node, messageId: 'requireStory' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport type {\n ArrowFunctionExpression,\n CallExpression,\n FunctionDeclaration,\n FunctionExpression,\n Node,\n Property,\n VariableDeclarator,\n} from 'estree';\n\nconst TEST_MODIFIERS = new Set([\n 'only',\n 'skip',\n 'todo',\n 'concurrent',\n 'failing',\n]);\n\nfunction isFunction(node: Node): boolean {\n return (\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\nfunction isFunctionNode(\n node: Node,\n): node is FunctionDeclaration | ArrowFunctionExpression | FunctionExpression {\n return (\n node.type === 'FunctionDeclaration' ||\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\n/** Legacy doc.story() is not used; current API uses story.init() inside it/test. */\nconst V1_PACKAGE = 'executable-stories-jest';\n\nfunction isDocStoryCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type !== 'MemberExpression') return false;\n return (\n callee.object.type === 'Identifier' &&\n callee.object.name === 'doc' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'story'\n );\n}\n\nfunction isTestCallExpression(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type === 'Identifier') {\n return callee.name === 'test' || callee.name === 'it';\n }\n if (callee.type === 'MemberExpression') {\n if (callee.object.type !== 'Identifier') return false;\n if (callee.object.name !== 'test' && callee.object.name !== 'it')\n return false;\n if (callee.property.type !== 'Identifier') return false;\n return TEST_MODIFIERS.has(callee.property.name);\n }\n if (callee.type === 'CallExpression') {\n const inner = callee.callee;\n if (inner.type !== 'MemberExpression') return false;\n if (inner.object.type !== 'Identifier') return false;\n if (inner.object.name !== 'test' && inner.object.name !== 'it')\n return false;\n return (\n inner.property.type === 'Identifier' && inner.property.name === 'each'\n );\n }\n return false;\n}\n\nfunction insideTestCallback(\n node: CallExpression,\n context: Rule.RuleContext,\n): boolean {\n const ancestors = context.sourceCode.getAncestors(node);\n const functionAncestors = new Set(ancestors.filter(isFunction));\n\n for (const ancestor of ancestors) {\n if (ancestor.type !== 'CallExpression') continue;\n if (!isTestCallExpression(ancestor)) continue;\n for (const arg of ancestor.arguments) {\n if (arg && typeof arg === 'object' && functionAncestors.has(arg)) {\n return true;\n }\n }\n }\n return false;\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require doc.story(title) to be called inside a test/it callback in Jest.',\n recommended: true,\n },\n schema: [],\n messages: {\n requireTest:\n \"doc.story(title) must be called inside a test/it callback (e.g. it('...', () => { doc.story('Title'); ... })).\",\n requireTitle: 'doc.story(title) requires a title argument.',\n },\n },\n create(context) {\n let hasV1Import = false;\n const namedFunctions = new Map<string, Node>();\n const testCallbackNames = new Set<string>();\n const pendingDocStoryCalls: Array<{\n node: CallExpression;\n containingFunctionName: string | null;\n }> = [];\n\n function getContainingFunctionName(node: CallExpression): string | null {\n const ancestors = context.sourceCode.getAncestors(node);\n for (let i = ancestors.length - 1; i >= 0; i--) {\n const ancestor = ancestors[i];\n if (ancestor.type === 'FunctionDeclaration' && ancestor.id) {\n return ancestor.id.name;\n }\n if (ancestor.type === 'VariableDeclarator') {\n const declarator = ancestor as VariableDeclarator;\n if (\n declarator.id.type === 'Identifier' &&\n declarator.init &&\n isFunctionNode(declarator.init)\n ) {\n return declarator.id.name;\n }\n }\n // Check for object method definitions\n if (ancestor.type === 'Property') {\n const prop = ancestor as Property;\n if (\n prop.key.type === 'Identifier' &&\n prop.value &&\n isFunctionNode(prop.value)\n ) {\n return prop.key.name;\n }\n // Shorthand method syntax: { run() {} }\n if (prop.key.type === 'Identifier' && prop.method) {\n return prop.key.name;\n }\n }\n }\n return null;\n }\n\n return {\n ImportDeclaration(node) {\n if (node.source.value === V1_PACKAGE) {\n hasV1Import = true;\n }\n },\n FunctionDeclaration(node: FunctionDeclaration) {\n if (node.id) {\n namedFunctions.set(node.id.name, node);\n }\n },\n VariableDeclarator(node: VariableDeclarator) {\n if (\n node.id.type === 'Identifier' &&\n node.init &&\n isFunctionNode(node.init)\n ) {\n namedFunctions.set(node.id.name, node.init);\n }\n },\n CallExpression(node: CallExpression) {\n if (!hasV1Import) return;\n\n if (isTestCallExpression(node)) {\n for (const arg of node.arguments) {\n if (arg.type === 'Identifier') {\n testCallbackNames.add(arg.name);\n }\n // Handle member expression callbacks like handlers.run\n if (\n arg.type === 'MemberExpression' &&\n arg.property.type === 'Identifier'\n ) {\n testCallbackNames.add(arg.property.name);\n }\n }\n return;\n }\n\n if (!isDocStoryCall(node)) return;\n\n if (node.arguments.length === 0) {\n context.report({ node, messageId: 'requireTitle' });\n return;\n }\n if (node.arguments.length >= 2) return;\n\n if (insideTestCallback(node, context)) return;\n\n const containingFunctionName = getContainingFunctionName(node);\n pendingDocStoryCalls.push({ node, containingFunctionName });\n },\n 'Program:exit'() {\n for (const { node, containingFunctionName } of pendingDocStoryCalls) {\n if (\n containingFunctionName &&\n testCallbackNames.has(containingFunctionName)\n ) {\n continue;\n }\n context.report({ node, messageId: 'requireTest' });\n }\n },\n };\n },\n};\n\nexport default rule;\n"],"mappings":"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","names":["isFunction","isFunctionNode","V1_PACKAGE","isDocStoryCall","rule"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/rules/require-init-before-steps.ts","../src/rules/require-story-context-for-steps.ts","../src/rules/require-test-context-for-doc-story.ts"],"sourcesContent":["import type { ESLint, Linter } from 'eslint';\nimport requireInitBeforeSteps from './rules/require-init-before-steps.js';\nimport requireStoryContextForSteps from './rules/require-story-context-for-steps.js';\nimport requireTestContextForDocStory from './rules/require-test-context-for-doc-story.js';\n\nconst rules = {\n 'require-init-before-steps': requireInitBeforeSteps,\n 'require-story-context-for-steps': requireStoryContextForSteps,\n 'require-test-context-for-doc-story': requireTestContextForDocStory,\n};\n\nconst configs: Record<string, Linter.Config[]> = {\n recommended: [\n {\n plugins: {\n 'executable-stories-jest': { rules },\n },\n rules: {\n 'executable-stories-jest/require-init-before-steps': 'error',\n 'executable-stories-jest/require-story-context-for-steps': 'error',\n 'executable-stories-jest/require-test-context-for-doc-story': 'error',\n },\n },\n ],\n};\n\nconst plugin: ESLint.Plugin = {\n meta: {\n name: 'eslint-plugin-executable-stories-jest',\n version: '0.1.0',\n },\n rules,\n configs,\n};\n\nexport default plugin;\nexport { rules, configs };\n","import type { Rule } from 'eslint';\nimport type { CallExpression, MemberExpression, Node } from 'estree';\n\n/**\n * Rule: require-init-before-steps\n *\n * Ensures story.init() is called before any step markers (story.given/when/then/etc).\n * This is a best-effort static check - it flags step markers that appear before any story.init()\n * call in the same function scope.\n *\n * BAD:\n * story.given('something'); // No story.init() before this\n *\n * GOOD:\n * story.init();\n * story.given('something');\n */\n\nconst STEP_NAMES = new Set([\n 'given',\n 'when',\n 'then',\n 'and',\n 'but',\n 'arrange',\n 'act',\n 'assert',\n 'setup',\n 'context',\n 'execute',\n 'action',\n 'verify',\n 'fn',\n 'expect',\n]);\n\nfunction isFunction(node: Node): boolean {\n return (\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression' ||\n node.type === 'FunctionDeclaration'\n );\n}\n\nfunction isStoryInitCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type !== 'MemberExpression') return false;\n const member = callee as MemberExpression;\n const { object, property } = member;\n return (\n object.type === 'Identifier' &&\n object.name === 'story' &&\n property.type === 'Identifier' &&\n property.name === 'init'\n );\n}\n\nfunction isStoryStepCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type !== 'MemberExpression') return false;\n const member = callee as MemberExpression;\n const { object, property } = member;\n return (\n object.type === 'Identifier' &&\n object.name === 'story' &&\n property.type === 'Identifier' &&\n STEP_NAMES.has(property.name)\n );\n}\n\nfunction getContainingFunction(\n node: Node,\n context: Rule.RuleContext,\n): Node | null {\n const ancestors = context.sourceCode.getAncestors(node);\n for (let i = ancestors.length - 1; i >= 0; i--) {\n if (isFunction(ancestors[i])) {\n return ancestors[i];\n }\n }\n return null;\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require story.init() to be called before any step markers (story.given/when/then/etc) in Jest.',\n recommended: true,\n },\n schema: [],\n messages: {\n requireInit:\n 'story.init() must be called before using step markers like story.given(), story.when(), story.then().',\n },\n },\n create(context) {\n let hasRelevantImport = false;\n // Track functions that have story.init() calls\n const functionsWithInit = new WeakSet<Node>();\n // Track step calls and their containing functions for later verification\n const pendingStepCalls: Array<{\n node: CallExpression;\n containingFunction: Node | null;\n }> = [];\n\n return {\n ImportDeclaration(node) {\n if (node.source.value === 'executable-stories-jest') {\n hasRelevantImport = true;\n }\n },\n CallExpression(node: CallExpression) {\n if (!hasRelevantImport) return;\n\n if (isStoryInitCall(node)) {\n const containingFunction = getContainingFunction(node, context);\n if (containingFunction) {\n functionsWithInit.add(containingFunction);\n }\n return;\n }\n\n if (isStoryStepCall(node)) {\n const containingFunction = getContainingFunction(node, context);\n pendingStepCalls.push({ node, containingFunction });\n }\n },\n 'Program:exit'() {\n for (const { node, containingFunction } of pendingStepCalls) {\n // If no containing function, or the function doesn't have story.init(), report\n if (\n !containingFunction ||\n !functionsWithInit.has(containingFunction)\n ) {\n context.report({ node, messageId: 'requireInit' });\n }\n }\n },\n };\n },\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport type {\n ArrowFunctionExpression,\n CallExpression,\n FunctionDeclaration,\n FunctionExpression,\n Node,\n Property,\n VariableDeclarator,\n} from 'estree';\n\n/** Current API uses story.given() etc. inside test(); no top-level given/when/then. */\nconst V1_PACKAGE = 'executable-stories-jest';\n\nconst STEP_NAMES = new Set([\n 'given',\n 'when',\n 'then',\n 'and',\n 'but',\n 'arrange',\n 'act',\n 'assert',\n 'setup',\n 'context',\n 'execute',\n 'action',\n 'verify',\n]);\n\nconst STORY_MODIFIERS = new Set(['skip', 'only']);\n\nfunction isFunction(node: Node): boolean {\n return (\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\nfunction isFunctionNode(\n node: Node,\n): node is FunctionDeclaration | ArrowFunctionExpression | FunctionExpression {\n return (\n node.type === 'FunctionDeclaration' ||\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\nfunction isStoryCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type === 'Identifier') return callee.name === 'story';\n if (callee.type !== 'MemberExpression') return false;\n if (callee.object.type !== 'Identifier' || callee.object.name !== 'story')\n return false;\n return (\n callee.property.type === 'Identifier' &&\n STORY_MODIFIERS.has(callee.property.name)\n );\n}\n\nfunction isStoryInitCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type !== 'MemberExpression') return false;\n return (\n callee.object.type === 'Identifier' &&\n callee.object.name === 'story' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'init'\n );\n}\n\nfunction isDocStoryCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type !== 'MemberExpression') return false;\n return (\n callee.object.type === 'Identifier' &&\n callee.object.name === 'doc' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'story'\n );\n}\n\nfunction isStepCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type === 'Identifier') return STEP_NAMES.has(callee.name);\n if (callee.type !== 'MemberExpression') return false;\n if (callee.object.type !== 'Identifier') return false;\n if (callee.object.name !== 'steps' && callee.object.name !== 'step')\n return false;\n return (\n callee.property.type === 'Identifier' &&\n STEP_NAMES.has(callee.property.name)\n );\n}\n\nfunction insideStoryCallback(\n node: CallExpression,\n context: Rule.RuleContext,\n): boolean {\n const ancestors = context.sourceCode.getAncestors(node);\n const functionAncestors = new Set(ancestors.filter(isFunction));\n\n for (const ancestor of ancestors) {\n if (ancestor.type !== 'CallExpression') continue;\n if (!isStoryCall(ancestor) && !isDocStoryCall(ancestor)) continue;\n for (const arg of ancestor.arguments) {\n if (arg && typeof arg === 'object' && functionAncestors.has(arg)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require step functions (given/when/then/and/but and aliases) to be called inside story() or doc.story(..., callback).',\n recommended: true,\n },\n schema: [],\n messages: {\n requireStory:\n 'Step functions must be called inside story(...) (or doc.story(..., callback)).',\n },\n },\n create(context) {\n let hasV1Import = false;\n const namedFunctions = new Map<string, Node>();\n const storyCallbackNames = new Set<string>();\n // Track functions that contain story.init() calls\n const functionsWithInit = new WeakSet<Node>();\n const pendingStepCalls: Array<{\n node: CallExpression;\n containingFunction: Node | null;\n containingFunctionName: string | null;\n }> = [];\n\n function getContainingFunction(node: CallExpression): Node | null {\n const ancestors = context.sourceCode.getAncestors(node);\n for (let i = ancestors.length - 1; i >= 0; i--) {\n if (isFunctionNode(ancestors[i]) || isFunction(ancestors[i])) {\n return ancestors[i];\n }\n }\n return null;\n }\n\n function getContainingFunctionName(node: CallExpression): string | null {\n const ancestors = context.sourceCode.getAncestors(node);\n for (let i = ancestors.length - 1; i >= 0; i--) {\n const ancestor = ancestors[i];\n if (ancestor.type === 'FunctionDeclaration' && ancestor.id) {\n return ancestor.id.name;\n }\n if (ancestor.type === 'VariableDeclarator') {\n const declarator = ancestor as VariableDeclarator;\n if (\n declarator.id.type === 'Identifier' &&\n declarator.init &&\n isFunctionNode(declarator.init)\n ) {\n return declarator.id.name;\n }\n }\n // Check for object method definitions\n if (ancestor.type === 'Property') {\n const prop = ancestor as Property;\n if (\n prop.key.type === 'Identifier' &&\n prop.value &&\n isFunctionNode(prop.value)\n ) {\n return prop.key.name;\n }\n // Shorthand method syntax: { define() {} }\n if (prop.key.type === 'Identifier' && prop.method) {\n return prop.key.name;\n }\n }\n }\n return null;\n }\n\n return {\n ImportDeclaration(node) {\n const pkg = node.source.value;\n if (typeof pkg === 'string' && pkg === V1_PACKAGE) {\n hasV1Import = true;\n }\n },\n FunctionDeclaration(node: FunctionDeclaration) {\n if (node.id) {\n namedFunctions.set(node.id.name, node);\n }\n },\n VariableDeclarator(node: VariableDeclarator) {\n if (\n node.id.type === 'Identifier' &&\n node.init &&\n isFunctionNode(node.init)\n ) {\n namedFunctions.set(node.id.name, node.init);\n }\n },\n CallExpression(node: CallExpression) {\n if (!hasV1Import) return;\n\n // Track story.init() calls and their containing functions\n if (isStoryInitCall(node)) {\n const containingFunction = getContainingFunction(node);\n if (containingFunction) {\n functionsWithInit.add(containingFunction);\n }\n return;\n }\n\n if (isStoryCall(node) || isDocStoryCall(node)) {\n for (const arg of node.arguments) {\n if (arg.type === 'Identifier') {\n storyCallbackNames.add(arg.name);\n }\n // Handle member expression callbacks like handlers.define\n if (\n arg.type === 'MemberExpression' &&\n arg.property.type === 'Identifier'\n ) {\n storyCallbackNames.add(arg.property.name);\n }\n }\n return;\n }\n\n if (!isStepCall(node)) return;\n if (insideStoryCallback(node, context)) return;\n\n const containingFunction = getContainingFunction(node);\n const containingFunctionName = getContainingFunctionName(node);\n pendingStepCalls.push({ node, containingFunction, containingFunctionName });\n },\n 'Program:exit'() {\n for (const { node, containingFunction, containingFunctionName } of pendingStepCalls) {\n // Allow if inside a named function passed to story()/doc.story()\n if (\n containingFunctionName &&\n storyCallbackNames.has(containingFunctionName)\n ) {\n continue;\n }\n // Allow if inside a function that has story.init()\n if (containingFunction && functionsWithInit.has(containingFunction)) {\n continue;\n }\n context.report({ node, messageId: 'requireStory' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport type {\n ArrowFunctionExpression,\n CallExpression,\n FunctionDeclaration,\n FunctionExpression,\n Node,\n Property,\n VariableDeclarator,\n} from 'estree';\n\nconst TEST_MODIFIERS = new Set([\n 'only',\n 'skip',\n 'todo',\n 'concurrent',\n 'failing',\n]);\n\nfunction isFunction(node: Node): boolean {\n return (\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\nfunction isFunctionNode(\n node: Node,\n): node is FunctionDeclaration | ArrowFunctionExpression | FunctionExpression {\n return (\n node.type === 'FunctionDeclaration' ||\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\n/** Legacy doc.story() is not used; current API uses story.init() inside it/test. */\nconst V1_PACKAGE = 'executable-stories-jest';\n\nfunction isDocStoryCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type !== 'MemberExpression') return false;\n return (\n callee.object.type === 'Identifier' &&\n callee.object.name === 'doc' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'story'\n );\n}\n\nfunction isTestCallExpression(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type === 'Identifier') {\n return callee.name === 'test' || callee.name === 'it';\n }\n if (callee.type === 'MemberExpression') {\n if (callee.object.type !== 'Identifier') return false;\n if (callee.object.name !== 'test' && callee.object.name !== 'it')\n return false;\n if (callee.property.type !== 'Identifier') return false;\n return TEST_MODIFIERS.has(callee.property.name);\n }\n if (callee.type === 'CallExpression') {\n const inner = callee.callee;\n if (inner.type !== 'MemberExpression') return false;\n if (inner.object.type !== 'Identifier') return false;\n if (inner.object.name !== 'test' && inner.object.name !== 'it')\n return false;\n return (\n inner.property.type === 'Identifier' && inner.property.name === 'each'\n );\n }\n return false;\n}\n\nfunction insideTestCallback(\n node: CallExpression,\n context: Rule.RuleContext,\n): boolean {\n const ancestors = context.sourceCode.getAncestors(node);\n const functionAncestors = new Set(ancestors.filter(isFunction));\n\n for (const ancestor of ancestors) {\n if (ancestor.type !== 'CallExpression') continue;\n if (!isTestCallExpression(ancestor)) continue;\n for (const arg of ancestor.arguments) {\n if (arg && typeof arg === 'object' && functionAncestors.has(arg)) {\n return true;\n }\n }\n }\n return false;\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require doc.story(title) to be called inside a test/it callback in Jest.',\n recommended: true,\n },\n schema: [],\n messages: {\n requireTest:\n \"doc.story(title) must be called inside a test/it callback (e.g. it('...', () => { doc.story('Title'); ... })).\",\n requireTitle: 'doc.story(title) requires a title argument.',\n },\n },\n create(context) {\n let hasV1Import = false;\n const namedFunctions = new Map<string, Node>();\n const testCallbackNames = new Set<string>();\n const pendingDocStoryCalls: Array<{\n node: CallExpression;\n containingFunctionName: string | null;\n }> = [];\n\n function getContainingFunctionName(node: CallExpression): string | null {\n const ancestors = context.sourceCode.getAncestors(node);\n for (let i = ancestors.length - 1; i >= 0; i--) {\n const ancestor = ancestors[i];\n if (ancestor.type === 'FunctionDeclaration' && ancestor.id) {\n return ancestor.id.name;\n }\n if (ancestor.type === 'VariableDeclarator') {\n const declarator = ancestor as VariableDeclarator;\n if (\n declarator.id.type === 'Identifier' &&\n declarator.init &&\n isFunctionNode(declarator.init)\n ) {\n return declarator.id.name;\n }\n }\n // Check for object method definitions\n if (ancestor.type === 'Property') {\n const prop = ancestor as Property;\n if (\n prop.key.type === 'Identifier' &&\n prop.value &&\n isFunctionNode(prop.value)\n ) {\n return prop.key.name;\n }\n // Shorthand method syntax: { run() {} }\n if (prop.key.type === 'Identifier' && prop.method) {\n return prop.key.name;\n }\n }\n }\n return null;\n }\n\n return {\n ImportDeclaration(node) {\n if (node.source.value === V1_PACKAGE) {\n hasV1Import = true;\n }\n },\n FunctionDeclaration(node: FunctionDeclaration) {\n if (node.id) {\n namedFunctions.set(node.id.name, node);\n }\n },\n VariableDeclarator(node: VariableDeclarator) {\n if (\n node.id.type === 'Identifier' &&\n node.init &&\n isFunctionNode(node.init)\n ) {\n namedFunctions.set(node.id.name, node.init);\n }\n },\n CallExpression(node: CallExpression) {\n if (!hasV1Import) return;\n\n if (isTestCallExpression(node)) {\n for (const arg of node.arguments) {\n if (arg.type === 'Identifier') {\n testCallbackNames.add(arg.name);\n }\n // Handle member expression callbacks like handlers.run\n if (\n arg.type === 'MemberExpression' &&\n arg.property.type === 'Identifier'\n ) {\n testCallbackNames.add(arg.property.name);\n }\n }\n return;\n }\n\n if (!isDocStoryCall(node)) return;\n\n if (node.arguments.length === 0) {\n context.report({ node, messageId: 'requireTitle' });\n return;\n }\n if (node.arguments.length >= 2) return;\n\n if (insideTestCallback(node, context)) return;\n\n const containingFunctionName = getContainingFunctionName(node);\n pendingDocStoryCalls.push({ node, containingFunctionName });\n },\n 'Program:exit'() {\n for (const { node, containingFunctionName } of pendingDocStoryCalls) {\n if (\n containingFunctionName &&\n testCallbackNames.has(containingFunctionName)\n ) {\n continue;\n }\n context.report({ node, messageId: 'requireTest' });\n }\n },\n };\n },\n};\n\nexport default rule;\n"],"mappings":"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","names":["STEP_NAMES","isFunction","isStoryInitCall","rule","getContainingFunction","containingFunction","isFunction","isFunctionNode","V1_PACKAGE","isDocStoryCall","rule"]}
package/dist/index.d.cts CHANGED
@@ -2,6 +2,7 @@ import * as eslint from 'eslint';
2
2
  import { Linter, ESLint } from 'eslint';
3
3
 
4
4
  declare const rules: {
5
+ 'require-init-before-steps': eslint.Rule.RuleModule;
5
6
  'require-story-context-for-steps': eslint.Rule.RuleModule;
6
7
  'require-test-context-for-doc-story': eslint.Rule.RuleModule;
7
8
  };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import * as eslint from 'eslint';
2
2
  import { Linter, ESLint } from 'eslint';
3
3
 
4
4
  declare const rules: {
5
+ 'require-init-before-steps': eslint.Rule.RuleModule;
5
6
  'require-story-context-for-steps': eslint.Rule.RuleModule;
6
7
  'require-test-context-for-doc-story': eslint.Rule.RuleModule;
7
8
  };
package/dist/index.js CHANGED
@@ -1,6 +1,98 @@
1
+ // src/rules/require-init-before-steps.ts
2
+ var STEP_NAMES = /* @__PURE__ */ new Set([
3
+ "given",
4
+ "when",
5
+ "then",
6
+ "and",
7
+ "but",
8
+ "arrange",
9
+ "act",
10
+ "assert",
11
+ "setup",
12
+ "context",
13
+ "execute",
14
+ "action",
15
+ "verify",
16
+ "fn",
17
+ "expect"
18
+ ]);
19
+ function isFunction(node) {
20
+ return node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration";
21
+ }
22
+ function isStoryInitCall(node) {
23
+ const { callee } = node;
24
+ if (callee.type !== "MemberExpression") return false;
25
+ const member = callee;
26
+ const { object, property } = member;
27
+ return object.type === "Identifier" && object.name === "story" && property.type === "Identifier" && property.name === "init";
28
+ }
29
+ function isStoryStepCall(node) {
30
+ const { callee } = node;
31
+ if (callee.type !== "MemberExpression") return false;
32
+ const member = callee;
33
+ const { object, property } = member;
34
+ return object.type === "Identifier" && object.name === "story" && property.type === "Identifier" && STEP_NAMES.has(property.name);
35
+ }
36
+ function getContainingFunction(node, context) {
37
+ const ancestors = context.sourceCode.getAncestors(node);
38
+ for (let i = ancestors.length - 1; i >= 0; i--) {
39
+ if (isFunction(ancestors[i])) {
40
+ return ancestors[i];
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+ var rule = {
46
+ meta: {
47
+ type: "problem",
48
+ docs: {
49
+ description: "Require story.init() to be called before any step markers (story.given/when/then/etc) in Jest.",
50
+ recommended: true
51
+ },
52
+ schema: [],
53
+ messages: {
54
+ requireInit: "story.init() must be called before using step markers like story.given(), story.when(), story.then()."
55
+ }
56
+ },
57
+ create(context) {
58
+ let hasRelevantImport = false;
59
+ const functionsWithInit = /* @__PURE__ */ new WeakSet();
60
+ const pendingStepCalls = [];
61
+ return {
62
+ ImportDeclaration(node) {
63
+ if (node.source.value === "executable-stories-jest") {
64
+ hasRelevantImport = true;
65
+ }
66
+ },
67
+ CallExpression(node) {
68
+ if (!hasRelevantImport) return;
69
+ if (isStoryInitCall(node)) {
70
+ const containingFunction = getContainingFunction(node, context);
71
+ if (containingFunction) {
72
+ functionsWithInit.add(containingFunction);
73
+ }
74
+ return;
75
+ }
76
+ if (isStoryStepCall(node)) {
77
+ const containingFunction = getContainingFunction(node, context);
78
+ pendingStepCalls.push({ node, containingFunction });
79
+ }
80
+ },
81
+ "Program:exit"() {
82
+ for (const { node, containingFunction } of pendingStepCalls) {
83
+ if (!containingFunction || !functionsWithInit.has(containingFunction)) {
84
+ context.report({ node, messageId: "requireInit" });
85
+ }
86
+ }
87
+ }
88
+ };
89
+ }
90
+ };
91
+ var require_init_before_steps_default = rule;
92
+
1
93
  // src/rules/require-story-context-for-steps.ts
2
94
  var V1_PACKAGE = "executable-stories-jest";
3
- var STEP_NAMES = /* @__PURE__ */ new Set([
95
+ var STEP_NAMES2 = /* @__PURE__ */ new Set([
4
96
  "given",
5
97
  "when",
6
98
  "then",
@@ -16,7 +108,7 @@ var STEP_NAMES = /* @__PURE__ */ new Set([
16
108
  "verify"
17
109
  ]);
18
110
  var STORY_MODIFIERS = /* @__PURE__ */ new Set(["skip", "only"]);
19
- function isFunction(node) {
111
+ function isFunction2(node) {
20
112
  return node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
21
113
  }
22
114
  function isFunctionNode(node) {
@@ -30,6 +122,11 @@ function isStoryCall(node) {
30
122
  return false;
31
123
  return callee.property.type === "Identifier" && STORY_MODIFIERS.has(callee.property.name);
32
124
  }
125
+ function isStoryInitCall2(node) {
126
+ const { callee } = node;
127
+ if (callee.type !== "MemberExpression") return false;
128
+ return callee.object.type === "Identifier" && callee.object.name === "story" && callee.property.type === "Identifier" && callee.property.name === "init";
129
+ }
33
130
  function isDocStoryCall(node) {
34
131
  const { callee } = node;
35
132
  if (callee.type !== "MemberExpression") return false;
@@ -37,16 +134,16 @@ function isDocStoryCall(node) {
37
134
  }
38
135
  function isStepCall(node) {
39
136
  const { callee } = node;
40
- if (callee.type === "Identifier") return STEP_NAMES.has(callee.name);
137
+ if (callee.type === "Identifier") return STEP_NAMES2.has(callee.name);
41
138
  if (callee.type !== "MemberExpression") return false;
42
139
  if (callee.object.type !== "Identifier") return false;
43
140
  if (callee.object.name !== "steps" && callee.object.name !== "step")
44
141
  return false;
45
- return callee.property.type === "Identifier" && STEP_NAMES.has(callee.property.name);
142
+ return callee.property.type === "Identifier" && STEP_NAMES2.has(callee.property.name);
46
143
  }
47
144
  function insideStoryCallback(node, context) {
48
145
  const ancestors = context.sourceCode.getAncestors(node);
49
- const functionAncestors = new Set(ancestors.filter(isFunction));
146
+ const functionAncestors = new Set(ancestors.filter(isFunction2));
50
147
  for (const ancestor of ancestors) {
51
148
  if (ancestor.type !== "CallExpression") continue;
52
149
  if (!isStoryCall(ancestor) && !isDocStoryCall(ancestor)) continue;
@@ -58,7 +155,7 @@ function insideStoryCallback(node, context) {
58
155
  }
59
156
  return false;
60
157
  }
61
- var rule = {
158
+ var rule2 = {
62
159
  meta: {
63
160
  type: "problem",
64
161
  docs: {
@@ -74,7 +171,17 @@ var rule = {
74
171
  let hasV1Import = false;
75
172
  const namedFunctions = /* @__PURE__ */ new Map();
76
173
  const storyCallbackNames = /* @__PURE__ */ new Set();
174
+ const functionsWithInit = /* @__PURE__ */ new WeakSet();
77
175
  const pendingStepCalls = [];
176
+ function getContainingFunction2(node) {
177
+ const ancestors = context.sourceCode.getAncestors(node);
178
+ for (let i = ancestors.length - 1; i >= 0; i--) {
179
+ if (isFunctionNode(ancestors[i]) || isFunction2(ancestors[i])) {
180
+ return ancestors[i];
181
+ }
182
+ }
183
+ return null;
184
+ }
78
185
  function getContainingFunctionName(node) {
79
186
  const ancestors = context.sourceCode.getAncestors(node);
80
187
  for (let i = ancestors.length - 1; i >= 0; i--) {
@@ -119,6 +226,13 @@ var rule = {
119
226
  },
120
227
  CallExpression(node) {
121
228
  if (!hasV1Import) return;
229
+ if (isStoryInitCall2(node)) {
230
+ const containingFunction2 = getContainingFunction2(node);
231
+ if (containingFunction2) {
232
+ functionsWithInit.add(containingFunction2);
233
+ }
234
+ return;
235
+ }
122
236
  if (isStoryCall(node) || isDocStoryCall(node)) {
123
237
  for (const arg of node.arguments) {
124
238
  if (arg.type === "Identifier") {
@@ -132,21 +246,25 @@ var rule = {
132
246
  }
133
247
  if (!isStepCall(node)) return;
134
248
  if (insideStoryCallback(node, context)) return;
249
+ const containingFunction = getContainingFunction2(node);
135
250
  const containingFunctionName = getContainingFunctionName(node);
136
- pendingStepCalls.push({ node, containingFunctionName });
251
+ pendingStepCalls.push({ node, containingFunction, containingFunctionName });
137
252
  },
138
253
  "Program:exit"() {
139
- for (const { node, containingFunctionName } of pendingStepCalls) {
254
+ for (const { node, containingFunction, containingFunctionName } of pendingStepCalls) {
140
255
  if (containingFunctionName && storyCallbackNames.has(containingFunctionName)) {
141
256
  continue;
142
257
  }
258
+ if (containingFunction && functionsWithInit.has(containingFunction)) {
259
+ continue;
260
+ }
143
261
  context.report({ node, messageId: "requireStory" });
144
262
  }
145
263
  }
146
264
  };
147
265
  }
148
266
  };
149
- var require_story_context_for_steps_default = rule;
267
+ var require_story_context_for_steps_default = rule2;
150
268
 
151
269
  // src/rules/require-test-context-for-doc-story.ts
152
270
  var TEST_MODIFIERS = /* @__PURE__ */ new Set([
@@ -156,7 +274,7 @@ var TEST_MODIFIERS = /* @__PURE__ */ new Set([
156
274
  "concurrent",
157
275
  "failing"
158
276
  ]);
159
- function isFunction2(node) {
277
+ function isFunction3(node) {
160
278
  return node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
161
279
  }
162
280
  function isFunctionNode2(node) {
@@ -192,7 +310,7 @@ function isTestCallExpression(node) {
192
310
  }
193
311
  function insideTestCallback(node, context) {
194
312
  const ancestors = context.sourceCode.getAncestors(node);
195
- const functionAncestors = new Set(ancestors.filter(isFunction2));
313
+ const functionAncestors = new Set(ancestors.filter(isFunction3));
196
314
  for (const ancestor of ancestors) {
197
315
  if (ancestor.type !== "CallExpression") continue;
198
316
  if (!isTestCallExpression(ancestor)) continue;
@@ -204,7 +322,7 @@ function insideTestCallback(node, context) {
204
322
  }
205
323
  return false;
206
324
  }
207
- var rule2 = {
325
+ var rule3 = {
208
326
  meta: {
209
327
  type: "problem",
210
328
  docs: {
@@ -297,10 +415,11 @@ var rule2 = {
297
415
  };
298
416
  }
299
417
  };
300
- var require_test_context_for_doc_story_default = rule2;
418
+ var require_test_context_for_doc_story_default = rule3;
301
419
 
302
420
  // src/index.ts
303
421
  var rules = {
422
+ "require-init-before-steps": require_init_before_steps_default,
304
423
  "require-story-context-for-steps": require_story_context_for_steps_default,
305
424
  "require-test-context-for-doc-story": require_test_context_for_doc_story_default
306
425
  };
@@ -311,6 +430,7 @@ var configs = {
311
430
  "executable-stories-jest": { rules }
312
431
  },
313
432
  rules: {
433
+ "executable-stories-jest/require-init-before-steps": "error",
314
434
  "executable-stories-jest/require-story-context-for-steps": "error",
315
435
  "executable-stories-jest/require-test-context-for-doc-story": "error"
316
436
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/rules/require-story-context-for-steps.ts","../src/rules/require-test-context-for-doc-story.ts","../src/index.ts"],"sourcesContent":["import type { Rule } from 'eslint';\nimport type {\n ArrowFunctionExpression,\n CallExpression,\n FunctionDeclaration,\n FunctionExpression,\n Node,\n Property,\n VariableDeclarator,\n} from 'estree';\n\n/** Current API uses story.given() etc. inside test(); no top-level given/when/then. */\nconst V1_PACKAGE = 'executable-stories-jest';\n\nconst STEP_NAMES = new Set([\n 'given',\n 'when',\n 'then',\n 'and',\n 'but',\n 'arrange',\n 'act',\n 'assert',\n 'setup',\n 'context',\n 'execute',\n 'action',\n 'verify',\n]);\n\nconst STORY_MODIFIERS = new Set(['skip', 'only']);\n\nfunction isFunction(node: Node): boolean {\n return (\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\nfunction isFunctionNode(\n node: Node,\n): node is FunctionDeclaration | ArrowFunctionExpression | FunctionExpression {\n return (\n node.type === 'FunctionDeclaration' ||\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\nfunction isStoryCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type === 'Identifier') return callee.name === 'story';\n if (callee.type !== 'MemberExpression') return false;\n if (callee.object.type !== 'Identifier' || callee.object.name !== 'story')\n return false;\n return (\n callee.property.type === 'Identifier' &&\n STORY_MODIFIERS.has(callee.property.name)\n );\n}\n\nfunction isDocStoryCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type !== 'MemberExpression') return false;\n return (\n callee.object.type === 'Identifier' &&\n callee.object.name === 'doc' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'story'\n );\n}\n\nfunction isStepCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type === 'Identifier') return STEP_NAMES.has(callee.name);\n if (callee.type !== 'MemberExpression') return false;\n if (callee.object.type !== 'Identifier') return false;\n if (callee.object.name !== 'steps' && callee.object.name !== 'step')\n return false;\n return (\n callee.property.type === 'Identifier' &&\n STEP_NAMES.has(callee.property.name)\n );\n}\n\nfunction insideStoryCallback(\n node: CallExpression,\n context: Rule.RuleContext,\n): boolean {\n const ancestors = context.sourceCode.getAncestors(node);\n const functionAncestors = new Set(ancestors.filter(isFunction));\n\n for (const ancestor of ancestors) {\n if (ancestor.type !== 'CallExpression') continue;\n if (!isStoryCall(ancestor) && !isDocStoryCall(ancestor)) continue;\n for (const arg of ancestor.arguments) {\n if (arg && typeof arg === 'object' && functionAncestors.has(arg)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require step functions (given/when/then/and/but and aliases) to be called inside story() or doc.story(..., callback).',\n recommended: true,\n },\n schema: [],\n messages: {\n requireStory:\n 'Step functions must be called inside story(...) (or doc.story(..., callback)).',\n },\n },\n create(context) {\n let hasV1Import = false;\n const namedFunctions = new Map<string, Node>();\n const storyCallbackNames = new Set<string>();\n const pendingStepCalls: Array<{\n node: CallExpression;\n containingFunctionName: string | null;\n }> = [];\n\n function getContainingFunctionName(node: CallExpression): string | null {\n const ancestors = context.sourceCode.getAncestors(node);\n for (let i = ancestors.length - 1; i >= 0; i--) {\n const ancestor = ancestors[i];\n if (ancestor.type === 'FunctionDeclaration' && ancestor.id) {\n return ancestor.id.name;\n }\n if (ancestor.type === 'VariableDeclarator') {\n const declarator = ancestor as VariableDeclarator;\n if (\n declarator.id.type === 'Identifier' &&\n declarator.init &&\n isFunctionNode(declarator.init)\n ) {\n return declarator.id.name;\n }\n }\n // Check for object method definitions\n if (ancestor.type === 'Property') {\n const prop = ancestor as Property;\n if (\n prop.key.type === 'Identifier' &&\n prop.value &&\n isFunctionNode(prop.value)\n ) {\n return prop.key.name;\n }\n // Shorthand method syntax: { define() {} }\n if (prop.key.type === 'Identifier' && prop.method) {\n return prop.key.name;\n }\n }\n }\n return null;\n }\n\n return {\n ImportDeclaration(node) {\n const pkg = node.source.value;\n if (typeof pkg === 'string' && pkg === V1_PACKAGE) {\n hasV1Import = true;\n }\n },\n FunctionDeclaration(node: FunctionDeclaration) {\n if (node.id) {\n namedFunctions.set(node.id.name, node);\n }\n },\n VariableDeclarator(node: VariableDeclarator) {\n if (\n node.id.type === 'Identifier' &&\n node.init &&\n isFunctionNode(node.init)\n ) {\n namedFunctions.set(node.id.name, node.init);\n }\n },\n CallExpression(node: CallExpression) {\n if (!hasV1Import) return;\n\n if (isStoryCall(node) || isDocStoryCall(node)) {\n for (const arg of node.arguments) {\n if (arg.type === 'Identifier') {\n storyCallbackNames.add(arg.name);\n }\n // Handle member expression callbacks like handlers.define\n if (\n arg.type === 'MemberExpression' &&\n arg.property.type === 'Identifier'\n ) {\n storyCallbackNames.add(arg.property.name);\n }\n }\n return;\n }\n\n if (!isStepCall(node)) return;\n if (insideStoryCallback(node, context)) return;\n\n const containingFunctionName = getContainingFunctionName(node);\n pendingStepCalls.push({ node, containingFunctionName });\n },\n 'Program:exit'() {\n for (const { node, containingFunctionName } of pendingStepCalls) {\n if (\n containingFunctionName &&\n storyCallbackNames.has(containingFunctionName)\n ) {\n continue;\n }\n context.report({ node, messageId: 'requireStory' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport type {\n ArrowFunctionExpression,\n CallExpression,\n FunctionDeclaration,\n FunctionExpression,\n Node,\n Property,\n VariableDeclarator,\n} from 'estree';\n\nconst TEST_MODIFIERS = new Set([\n 'only',\n 'skip',\n 'todo',\n 'concurrent',\n 'failing',\n]);\n\nfunction isFunction(node: Node): boolean {\n return (\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\nfunction isFunctionNode(\n node: Node,\n): node is FunctionDeclaration | ArrowFunctionExpression | FunctionExpression {\n return (\n node.type === 'FunctionDeclaration' ||\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\n/** Legacy doc.story() is not used; current API uses story.init() inside it/test. */\nconst V1_PACKAGE = 'executable-stories-jest';\n\nfunction isDocStoryCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type !== 'MemberExpression') return false;\n return (\n callee.object.type === 'Identifier' &&\n callee.object.name === 'doc' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'story'\n );\n}\n\nfunction isTestCallExpression(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type === 'Identifier') {\n return callee.name === 'test' || callee.name === 'it';\n }\n if (callee.type === 'MemberExpression') {\n if (callee.object.type !== 'Identifier') return false;\n if (callee.object.name !== 'test' && callee.object.name !== 'it')\n return false;\n if (callee.property.type !== 'Identifier') return false;\n return TEST_MODIFIERS.has(callee.property.name);\n }\n if (callee.type === 'CallExpression') {\n const inner = callee.callee;\n if (inner.type !== 'MemberExpression') return false;\n if (inner.object.type !== 'Identifier') return false;\n if (inner.object.name !== 'test' && inner.object.name !== 'it')\n return false;\n return (\n inner.property.type === 'Identifier' && inner.property.name === 'each'\n );\n }\n return false;\n}\n\nfunction insideTestCallback(\n node: CallExpression,\n context: Rule.RuleContext,\n): boolean {\n const ancestors = context.sourceCode.getAncestors(node);\n const functionAncestors = new Set(ancestors.filter(isFunction));\n\n for (const ancestor of ancestors) {\n if (ancestor.type !== 'CallExpression') continue;\n if (!isTestCallExpression(ancestor)) continue;\n for (const arg of ancestor.arguments) {\n if (arg && typeof arg === 'object' && functionAncestors.has(arg)) {\n return true;\n }\n }\n }\n return false;\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require doc.story(title) to be called inside a test/it callback in Jest.',\n recommended: true,\n },\n schema: [],\n messages: {\n requireTest:\n \"doc.story(title) must be called inside a test/it callback (e.g. it('...', () => { doc.story('Title'); ... })).\",\n requireTitle: 'doc.story(title) requires a title argument.',\n },\n },\n create(context) {\n let hasV1Import = false;\n const namedFunctions = new Map<string, Node>();\n const testCallbackNames = new Set<string>();\n const pendingDocStoryCalls: Array<{\n node: CallExpression;\n containingFunctionName: string | null;\n }> = [];\n\n function getContainingFunctionName(node: CallExpression): string | null {\n const ancestors = context.sourceCode.getAncestors(node);\n for (let i = ancestors.length - 1; i >= 0; i--) {\n const ancestor = ancestors[i];\n if (ancestor.type === 'FunctionDeclaration' && ancestor.id) {\n return ancestor.id.name;\n }\n if (ancestor.type === 'VariableDeclarator') {\n const declarator = ancestor as VariableDeclarator;\n if (\n declarator.id.type === 'Identifier' &&\n declarator.init &&\n isFunctionNode(declarator.init)\n ) {\n return declarator.id.name;\n }\n }\n // Check for object method definitions\n if (ancestor.type === 'Property') {\n const prop = ancestor as Property;\n if (\n prop.key.type === 'Identifier' &&\n prop.value &&\n isFunctionNode(prop.value)\n ) {\n return prop.key.name;\n }\n // Shorthand method syntax: { run() {} }\n if (prop.key.type === 'Identifier' && prop.method) {\n return prop.key.name;\n }\n }\n }\n return null;\n }\n\n return {\n ImportDeclaration(node) {\n if (node.source.value === V1_PACKAGE) {\n hasV1Import = true;\n }\n },\n FunctionDeclaration(node: FunctionDeclaration) {\n if (node.id) {\n namedFunctions.set(node.id.name, node);\n }\n },\n VariableDeclarator(node: VariableDeclarator) {\n if (\n node.id.type === 'Identifier' &&\n node.init &&\n isFunctionNode(node.init)\n ) {\n namedFunctions.set(node.id.name, node.init);\n }\n },\n CallExpression(node: CallExpression) {\n if (!hasV1Import) return;\n\n if (isTestCallExpression(node)) {\n for (const arg of node.arguments) {\n if (arg.type === 'Identifier') {\n testCallbackNames.add(arg.name);\n }\n // Handle member expression callbacks like handlers.run\n if (\n arg.type === 'MemberExpression' &&\n arg.property.type === 'Identifier'\n ) {\n testCallbackNames.add(arg.property.name);\n }\n }\n return;\n }\n\n if (!isDocStoryCall(node)) return;\n\n if (node.arguments.length === 0) {\n context.report({ node, messageId: 'requireTitle' });\n return;\n }\n if (node.arguments.length >= 2) return;\n\n if (insideTestCallback(node, context)) return;\n\n const containingFunctionName = getContainingFunctionName(node);\n pendingDocStoryCalls.push({ node, containingFunctionName });\n },\n 'Program:exit'() {\n for (const { node, containingFunctionName } of pendingDocStoryCalls) {\n if (\n containingFunctionName &&\n testCallbackNames.has(containingFunctionName)\n ) {\n continue;\n }\n context.report({ node, messageId: 'requireTest' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","import type { ESLint, Linter } from 'eslint';\nimport requireStoryContextForSteps from './rules/require-story-context-for-steps.js';\nimport requireTestContextForDocStory from './rules/require-test-context-for-doc-story.js';\n\nconst rules = {\n 'require-story-context-for-steps': requireStoryContextForSteps,\n 'require-test-context-for-doc-story': requireTestContextForDocStory,\n};\n\nconst configs: Record<string, Linter.Config[]> = {\n recommended: [\n {\n plugins: {\n 'executable-stories-jest': { rules },\n },\n rules: {\n 'executable-stories-jest/require-story-context-for-steps': 'error',\n 'executable-stories-jest/require-test-context-for-doc-story': 'error',\n },\n },\n ],\n};\n\nconst plugin: ESLint.Plugin = {\n meta: {\n name: 'eslint-plugin-executable-stories-jest',\n version: '0.1.0',\n },\n rules,\n configs,\n};\n\nexport default plugin;\nexport { rules, configs };\n"],"mappings":"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","names":["isFunction","isFunctionNode","V1_PACKAGE","isDocStoryCall","rule"]}
1
+ {"version":3,"sources":["../src/rules/require-init-before-steps.ts","../src/rules/require-story-context-for-steps.ts","../src/rules/require-test-context-for-doc-story.ts","../src/index.ts"],"sourcesContent":["import type { Rule } from 'eslint';\nimport type { CallExpression, MemberExpression, Node } from 'estree';\n\n/**\n * Rule: require-init-before-steps\n *\n * Ensures story.init() is called before any step markers (story.given/when/then/etc).\n * This is a best-effort static check - it flags step markers that appear before any story.init()\n * call in the same function scope.\n *\n * BAD:\n * story.given('something'); // No story.init() before this\n *\n * GOOD:\n * story.init();\n * story.given('something');\n */\n\nconst STEP_NAMES = new Set([\n 'given',\n 'when',\n 'then',\n 'and',\n 'but',\n 'arrange',\n 'act',\n 'assert',\n 'setup',\n 'context',\n 'execute',\n 'action',\n 'verify',\n 'fn',\n 'expect',\n]);\n\nfunction isFunction(node: Node): boolean {\n return (\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression' ||\n node.type === 'FunctionDeclaration'\n );\n}\n\nfunction isStoryInitCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type !== 'MemberExpression') return false;\n const member = callee as MemberExpression;\n const { object, property } = member;\n return (\n object.type === 'Identifier' &&\n object.name === 'story' &&\n property.type === 'Identifier' &&\n property.name === 'init'\n );\n}\n\nfunction isStoryStepCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type !== 'MemberExpression') return false;\n const member = callee as MemberExpression;\n const { object, property } = member;\n return (\n object.type === 'Identifier' &&\n object.name === 'story' &&\n property.type === 'Identifier' &&\n STEP_NAMES.has(property.name)\n );\n}\n\nfunction getContainingFunction(\n node: Node,\n context: Rule.RuleContext,\n): Node | null {\n const ancestors = context.sourceCode.getAncestors(node);\n for (let i = ancestors.length - 1; i >= 0; i--) {\n if (isFunction(ancestors[i])) {\n return ancestors[i];\n }\n }\n return null;\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require story.init() to be called before any step markers (story.given/when/then/etc) in Jest.',\n recommended: true,\n },\n schema: [],\n messages: {\n requireInit:\n 'story.init() must be called before using step markers like story.given(), story.when(), story.then().',\n },\n },\n create(context) {\n let hasRelevantImport = false;\n // Track functions that have story.init() calls\n const functionsWithInit = new WeakSet<Node>();\n // Track step calls and their containing functions for later verification\n const pendingStepCalls: Array<{\n node: CallExpression;\n containingFunction: Node | null;\n }> = [];\n\n return {\n ImportDeclaration(node) {\n if (node.source.value === 'executable-stories-jest') {\n hasRelevantImport = true;\n }\n },\n CallExpression(node: CallExpression) {\n if (!hasRelevantImport) return;\n\n if (isStoryInitCall(node)) {\n const containingFunction = getContainingFunction(node, context);\n if (containingFunction) {\n functionsWithInit.add(containingFunction);\n }\n return;\n }\n\n if (isStoryStepCall(node)) {\n const containingFunction = getContainingFunction(node, context);\n pendingStepCalls.push({ node, containingFunction });\n }\n },\n 'Program:exit'() {\n for (const { node, containingFunction } of pendingStepCalls) {\n // If no containing function, or the function doesn't have story.init(), report\n if (\n !containingFunction ||\n !functionsWithInit.has(containingFunction)\n ) {\n context.report({ node, messageId: 'requireInit' });\n }\n }\n },\n };\n },\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport type {\n ArrowFunctionExpression,\n CallExpression,\n FunctionDeclaration,\n FunctionExpression,\n Node,\n Property,\n VariableDeclarator,\n} from 'estree';\n\n/** Current API uses story.given() etc. inside test(); no top-level given/when/then. */\nconst V1_PACKAGE = 'executable-stories-jest';\n\nconst STEP_NAMES = new Set([\n 'given',\n 'when',\n 'then',\n 'and',\n 'but',\n 'arrange',\n 'act',\n 'assert',\n 'setup',\n 'context',\n 'execute',\n 'action',\n 'verify',\n]);\n\nconst STORY_MODIFIERS = new Set(['skip', 'only']);\n\nfunction isFunction(node: Node): boolean {\n return (\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\nfunction isFunctionNode(\n node: Node,\n): node is FunctionDeclaration | ArrowFunctionExpression | FunctionExpression {\n return (\n node.type === 'FunctionDeclaration' ||\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\nfunction isStoryCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type === 'Identifier') return callee.name === 'story';\n if (callee.type !== 'MemberExpression') return false;\n if (callee.object.type !== 'Identifier' || callee.object.name !== 'story')\n return false;\n return (\n callee.property.type === 'Identifier' &&\n STORY_MODIFIERS.has(callee.property.name)\n );\n}\n\nfunction isStoryInitCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type !== 'MemberExpression') return false;\n return (\n callee.object.type === 'Identifier' &&\n callee.object.name === 'story' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'init'\n );\n}\n\nfunction isDocStoryCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type !== 'MemberExpression') return false;\n return (\n callee.object.type === 'Identifier' &&\n callee.object.name === 'doc' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'story'\n );\n}\n\nfunction isStepCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type === 'Identifier') return STEP_NAMES.has(callee.name);\n if (callee.type !== 'MemberExpression') return false;\n if (callee.object.type !== 'Identifier') return false;\n if (callee.object.name !== 'steps' && callee.object.name !== 'step')\n return false;\n return (\n callee.property.type === 'Identifier' &&\n STEP_NAMES.has(callee.property.name)\n );\n}\n\nfunction insideStoryCallback(\n node: CallExpression,\n context: Rule.RuleContext,\n): boolean {\n const ancestors = context.sourceCode.getAncestors(node);\n const functionAncestors = new Set(ancestors.filter(isFunction));\n\n for (const ancestor of ancestors) {\n if (ancestor.type !== 'CallExpression') continue;\n if (!isStoryCall(ancestor) && !isDocStoryCall(ancestor)) continue;\n for (const arg of ancestor.arguments) {\n if (arg && typeof arg === 'object' && functionAncestors.has(arg)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require step functions (given/when/then/and/but and aliases) to be called inside story() or doc.story(..., callback).',\n recommended: true,\n },\n schema: [],\n messages: {\n requireStory:\n 'Step functions must be called inside story(...) (or doc.story(..., callback)).',\n },\n },\n create(context) {\n let hasV1Import = false;\n const namedFunctions = new Map<string, Node>();\n const storyCallbackNames = new Set<string>();\n // Track functions that contain story.init() calls\n const functionsWithInit = new WeakSet<Node>();\n const pendingStepCalls: Array<{\n node: CallExpression;\n containingFunction: Node | null;\n containingFunctionName: string | null;\n }> = [];\n\n function getContainingFunction(node: CallExpression): Node | null {\n const ancestors = context.sourceCode.getAncestors(node);\n for (let i = ancestors.length - 1; i >= 0; i--) {\n if (isFunctionNode(ancestors[i]) || isFunction(ancestors[i])) {\n return ancestors[i];\n }\n }\n return null;\n }\n\n function getContainingFunctionName(node: CallExpression): string | null {\n const ancestors = context.sourceCode.getAncestors(node);\n for (let i = ancestors.length - 1; i >= 0; i--) {\n const ancestor = ancestors[i];\n if (ancestor.type === 'FunctionDeclaration' && ancestor.id) {\n return ancestor.id.name;\n }\n if (ancestor.type === 'VariableDeclarator') {\n const declarator = ancestor as VariableDeclarator;\n if (\n declarator.id.type === 'Identifier' &&\n declarator.init &&\n isFunctionNode(declarator.init)\n ) {\n return declarator.id.name;\n }\n }\n // Check for object method definitions\n if (ancestor.type === 'Property') {\n const prop = ancestor as Property;\n if (\n prop.key.type === 'Identifier' &&\n prop.value &&\n isFunctionNode(prop.value)\n ) {\n return prop.key.name;\n }\n // Shorthand method syntax: { define() {} }\n if (prop.key.type === 'Identifier' && prop.method) {\n return prop.key.name;\n }\n }\n }\n return null;\n }\n\n return {\n ImportDeclaration(node) {\n const pkg = node.source.value;\n if (typeof pkg === 'string' && pkg === V1_PACKAGE) {\n hasV1Import = true;\n }\n },\n FunctionDeclaration(node: FunctionDeclaration) {\n if (node.id) {\n namedFunctions.set(node.id.name, node);\n }\n },\n VariableDeclarator(node: VariableDeclarator) {\n if (\n node.id.type === 'Identifier' &&\n node.init &&\n isFunctionNode(node.init)\n ) {\n namedFunctions.set(node.id.name, node.init);\n }\n },\n CallExpression(node: CallExpression) {\n if (!hasV1Import) return;\n\n // Track story.init() calls and their containing functions\n if (isStoryInitCall(node)) {\n const containingFunction = getContainingFunction(node);\n if (containingFunction) {\n functionsWithInit.add(containingFunction);\n }\n return;\n }\n\n if (isStoryCall(node) || isDocStoryCall(node)) {\n for (const arg of node.arguments) {\n if (arg.type === 'Identifier') {\n storyCallbackNames.add(arg.name);\n }\n // Handle member expression callbacks like handlers.define\n if (\n arg.type === 'MemberExpression' &&\n arg.property.type === 'Identifier'\n ) {\n storyCallbackNames.add(arg.property.name);\n }\n }\n return;\n }\n\n if (!isStepCall(node)) return;\n if (insideStoryCallback(node, context)) return;\n\n const containingFunction = getContainingFunction(node);\n const containingFunctionName = getContainingFunctionName(node);\n pendingStepCalls.push({ node, containingFunction, containingFunctionName });\n },\n 'Program:exit'() {\n for (const { node, containingFunction, containingFunctionName } of pendingStepCalls) {\n // Allow if inside a named function passed to story()/doc.story()\n if (\n containingFunctionName &&\n storyCallbackNames.has(containingFunctionName)\n ) {\n continue;\n }\n // Allow if inside a function that has story.init()\n if (containingFunction && functionsWithInit.has(containingFunction)) {\n continue;\n }\n context.report({ node, messageId: 'requireStory' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","import type { Rule } from 'eslint';\nimport type {\n ArrowFunctionExpression,\n CallExpression,\n FunctionDeclaration,\n FunctionExpression,\n Node,\n Property,\n VariableDeclarator,\n} from 'estree';\n\nconst TEST_MODIFIERS = new Set([\n 'only',\n 'skip',\n 'todo',\n 'concurrent',\n 'failing',\n]);\n\nfunction isFunction(node: Node): boolean {\n return (\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\nfunction isFunctionNode(\n node: Node,\n): node is FunctionDeclaration | ArrowFunctionExpression | FunctionExpression {\n return (\n node.type === 'FunctionDeclaration' ||\n node.type === 'FunctionExpression' ||\n node.type === 'ArrowFunctionExpression'\n );\n}\n\n/** Legacy doc.story() is not used; current API uses story.init() inside it/test. */\nconst V1_PACKAGE = 'executable-stories-jest';\n\nfunction isDocStoryCall(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type !== 'MemberExpression') return false;\n return (\n callee.object.type === 'Identifier' &&\n callee.object.name === 'doc' &&\n callee.property.type === 'Identifier' &&\n callee.property.name === 'story'\n );\n}\n\nfunction isTestCallExpression(node: CallExpression): boolean {\n const { callee } = node;\n if (callee.type === 'Identifier') {\n return callee.name === 'test' || callee.name === 'it';\n }\n if (callee.type === 'MemberExpression') {\n if (callee.object.type !== 'Identifier') return false;\n if (callee.object.name !== 'test' && callee.object.name !== 'it')\n return false;\n if (callee.property.type !== 'Identifier') return false;\n return TEST_MODIFIERS.has(callee.property.name);\n }\n if (callee.type === 'CallExpression') {\n const inner = callee.callee;\n if (inner.type !== 'MemberExpression') return false;\n if (inner.object.type !== 'Identifier') return false;\n if (inner.object.name !== 'test' && inner.object.name !== 'it')\n return false;\n return (\n inner.property.type === 'Identifier' && inner.property.name === 'each'\n );\n }\n return false;\n}\n\nfunction insideTestCallback(\n node: CallExpression,\n context: Rule.RuleContext,\n): boolean {\n const ancestors = context.sourceCode.getAncestors(node);\n const functionAncestors = new Set(ancestors.filter(isFunction));\n\n for (const ancestor of ancestors) {\n if (ancestor.type !== 'CallExpression') continue;\n if (!isTestCallExpression(ancestor)) continue;\n for (const arg of ancestor.arguments) {\n if (arg && typeof arg === 'object' && functionAncestors.has(arg)) {\n return true;\n }\n }\n }\n return false;\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Require doc.story(title) to be called inside a test/it callback in Jest.',\n recommended: true,\n },\n schema: [],\n messages: {\n requireTest:\n \"doc.story(title) must be called inside a test/it callback (e.g. it('...', () => { doc.story('Title'); ... })).\",\n requireTitle: 'doc.story(title) requires a title argument.',\n },\n },\n create(context) {\n let hasV1Import = false;\n const namedFunctions = new Map<string, Node>();\n const testCallbackNames = new Set<string>();\n const pendingDocStoryCalls: Array<{\n node: CallExpression;\n containingFunctionName: string | null;\n }> = [];\n\n function getContainingFunctionName(node: CallExpression): string | null {\n const ancestors = context.sourceCode.getAncestors(node);\n for (let i = ancestors.length - 1; i >= 0; i--) {\n const ancestor = ancestors[i];\n if (ancestor.type === 'FunctionDeclaration' && ancestor.id) {\n return ancestor.id.name;\n }\n if (ancestor.type === 'VariableDeclarator') {\n const declarator = ancestor as VariableDeclarator;\n if (\n declarator.id.type === 'Identifier' &&\n declarator.init &&\n isFunctionNode(declarator.init)\n ) {\n return declarator.id.name;\n }\n }\n // Check for object method definitions\n if (ancestor.type === 'Property') {\n const prop = ancestor as Property;\n if (\n prop.key.type === 'Identifier' &&\n prop.value &&\n isFunctionNode(prop.value)\n ) {\n return prop.key.name;\n }\n // Shorthand method syntax: { run() {} }\n if (prop.key.type === 'Identifier' && prop.method) {\n return prop.key.name;\n }\n }\n }\n return null;\n }\n\n return {\n ImportDeclaration(node) {\n if (node.source.value === V1_PACKAGE) {\n hasV1Import = true;\n }\n },\n FunctionDeclaration(node: FunctionDeclaration) {\n if (node.id) {\n namedFunctions.set(node.id.name, node);\n }\n },\n VariableDeclarator(node: VariableDeclarator) {\n if (\n node.id.type === 'Identifier' &&\n node.init &&\n isFunctionNode(node.init)\n ) {\n namedFunctions.set(node.id.name, node.init);\n }\n },\n CallExpression(node: CallExpression) {\n if (!hasV1Import) return;\n\n if (isTestCallExpression(node)) {\n for (const arg of node.arguments) {\n if (arg.type === 'Identifier') {\n testCallbackNames.add(arg.name);\n }\n // Handle member expression callbacks like handlers.run\n if (\n arg.type === 'MemberExpression' &&\n arg.property.type === 'Identifier'\n ) {\n testCallbackNames.add(arg.property.name);\n }\n }\n return;\n }\n\n if (!isDocStoryCall(node)) return;\n\n if (node.arguments.length === 0) {\n context.report({ node, messageId: 'requireTitle' });\n return;\n }\n if (node.arguments.length >= 2) return;\n\n if (insideTestCallback(node, context)) return;\n\n const containingFunctionName = getContainingFunctionName(node);\n pendingDocStoryCalls.push({ node, containingFunctionName });\n },\n 'Program:exit'() {\n for (const { node, containingFunctionName } of pendingDocStoryCalls) {\n if (\n containingFunctionName &&\n testCallbackNames.has(containingFunctionName)\n ) {\n continue;\n }\n context.report({ node, messageId: 'requireTest' });\n }\n },\n };\n },\n};\n\nexport default rule;\n","import type { ESLint, Linter } from 'eslint';\nimport requireInitBeforeSteps from './rules/require-init-before-steps.js';\nimport requireStoryContextForSteps from './rules/require-story-context-for-steps.js';\nimport requireTestContextForDocStory from './rules/require-test-context-for-doc-story.js';\n\nconst rules = {\n 'require-init-before-steps': requireInitBeforeSteps,\n 'require-story-context-for-steps': requireStoryContextForSteps,\n 'require-test-context-for-doc-story': requireTestContextForDocStory,\n};\n\nconst configs: Record<string, Linter.Config[]> = {\n recommended: [\n {\n plugins: {\n 'executable-stories-jest': { rules },\n },\n rules: {\n 'executable-stories-jest/require-init-before-steps': 'error',\n 'executable-stories-jest/require-story-context-for-steps': 'error',\n 'executable-stories-jest/require-test-context-for-doc-story': 'error',\n },\n },\n ],\n};\n\nconst plugin: ESLint.Plugin = {\n meta: {\n name: 'eslint-plugin-executable-stories-jest',\n version: '0.1.0',\n },\n rules,\n configs,\n};\n\nexport default plugin;\nexport { rules, configs };\n"],"mappings":"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","names":["STEP_NAMES","isFunction","isStoryInitCall","rule","getContainingFunction","containingFunction","isFunction","isFunctionNode","V1_PACKAGE","isDocStoryCall","rule"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-executable-stories-jest",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "type": "module",
5
5
  "description": "ESLint rules for executable-stories-jest: step context, doc.story in test/it",
6
6
  "main": "./dist/index.cjs",
@@ -40,13 +40,13 @@
40
40
  "devDependencies": {
41
41
  "@types/eslint": "^9.6.1",
42
42
  "@types/estree": "^1.0.8",
43
- "@types/node": "^25.3.2",
43
+ "@types/node": "^25.5.0",
44
44
  "eslint": "^10.0.1",
45
45
  "tsup": "^8.5.1",
46
46
  "typescript": "^5.9.3",
47
- "vitest": "^4.0.18",
47
+ "vitest": "^4.1.0",
48
48
  "eslint-config-executable-stories": "0.2.0",
49
- "executable-stories-vitest": "7.0.2"
49
+ "executable-stories-vitest": "8.1.3"
50
50
  },
51
51
  "engines": {
52
52
  "node": ">=22"
@@ -1,10 +1,11 @@
1
1
  ---
2
2
  name: eslint-jest-rules
3
3
  description: >
4
- ESLint flat config plugin for executable-stories-jest. Two rules:
5
- require-story-context-for-steps (steps must be inside story callback),
4
+ ESLint flat config plugin for executable-stories-jest. Three rules:
5
+ require-init-before-steps (init before given/when/then),
6
+ require-story-context-for-steps (steps must be inside story callback or story.init scope),
6
7
  require-test-context-for-doc-story (doc.story must be inside test/it).
7
- Recommended config enables both at error level.
8
+ Recommended config enables all at error level.
8
9
  type: core
9
10
  library: eslint-plugin-executable-stories-jest
10
11
  library_version: "0.1.0"
@@ -21,7 +22,7 @@ sources:
21
22
  import jestStories from "eslint-plugin-executable-stories-jest";
22
23
 
23
24
  export default [
24
- // Option A: Use recommended config
25
+ // Option A: Use recommended config (enables all rules at error)
25
26
  ...jestStories.configs.recommended,
26
27
 
27
28
  // Option B: Manual configuration
@@ -30,6 +31,7 @@ export default [
30
31
  "executable-stories-jest": jestStories,
31
32
  },
32
33
  rules: {
34
+ "executable-stories-jest/require-init-before-steps": "error",
33
35
  "executable-stories-jest/require-story-context-for-steps": "error",
34
36
  "executable-stories-jest/require-test-context-for-doc-story": "error",
35
37
  },
@@ -39,20 +41,47 @@ export default [
39
41
 
40
42
  ## Core Patterns
41
43
 
42
- ### Rule: require-story-context-for-steps
44
+ ### Rule: require-init-before-steps
43
45
 
44
- Ensures step functions (`given`, `when`, `then`, `and`, `but` and aliases) are called inside a `story()` or `doc.story(..., callback)`.
46
+ Ensures `story.init()` is called before any step markers.
45
47
 
46
48
  ```typescript
47
49
  // Fails lint
48
50
  it("my test", () => {
49
- given("something"); // Error: must be inside story() or doc.story()
51
+ story.given("something", () => {}); // Error: story.init() must be called first
52
+ story.init();
50
53
  });
51
54
 
52
55
  // Passes lint
53
56
  it("my test", () => {
54
57
  story.init();
55
- story.given("something");
58
+ story.given("something", () => {});
59
+ });
60
+ ```
61
+
62
+ Detects all step methods: `given`, `when`, `then`, `and`, `but`, `arrange`, `act`, `assert`, `setup`, `context`, `execute`, `action`, `verify`, `fn`, `expect`.
63
+
64
+ ### Rule: require-story-context-for-steps
65
+
66
+ Ensures bare step functions (`given`, `when`, `then`, `and`, `but` and aliases) are called inside a `story()` callback, `doc.story(..., callback)`, or a function that has `story.init()`.
67
+
68
+ ```typescript
69
+ // Fails lint
70
+ it("my test", () => {
71
+ given("something", () => {}); // Error: must be inside story() or story.init() scope
72
+ });
73
+
74
+ // Passes lint — inside story() callback
75
+ story("Login", () => {
76
+ given("a user", () => {});
77
+ when("they sign in", () => {});
78
+ then("they see the dashboard", () => {});
79
+ });
80
+
81
+ // Passes lint — story.init() in same scope
82
+ it("my test", () => {
83
+ story.init();
84
+ given("a user", () => {});
56
85
  });
57
86
  ```
58
87
 
@@ -109,3 +138,29 @@ export default [...jestStories.configs.recommended];
109
138
  This plugin only supports ESLint 9 flat config.
110
139
 
111
140
  Source: packages/eslint-plugin-executable-stories-jest/src/index.ts
141
+
142
+ ### MEDIUM Not scoping rules to story test files
143
+
144
+ ```typescript
145
+ // eslint.config.mjs
146
+ import jestStories from "eslint-plugin-executable-stories-jest";
147
+
148
+ export default [
149
+ {
150
+ // Scope to story test files only
151
+ files: ["**/*.story.test.ts", "**/*.story.spec.ts"],
152
+ plugins: {
153
+ "executable-stories-jest": jestStories,
154
+ },
155
+ rules: {
156
+ "executable-stories-jest/require-init-before-steps": "error",
157
+ "executable-stories-jest/require-story-context-for-steps": "error",
158
+ "executable-stories-jest/require-test-context-for-doc-story": "error",
159
+ },
160
+ },
161
+ ];
162
+ ```
163
+
164
+ Scoping avoids false positives on non-story test files that don't use the story API.
165
+
166
+ Source: packages/eslint-plugin-executable-stories-jest/src/index.ts