eslint-plugin-executable-stories-vitest 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
@@ -40,7 +40,9 @@ var STEP_NAMES = /* @__PURE__ */ new Set([
40
40
  "context",
41
41
  "execute",
42
42
  "action",
43
- "verify"
43
+ "verify",
44
+ "fn",
45
+ "expect"
44
46
  ]);
45
47
  function isFunction(node) {
46
48
  return node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/rules/require-init-before-steps.ts","../src/rules/require-task-for-story-init.ts","../src/rules/require-test-context-for-story-init.ts"],"sourcesContent":["import type { ESLint, Linter } from 'eslint';\nimport requireInitBeforeSteps from './rules/require-init-before-steps.js';\nimport requireTaskForStoryInit from './rules/require-task-for-story-init.js';\nimport requireTestContextForStoryInit from './rules/require-test-context-for-story-init.js';\n\nconst rules = {\n 'require-task-for-story-init': requireTaskForStoryInit,\n 'require-test-context-for-story-init': requireTestContextForStoryInit,\n 'require-init-before-steps': requireInitBeforeSteps,\n};\n\nconst configs: Record<string, Linter.Config[]> = {\n recommended: [\n {\n plugins: {\n 'executable-stories-vitest': { rules },\n },\n rules: {\n 'executable-stories-vitest/require-task-for-story-init': 'error',\n 'executable-stories-vitest/require-test-context-for-story-init':\n 'error',\n 'executable-stories-vitest/require-init-before-steps': 'error',\n },\n },\n ],\n};\n\nconst plugin: ESLint.Plugin = {\n meta: {\n name: 'eslint-plugin-executable-stories-vitest',\n version: '0.2.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(task) 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(task);\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]);\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(task) to be called before any step markers (story.given/when/then/etc) in Vitest.',\n recommended: true,\n },\n schema: [],\n messages: {\n requireInit:\n 'story.init(task) 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-vitest') {\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 { CallExpression, MemberExpression } from 'estree';\n\n/**\n * Rule: require-task-for-story-init\n *\n * In Vitest, story.init() requires the task argument.\n * Use: it('...', ({ task }) => { story.init(task); ... })\n *\n * BAD: story.init();\n * GOOD: story.init(task);\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\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n \"Require the task argument for story.init() in Vitest. Use it('...', ({ task }) => { story.init(task); ... }).\",\n recommended: true,\n },\n schema: [],\n messages: {\n requireTask:\n \"story.init(task) requires the task argument. Use it('...', ({ task }) => { story.init(task); ... }).\",\n },\n },\n\n create(context) {\n let hasRelevantImport = false;\n\n return {\n ImportDeclaration(node) {\n if (node.source.value === 'executable-stories-vitest') {\n hasRelevantImport = true;\n }\n },\n CallExpression(node: CallExpression) {\n if (!hasRelevantImport) return;\n if (!isStoryInitCall(node)) return;\n if (node.arguments.length >= 1) return;\n context.report({\n node,\n messageId: 'requireTask',\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 MemberExpression,\n Node,\n Property,\n VariableDeclarator,\n} from 'estree';\n\n/**\n * Rule: require-test-context-for-story-init\n *\n * Ensures story.init(task) is called inside a test/it callback.\n *\n * BAD: story.init(task); // at top level\n * GOOD: it('test', ({ task }) => { story.init(task); ... });\n */\n\nconst TEST_MODIFIERS = new Set(['only', 'skip', 'todo', 'concurrent', 'fails']);\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 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 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 story.init(task) to be called inside a test/it callback in Vitest.',\n recommended: true,\n },\n schema: [],\n messages: {\n requireTest:\n \"story.init(task) must be called inside a test/it callback (e.g. it('...', ({ task }) => { story.init(task); ... })).\",\n },\n },\n create(context) {\n let hasRelevantImport = false;\n const namedFunctions = new Map<string, Node>();\n const testCallbackNames = new Set<string>();\n const pendingStoryInitCalls: 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 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 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 === 'executable-stories-vitest') {\n hasRelevantImport = 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 (!hasRelevantImport) 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 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 (!isStoryInitCall(node)) return;\n if (insideTestCallback(node, context)) return;\n\n const containingFunctionName = getContainingFunctionName(node);\n pendingStoryInitCalls.push({ node, containingFunctionName });\n },\n 'Program:exit'() {\n for (const { node, containingFunctionName } of pendingStoryInitCalls) {\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":["isStoryInitCall","rule","isFunction","isStoryInitCall","rule"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/rules/require-init-before-steps.ts","../src/rules/require-task-for-story-init.ts","../src/rules/require-test-context-for-story-init.ts"],"sourcesContent":["import type { ESLint, Linter } from 'eslint';\nimport requireInitBeforeSteps from './rules/require-init-before-steps.js';\nimport requireTaskForStoryInit from './rules/require-task-for-story-init.js';\nimport requireTestContextForStoryInit from './rules/require-test-context-for-story-init.js';\n\nconst rules = {\n 'require-task-for-story-init': requireTaskForStoryInit,\n 'require-test-context-for-story-init': requireTestContextForStoryInit,\n 'require-init-before-steps': requireInitBeforeSteps,\n};\n\nconst configs: Record<string, Linter.Config[]> = {\n recommended: [\n {\n plugins: {\n 'executable-stories-vitest': { rules },\n },\n rules: {\n 'executable-stories-vitest/require-task-for-story-init': 'error',\n 'executable-stories-vitest/require-test-context-for-story-init':\n 'error',\n 'executable-stories-vitest/require-init-before-steps': 'error',\n },\n },\n ],\n};\n\nconst plugin: ESLint.Plugin = {\n meta: {\n name: 'eslint-plugin-executable-stories-vitest',\n version: '0.2.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(task) 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(task);\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(task) to be called before any step markers (story.given/when/then/etc) in Vitest.',\n recommended: true,\n },\n schema: [],\n messages: {\n requireInit:\n 'story.init(task) 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-vitest') {\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 { CallExpression, MemberExpression } from 'estree';\n\n/**\n * Rule: require-task-for-story-init\n *\n * In Vitest, story.init() requires the task argument.\n * Use: it('...', ({ task }) => { story.init(task); ... })\n *\n * BAD: story.init();\n * GOOD: story.init(task);\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\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n \"Require the task argument for story.init() in Vitest. Use it('...', ({ task }) => { story.init(task); ... }).\",\n recommended: true,\n },\n schema: [],\n messages: {\n requireTask:\n \"story.init(task) requires the task argument. Use it('...', ({ task }) => { story.init(task); ... }).\",\n },\n },\n\n create(context) {\n let hasRelevantImport = false;\n\n return {\n ImportDeclaration(node) {\n if (node.source.value === 'executable-stories-vitest') {\n hasRelevantImport = true;\n }\n },\n CallExpression(node: CallExpression) {\n if (!hasRelevantImport) return;\n if (!isStoryInitCall(node)) return;\n if (node.arguments.length >= 1) return;\n context.report({\n node,\n messageId: 'requireTask',\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 MemberExpression,\n Node,\n Property,\n VariableDeclarator,\n} from 'estree';\n\n/**\n * Rule: require-test-context-for-story-init\n *\n * Ensures story.init(task) is called inside a test/it callback.\n *\n * BAD: story.init(task); // at top level\n * GOOD: it('test', ({ task }) => { story.init(task); ... });\n */\n\nconst TEST_MODIFIERS = new Set(['only', 'skip', 'todo', 'concurrent', 'fails']);\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 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 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 story.init(task) to be called inside a test/it callback in Vitest.',\n recommended: true,\n },\n schema: [],\n messages: {\n requireTest:\n \"story.init(task) must be called inside a test/it callback (e.g. it('...', ({ task }) => { story.init(task); ... })).\",\n },\n },\n create(context) {\n let hasRelevantImport = false;\n const namedFunctions = new Map<string, Node>();\n const testCallbackNames = new Set<string>();\n const pendingStoryInitCalls: 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 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 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 === 'executable-stories-vitest') {\n hasRelevantImport = 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 (!hasRelevantImport) 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 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 (!isStoryInitCall(node)) return;\n if (insideTestCallback(node, context)) return;\n\n const containingFunctionName = getContainingFunctionName(node);\n pendingStoryInitCalls.push({ node, containingFunctionName });\n },\n 'Program:exit'() {\n for (const { node, containingFunctionName } of pendingStoryInitCalls) {\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":["isStoryInitCall","rule","isFunction","isStoryInitCall","rule"]}
package/dist/index.js CHANGED
@@ -12,7 +12,9 @@ var STEP_NAMES = /* @__PURE__ */ new Set([
12
12
  "context",
13
13
  "execute",
14
14
  "action",
15
- "verify"
15
+ "verify",
16
+ "fn",
17
+ "expect"
16
18
  ]);
17
19
  function isFunction(node) {
18
20
  return node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/rules/require-init-before-steps.ts","../src/rules/require-task-for-story-init.ts","../src/rules/require-test-context-for-story-init.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(task) 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(task);\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]);\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(task) to be called before any step markers (story.given/when/then/etc) in Vitest.',\n recommended: true,\n },\n schema: [],\n messages: {\n requireInit:\n 'story.init(task) 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-vitest') {\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 { CallExpression, MemberExpression } from 'estree';\n\n/**\n * Rule: require-task-for-story-init\n *\n * In Vitest, story.init() requires the task argument.\n * Use: it('...', ({ task }) => { story.init(task); ... })\n *\n * BAD: story.init();\n * GOOD: story.init(task);\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\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n \"Require the task argument for story.init() in Vitest. Use it('...', ({ task }) => { story.init(task); ... }).\",\n recommended: true,\n },\n schema: [],\n messages: {\n requireTask:\n \"story.init(task) requires the task argument. Use it('...', ({ task }) => { story.init(task); ... }).\",\n },\n },\n\n create(context) {\n let hasRelevantImport = false;\n\n return {\n ImportDeclaration(node) {\n if (node.source.value === 'executable-stories-vitest') {\n hasRelevantImport = true;\n }\n },\n CallExpression(node: CallExpression) {\n if (!hasRelevantImport) return;\n if (!isStoryInitCall(node)) return;\n if (node.arguments.length >= 1) return;\n context.report({\n node,\n messageId: 'requireTask',\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 MemberExpression,\n Node,\n Property,\n VariableDeclarator,\n} from 'estree';\n\n/**\n * Rule: require-test-context-for-story-init\n *\n * Ensures story.init(task) is called inside a test/it callback.\n *\n * BAD: story.init(task); // at top level\n * GOOD: it('test', ({ task }) => { story.init(task); ... });\n */\n\nconst TEST_MODIFIERS = new Set(['only', 'skip', 'todo', 'concurrent', 'fails']);\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 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 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 story.init(task) to be called inside a test/it callback in Vitest.',\n recommended: true,\n },\n schema: [],\n messages: {\n requireTest:\n \"story.init(task) must be called inside a test/it callback (e.g. it('...', ({ task }) => { story.init(task); ... })).\",\n },\n },\n create(context) {\n let hasRelevantImport = false;\n const namedFunctions = new Map<string, Node>();\n const testCallbackNames = new Set<string>();\n const pendingStoryInitCalls: 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 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 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 === 'executable-stories-vitest') {\n hasRelevantImport = 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 (!hasRelevantImport) 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 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 (!isStoryInitCall(node)) return;\n if (insideTestCallback(node, context)) return;\n\n const containingFunctionName = getContainingFunctionName(node);\n pendingStoryInitCalls.push({ node, containingFunctionName });\n },\n 'Program:exit'() {\n for (const { node, containingFunctionName } of pendingStoryInitCalls) {\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 requireTaskForStoryInit from './rules/require-task-for-story-init.js';\nimport requireTestContextForStoryInit from './rules/require-test-context-for-story-init.js';\n\nconst rules = {\n 'require-task-for-story-init': requireTaskForStoryInit,\n 'require-test-context-for-story-init': requireTestContextForStoryInit,\n 'require-init-before-steps': requireInitBeforeSteps,\n};\n\nconst configs: Record<string, Linter.Config[]> = {\n recommended: [\n {\n plugins: {\n 'executable-stories-vitest': { rules },\n },\n rules: {\n 'executable-stories-vitest/require-task-for-story-init': 'error',\n 'executable-stories-vitest/require-test-context-for-story-init':\n 'error',\n 'executable-stories-vitest/require-init-before-steps': 'error',\n },\n },\n ],\n};\n\nconst plugin: ESLint.Plugin = {\n meta: {\n name: 'eslint-plugin-executable-stories-vitest',\n version: '0.2.0',\n },\n rules,\n configs,\n};\n\nexport default plugin;\nexport { rules, configs };\n"],"mappings":"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","names":["isStoryInitCall","rule","isFunction","isStoryInitCall","rule"]}
