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 +133 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +133 -13
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/skills/eslint-jest-rules/SKILL.md +63 -8
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
|
|
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
|
|
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
|
|
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" &&
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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" &&
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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.
|
|
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.
|
|
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
|
|
47
|
+
"vitest": "^4.1.0",
|
|
48
48
|
"eslint-config-executable-stories": "0.2.0",
|
|
49
|
-
"executable-stories-vitest": "
|
|
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.
|
|
5
|
-
require-
|
|
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
|
|
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-
|
|
44
|
+
### Rule: require-init-before-steps
|
|
43
45
|
|
|
44
|
-
Ensures
|
|
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
|
|
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
|