1
+ {"version":3,"sources":["../src/rules/require-init-before-steps.ts","../src/rules/require-task-for-story-init.ts","../src/rules/require-test-context-for-story-init.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(task) 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(task);\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(task) to be called before any step markers (story.given/when/then/etc) in Vitest.',\n recommended: true,\n },\n schema: [],\n messages: {\n requireInit:\n 'story.init(task) 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-vitest') {\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 { CallExpression, MemberExpression } from 'estree';\n\n/**\n * Rule: require-task-for-story-init\n *\n * In Vitest, story.init() requires the task argument.\n * Use: it('...', ({ task }) => { story.init(task); ... })\n *\n * BAD: story.init();\n * GOOD: story.init(task);\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\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n \"Require the task argument for story.init() in Vitest. Use it('...', ({ task }) => { story.init(task); ... }).\",\n recommended: true,\n },\n schema: [],\n messages: {\n requireTask:\n \"story.init(task) requires the task argument. Use it('...', ({ task }) => { story.init(task); ... }).\",\n },\n },\n\n create(context) {\n let hasRelevantImport = false;\n\n return {\n ImportDeclaration(node) {\n if (node.source.value === 'executable-stories-vitest') {\n hasRelevantImport = true;\n }\n },\n CallExpression(node: CallExpression) {\n if (!hasRelevantImport) return;\n if (!isStoryInitCall(node)) return;\n if (node.arguments.length >= 1) return;\n context.report({\n node,\n messageId: 'requireTask',\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 MemberExpression,\n Node,\n Property,\n VariableDeclarator,\n} from 'estree';\n\n/**\n * Rule: require-test-context-for-story-init\n *\n * Ensures story.init(task) is called inside a test/it callback.\n *\n * BAD: story.init(task); // at top level\n * GOOD: it('test', ({ task }) => { story.init(task); ... });\n */\n\nconst TEST_MODIFIERS = new Set(['only', 'skip', 'todo', 'concurrent', 'fails']);\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 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 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 story.init(task) to be called inside a test/it callback in Vitest.',\n recommended: true,\n },\n schema: [],\n messages: {\n requireTest:\n \"story.init(task) must be called inside a test/it callback (e.g. it('...', ({ task }) => { story.init(task); ... })).\",\n },\n },\n create(context) {\n let hasRelevantImport = false;\n const namedFunctions = new Map<string, Node>();\n const testCallbackNames = new Set<string>();\n const pendingStoryInitCalls: 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 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 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 === 'executable-stories-vitest') {\n hasRelevantImport = 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 (!hasRelevantImport) 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 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 (!isStoryInitCall(node)) return;\n if (insideTestCallback(node, context)) return;\n\n const containingFunctionName = getContainingFunctionName(node);\n pendingStoryInitCalls.push({ node, containingFunctionName });\n },\n 'Program:exit'() {\n for (const { node, containingFunctionName } of pendingStoryInitCalls) {\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 requireTaskForStoryInit from './rules/require-task-for-story-init.js';\nimport requireTestContextForStoryInit from './rules/require-test-context-for-story-init.js';\n\nconst rules = {\n 'require-task-for-story-init': requireTaskForStoryInit,\n 'require-test-context-for-story-init': requireTestContextForStoryInit,\n 'require-init-before-steps': requireInitBeforeSteps,\n};\n\nconst configs: Record<string, Linter.Config[]> = {\n recommended: [\n {\n plugins: {\n 'executable-stories-vitest': { rules },\n },\n rules: {\n 'executable-stories-vitest/require-task-for-story-init': 'error',\n 'executable-stories-vitest/require-test-context-for-story-init':\n 'error',\n 'executable-stories-vitest/require-init-before-steps': 'error',\n },\n },\n ],\n};\n\nconst plugin: ESLint.Plugin = {\n meta: {\n name: 'eslint-plugin-executable-stories-vitest',\n version: '0.2.0',\n },\n rules,\n configs,\n};\n\nexport default plugin;\nexport { rules, configs };\n"],"mappings":";AAkBA,IAAM,aAAa,oBAAI,IAAI;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,WAAW,MAAqB;AACvC,SACE,KAAK,SAAS,wBACd,KAAK,SAAS,6BACd,KAAK,SAAS;AAElB;AAEA,SAAS,gBAAgB,MAA+B;AACtD,QAAM,EAAE,OAAO,IAAI;AACnB,MAAI,OAAO,SAAS,mBAAoB,QAAO;AAC/C,QAAM,SAAS;AACf,QAAM,EAAE,QAAQ,SAAS,IAAI;AAC7B,SACE,OAAO,SAAS,gBAChB,OAAO,SAAS,WAChB,SAAS,SAAS,gBAClB,SAAS,SAAS;AAEtB;AAEA,SAAS,gBAAgB,MAA+B;AACtD,QAAM,EAAE,OAAO,IAAI;AACnB,MAAI,OAAO,SAAS,mBAAoB,QAAO;AAC/C,QAAM,SAAS;AACf,QAAM,EAAE,QAAQ,SAAS,IAAI;AAC7B,SACE,OAAO,SAAS,gBAChB,OAAO,SAAS,WAChB,SAAS,SAAS,gBAClB,WAAW,IAAI,SAAS,IAAI;AAEhC;AAEA,SAAS,sBACP,MACA,SACa;AACb,QAAM,YAAY,QAAQ,WAAW,aAAa,IAAI;AACtD,WAAS,IAAI,UAAU,SAAS,GAAG,KAAK,GAAG,KAAK;AAC9C,QAAI,WAAW,UAAU,CAAC,CAAC,GAAG;AAC5B,aAAO,UAAU,CAAC;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;AAEA,IAAM,OAAwB;AAAA,EAC5B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,aACE;AAAA,MACF,aAAa;AAAA,IACf;AAAA,IACA,QAAQ,CAAC;AAAA,IACT,UAAU;AAAA,MACR,aACE;AAAA,IACJ;AAAA,EACF;AAAA,EACA,OAAO,SAAS;AACd,QAAI,oBAAoB;AAExB,UAAM,oBAAoB,oBAAI,QAAc;AAE5C,UAAM,mBAGD,CAAC;AAEN,WAAO;AAAA,MACL,kBAAkB,MAAM;AACtB,YAAI,KAAK,OAAO,UAAU,6BAA6B;AACrD,8BAAoB;AAAA,QACtB;AAAA,MACF;AAAA,MACA,eAAe,MAAsB;AACnC,YAAI,CAAC,kBAAmB;AAExB,YAAI,gBAAgB,IAAI,GAAG;AACzB,gBAAM,qBAAqB,sBAAsB,MAAM,OAAO;AAC9D,cAAI,oBAAoB;AACtB,8BAAkB,IAAI,kBAAkB;AAAA,UAC1C;AACA;AAAA,QACF;AAEA,YAAI,gBAAgB,IAAI,GAAG;AACzB,gBAAM,qBAAqB,sBAAsB,MAAM,OAAO;AAC9D,2BAAiB,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAAA,QACpD;AAAA,MACF;AAAA,MACA,iBAAiB;AACf,mBAAW,EAAE,MAAM,mBAAmB,KAAK,kBAAkB;AAE3D,cACE,CAAC,sBACD,CAAC,kBAAkB,IAAI,kBAAkB,GACzC;AACA,oBAAQ,OAAO,EAAE,MAAM,WAAW,cAAc,CAAC;AAAA,UACnD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,oCAAQ;;;ACnIf,SAASA,iBAAgB,MAA+B;AACtD,QAAM,EAAE,OAAO,IAAI;AACnB,MAAI,OAAO,SAAS,mBAAoB,QAAO;AAC/C,QAAM,SAAS;AACf,QAAM,EAAE,QAAQ,SAAS,IAAI;AAC7B,SACE,OAAO,SAAS,gBAChB,OAAO,SAAS,WAChB,SAAS,SAAS,gBAClB,SAAS,SAAS;AAEtB;AAEA,IAAMC,QAAwB;AAAA,EAC5B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,aACE;AAAA,MACF,aAAa;AAAA,IACf;AAAA,IACA,QAAQ,CAAC;AAAA,IACT,UAAU;AAAA,MACR,aACE;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,OAAO,SAAS;AACd,QAAI,oBAAoB;AAExB,WAAO;AAAA,MACL,kBAAkB,MAAM;AACtB,YAAI,KAAK,OAAO,UAAU,6BAA6B;AACrD,8BAAoB;AAAA,QACtB;AAAA,MACF;AAAA,MACA,eAAe,MAAsB;AACnC,YAAI,CAAC,kBAAmB;AACxB,YAAI,CAACD,iBAAgB,IAAI,EAAG;AAC5B,YAAI,KAAK,UAAU,UAAU,EAAG;AAChC,gBAAQ,OAAO;AAAA,UACb;AAAA,UACA,WAAW;AAAA,QACb,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,sCAAQC;;;AC1Cf,IAAM,iBAAiB,oBAAI,IAAI,CAAC,QAAQ,QAAQ,QAAQ,cAAc,OAAO,CAAC;AAE9E,SAASC,YAAW,MAAqB;AACvC,SACE,KAAK,SAAS,wBACd,KAAK,SAAS;AAElB;AAEA,SAAS,eACP,MAC4E;AAC5E,SACE,KAAK,SAAS,yBACd,KAAK,SAAS,wBACd,KAAK,SAAS;AAElB;AAEA,SAASC,iBAAgB,MAA+B;AACtD,QAAM,EAAE,OAAO,IAAI;AACnB,MAAI,OAAO,SAAS,mBAAoB,QAAO;AAC/C,QAAM,SAAS;AACf,QAAM,EAAE,QAAQ,SAAS,IAAI;AAC7B,SACE,OAAO,SAAS,gBAChB,OAAO,SAAS,WAChB,SAAS,SAAS,gBAClB,SAAS,SAAS;AAEtB;AAEA,SAAS,qBAAqB,MAA+B;AAC3D,QAAM,EAAE,OAAO,IAAI;AACnB,MAAI,OAAO,SAAS,cAAc;AAChC,WAAO,OAAO,SAAS,UAAU,OAAO,SAAS;AAAA,EACnD;AACA,MAAI,OAAO,SAAS,oBAAoB;AACtC,QAAI,OAAO,OAAO,SAAS,aAAc,QAAO;AAChD,QAAI,OAAO,OAAO,SAAS,UAAU,OAAO,OAAO,SAAS;AAC1D,aAAO;AACT,QAAI,OAAO,SAAS,SAAS,aAAc,QAAO;AAClD,WAAO,eAAe,IAAI,OAAO,SAAS,IAAI;AAAA,EAChD;AACA,MAAI,OAAO,SAAS,kBAAkB;AACpC,UAAM,QAAQ,OAAO;AACrB,QAAI,MAAM,SAAS,mBAAoB,QAAO;AAC9C,QAAI,MAAM,OAAO,SAAS,aAAc,QAAO;AAC/C,QAAI,MAAM,OAAO,SAAS,UAAU,MAAM,OAAO,SAAS;AACxD,aAAO;AACT,WACE,MAAM,SAAS,SAAS,gBAAgB,MAAM,SAAS,SAAS;AAAA,EAEpE;AACA,SAAO;AACT;AAEA,SAAS,mBACP,MACA,SACS;AACT,QAAM,YAAY,QAAQ,WAAW,aAAa,IAAI;AACtD,QAAM,oBAAoB,IAAI,IAAI,UAAU,OAAOD,WAAU,CAAC;AAE9D,aAAW,YAAY,WAAW;AAChC,QAAI,SAAS,SAAS,iBAAkB;AACxC,QAAI,CAAC,qBAAqB,QAAQ,EAAG;AACrC,eAAW,OAAO,SAAS,WAAW;AACpC,UAAI,OAAO,OAAO,QAAQ,YAAY,kBAAkB,IAAI,GAAG,GAAG;AAChE,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,IAAME,QAAwB;AAAA,EAC5B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,aACE;AAAA,MACF,aAAa;AAAA,IACf;AAAA,IACA,QAAQ,CAAC;AAAA,IACT,UAAU;AAAA,MACR,aACE;AAAA,IACJ;AAAA,EACF;AAAA,EACA,OAAO,SAAS;AACd,QAAI,oBAAoB;AACxB,UAAM,iBAAiB,oBAAI,IAAkB;AAC7C,UAAM,oBAAoB,oBAAI,IAAY;AAC1C,UAAM,wBAGD,CAAC;AAEN,aAAS,0BAA0B,MAAqC;AACtE,YAAM,YAAY,QAAQ,WAAW,aAAa,IAAI;AACtD,eAAS,IAAI,UAAU,SAAS,GAAG,KAAK,GAAG,KAAK;AAC9C,cAAM,WAAW,UAAU,CAAC;AAC5B,YAAI,SAAS,SAAS,yBAAyB,SAAS,IAAI;AAC1D,iBAAO,SAAS,GAAG;AAAA,QACrB;AACA,YAAI,SAAS,SAAS,sBAAsB;AAC1C,gBAAM,aAAa;AACnB,cACE,WAAW,GAAG,SAAS,gBACvB,WAAW,QACX,eAAe,WAAW,IAAI,GAC9B;AACA,mBAAO,WAAW,GAAG;AAAA,UACvB;AAAA,QACF;AACA,YAAI,SAAS,SAAS,YAAY;AAChC,gBAAM,OAAO;AACb,cACE,KAAK,IAAI,SAAS,gBAClB,KAAK,SACL,eAAe,KAAK,KAAK,GACzB;AACA,mBAAO,KAAK,IAAI;AAAA,UAClB;AACA,cAAI,KAAK,IAAI,SAAS,gBAAgB,KAAK,QAAQ;AACjD,mBAAO,KAAK,IAAI;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL,kBAAkB,MAAM;AACtB,YAAI,KAAK,OAAO,UAAU,6BAA6B;AACrD,8BAAoB;AAAA,QACtB;AAAA,MACF;AAAA,MACA,oBAAoB,MAA2B;AAC7C,YAAI,KAAK,IAAI;AACX,yBAAe,IAAI,KAAK,GAAG,MAAM,IAAI;AAAA,QACvC;AAAA,MACF;AAAA,MACA,mBAAmB,MAA0B;AAC3C,YACE,KAAK,GAAG,SAAS,gBACjB,KAAK,QACL,eAAe,KAAK,IAAI,GACxB;AACA,yBAAe,IAAI,KAAK,GAAG,MAAM,KAAK,IAAI;AAAA,QAC5C;AAAA,MACF;AAAA,MACA,eAAe,MAAsB;AACnC,YAAI,CAAC,kBAAmB;AAExB,YAAI,qBAAqB,IAAI,GAAG;AAC9B,qBAAW,OAAO,KAAK,WAAW;AAChC,gBAAI,IAAI,SAAS,cAAc;AAC7B,gCAAkB,IAAI,IAAI,IAAI;AAAA,YAChC;AACA,gBACE,IAAI,SAAS,sBACb,IAAI,SAAS,SAAS,cACtB;AACA,gCAAkB,IAAI,IAAI,SAAS,IAAI;AAAA,YACzC;AAAA,UACF;AACA;AAAA,QACF;AAEA,YAAI,CAACD,iBAAgB,IAAI,EAAG;AAC5B,YAAI,mBAAmB,MAAM,OAAO,EAAG;AAEvC,cAAM,yBAAyB,0BAA0B,IAAI;AAC7D,8BAAsB,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAAA,MAC7D;AAAA,MACA,iBAAiB;AACf,mBAAW,EAAE,MAAM,uBAAuB,KAAK,uBAAuB;AACpE,cACE,0BACA,kBAAkB,IAAI,sBAAsB,GAC5C;AACA;AAAA,UACF;AACA,kBAAQ,OAAO,EAAE,MAAM,WAAW,cAAc,CAAC;AAAA,QACnD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,8CAAQC;;;AChNf,IAAM,QAAQ;AAAA,EACZ,+BAA+B;AAAA,EAC/B,uCAAuC;AAAA,EACvC,6BAA6B;AAC/B;AAEA,IAAM,UAA2C;AAAA,EAC/C,aAAa;AAAA,IACX;AAAA,MACE,SAAS;AAAA,QACP,6BAA6B,EAAE,MAAM;AAAA,MACvC;AAAA,MACA,OAAO;AAAA,QACL,yDAAyD;AAAA,QACzD,iEACE;AAAA,QACF,uDAAuD;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAM,SAAwB;AAAA,EAC5B,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,SAAS;AAAA,EACX;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAO,cAAQ;","names":["isStoryInitCall","rule","isFunction","isStoryInitCall","rule"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-executable-stories-vitest",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "type": "module",
5
5
  "description": "ESLint rules for executable-stories-vitest: step context, doc.story in test/it, require task for doc.story(title)",
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"
@@ -96,7 +96,7 @@ it("my test", ({ task }) => {
96
96
  });
97
97
  ```
98
98
 
99
- Detects all step methods: `given`, `when`, `then`, `and`, `but`, `arrange`, `act`, `assert`, `setup`, `context`, `execute`, `action`, `verify`.
99
+ Detects all step methods: `given`, `when`, `then`, `and`, `but`, `arrange`, `act`, `assert`, `setup`, `context`, `execute`, `action`, `verify`, `fn`, `expect`.
100
100
 
101
101
  ## Common Mistakes
102
102