eslint-plugin-executable-stories-playwright 2.0.0 → 2.1.1
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 +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -73,7 +73,7 @@ function isStepCall(node) {
|
|
|
73
73
|
return callee.property.type === "Identifier" && STEP_NAMES.has(callee.property.name);
|
|
74
74
|
}
|
|
75
75
|
function insideStoryCallback(node, context) {
|
|
76
|
-
const ancestors = context.
|
|
76
|
+
const ancestors = context.sourceCode.getAncestors(node);
|
|
77
77
|
const functionAncestors = new Set(ancestors.filter(isFunction));
|
|
78
78
|
for (const ancestor of ancestors) {
|
|
79
79
|
if (ancestor.type !== "CallExpression") continue;
|
|
@@ -104,7 +104,7 @@ var rule = {
|
|
|
104
104
|
const storyCallbackNames = /* @__PURE__ */ new Set();
|
|
105
105
|
const pendingStepCalls = [];
|
|
106
106
|
function getContainingFunctionName(node) {
|
|
107
|
-
const ancestors = context.
|
|
107
|
+
const ancestors = context.sourceCode.getAncestors(node);
|
|
108
108
|
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
109
109
|
const ancestor = ancestors[i];
|
|
110
110
|
if (ancestor.type === "FunctionDeclaration" && ancestor.id) {
|
|
@@ -202,7 +202,7 @@ function isTestCallExpression(node) {
|
|
|
202
202
|
return TEST_MODIFIERS.has(callee.property.name);
|
|
203
203
|
}
|
|
204
204
|
function insideTestCallback(node, context) {
|
|
205
|
-
const ancestors = context.
|
|
205
|
+
const ancestors = context.sourceCode.getAncestors(node);
|
|
206
206
|
const functionAncestors = new Set(ancestors.filter(isFunction2));
|
|
207
207
|
for (const ancestor of ancestors) {
|
|
208
208
|
if (ancestor.type !== "CallExpression") continue;
|
|
@@ -234,7 +234,7 @@ var rule2 = {
|
|
|
234
234
|
const testCallbackNames = /* @__PURE__ */ new Set();
|
|
235
235
|
const pendingDocStoryCalls = [];
|
|
236
236
|
function getContainingFunctionName(node) {
|
|
237
|
-
const ancestors = context.
|
|
237
|
+
const ancestors = context.sourceCode.getAncestors(node);
|
|
238
238
|
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
239
239
|
const ancestor = ancestors[i];
|
|
240
240
|
if (ancestor.type === "FunctionDeclaration" && ancestor.id) {
|
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-playwright': { rules },\n },\n rules: {\n 'executable-stories-playwright/require-story-context-for-steps':\n 'error',\n 'executable-stories-playwright/require-test-context-for-doc-story':\n 'error',\n },\n },\n ],\n};\n\nconst plugin: ESLint.Plugin = {\n meta: {\n name: 'eslint-plugin-executable-stories-playwright',\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-playwright';\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.getSourceCode().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.getSourceCode().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 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 (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\n/** Legacy doc.story() is not used; current API uses story.init(testInfo) inside test(). */\nconst V1_PACKAGE = 'executable-stories-playwright';\n\nconst TEST_MODIFIERS = new Set(['only', 'skip', 'fixme', 'fail', 'slow']);\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 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') return false;\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\nfunction insideTestCallback(\n node: CallExpression,\n context: Rule.RuleContext,\n): boolean {\n const ancestors = context.getSourceCode().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() or it() callback (framework-native pattern).',\n recommended: true,\n },\n schema: [],\n messages: {\n requireTest:\n \"doc.story(title) must be called inside a test() callback (e.g. test('...', () => { 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.getSourceCode().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":["V1_PACKAGE","isFunction","isFunctionNode","isDocStoryCall","rule"]}
|
|
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-playwright': { rules },\n },\n rules: {\n 'executable-stories-playwright/require-story-context-for-steps':\n 'error',\n 'executable-stories-playwright/require-test-context-for-doc-story':\n 'error',\n },\n },\n ],\n};\n\nconst plugin: ESLint.Plugin = {\n meta: {\n name: 'eslint-plugin-executable-stories-playwright',\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-playwright';\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 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 (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\n/** Legacy doc.story() is not used; current API uses story.init(testInfo) inside test(). */\nconst V1_PACKAGE = 'executable-stories-playwright';\n\nconst TEST_MODIFIERS = new Set(['only', 'skip', 'fixme', 'fail', 'slow']);\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 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') return false;\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\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() or it() callback (framework-native pattern).',\n recommended: true,\n },\n schema: [],\n messages: {\n requireTest:\n \"doc.story(title) must be called inside a test() callback (e.g. test('...', () => { 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":["V1_PACKAGE","isFunction","isFunctionNode","isDocStoryCall","rule"]}
|
package/dist/index.js
CHANGED
|
@@ -45,7 +45,7 @@ function isStepCall(node) {
|
|
|
45
45
|
return callee.property.type === "Identifier" && STEP_NAMES.has(callee.property.name);
|
|
46
46
|
}
|
|
47
47
|
function insideStoryCallback(node, context) {
|
|
48
|
-
const ancestors = context.
|
|
48
|
+
const ancestors = context.sourceCode.getAncestors(node);
|
|
49
49
|
const functionAncestors = new Set(ancestors.filter(isFunction));
|
|
50
50
|
for (const ancestor of ancestors) {
|
|
51
51
|
if (ancestor.type !== "CallExpression") continue;
|
|
@@ -76,7 +76,7 @@ var rule = {
|
|
|
76
76
|
const storyCallbackNames = /* @__PURE__ */ new Set();
|
|
77
77
|
const pendingStepCalls = [];
|
|
78
78
|
function getContainingFunctionName(node) {
|
|
79
|
-
const ancestors = context.
|
|
79
|
+
const ancestors = context.sourceCode.getAncestors(node);
|
|
80
80
|
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
81
81
|
const ancestor = ancestors[i];
|
|
82
82
|
if (ancestor.type === "FunctionDeclaration" && ancestor.id) {
|
|
@@ -174,7 +174,7 @@ function isTestCallExpression(node) {
|
|
|
174
174
|
return TEST_MODIFIERS.has(callee.property.name);
|
|
175
175
|
}
|
|
176
176
|
function insideTestCallback(node, context) {
|
|
177
|
-
const ancestors = context.
|
|
177
|
+
const ancestors = context.sourceCode.getAncestors(node);
|
|
178
178
|
const functionAncestors = new Set(ancestors.filter(isFunction2));
|
|
179
179
|
for (const ancestor of ancestors) {
|
|
180
180
|
if (ancestor.type !== "CallExpression") continue;
|
|
@@ -206,7 +206,7 @@ var rule2 = {
|
|
|
206
206
|
const testCallbackNames = /* @__PURE__ */ new Set();
|
|
207
207
|
const pendingDocStoryCalls = [];
|
|
208
208
|
function getContainingFunctionName(node) {
|
|
209
|
-
const ancestors = context.
|
|
209
|
+
const ancestors = context.sourceCode.getAncestors(node);
|
|
210
210
|
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
211
211
|
const ancestor = ancestors[i];
|
|
212
212
|
if (ancestor.type === "FunctionDeclaration" && ancestor.id) {
|
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-playwright';\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.getSourceCode().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.getSourceCode().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 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 (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\n/** Legacy doc.story() is not used; current API uses story.init(testInfo) inside test(). */\nconst V1_PACKAGE = 'executable-stories-playwright';\n\nconst TEST_MODIFIERS = new Set(['only', 'skip', 'fixme', 'fail', 'slow']);\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 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') return false;\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\nfunction insideTestCallback(\n node: CallExpression,\n context: Rule.RuleContext,\n): boolean {\n const ancestors = context.getSourceCode().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() or it() callback (framework-native pattern).',\n recommended: true,\n },\n schema: [],\n messages: {\n requireTest:\n \"doc.story(title) must be called inside a test() callback (e.g. test('...', () => { 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.getSourceCode().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-playwright': { rules },\n },\n rules: {\n 'executable-stories-playwright/require-story-context-for-steps':\n 'error',\n 'executable-stories-playwright/require-test-context-for-doc-story':\n 'error',\n },\n },\n ],\n};\n\nconst plugin: ESLint.Plugin = {\n meta: {\n name: 'eslint-plugin-executable-stories-playwright',\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":["V1_PACKAGE","isFunction","isFunctionNode","isDocStoryCall","rule"]}
|
|
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-playwright';\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 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 (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\n/** Legacy doc.story() is not used; current API uses story.init(testInfo) inside test(). */\nconst V1_PACKAGE = 'executable-stories-playwright';\n\nconst TEST_MODIFIERS = new Set(['only', 'skip', 'fixme', 'fail', 'slow']);\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 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') return false;\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\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() or it() callback (framework-native pattern).',\n recommended: true,\n },\n schema: [],\n messages: {\n requireTest:\n \"doc.story(title) must be called inside a test() callback (e.g. test('...', () => { 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-playwright': { rules },\n },\n rules: {\n 'executable-stories-playwright/require-story-context-for-steps':\n 'error',\n 'executable-stories-playwright/require-test-context-for-doc-story':\n 'error',\n },\n },\n ],\n};\n\nconst plugin: ESLint.Plugin = {\n meta: {\n name: 'eslint-plugin-executable-stories-playwright',\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":["V1_PACKAGE","isFunction","isFunctionNode","isDocStoryCall","rule"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-executable-stories-playwright",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ESLint rules for executable-stories-playwright: step context, doc.story in test/it",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/eslint": "^9.6.1",
|
|
40
40
|
"@types/estree": "^1.0.8",
|
|
41
|
-
"@types/node": "^25.2
|
|
42
|
-
"eslint": "^
|
|
41
|
+
"@types/node": "^25.3.2",
|
|
42
|
+
"eslint": "^10.0.1",
|
|
43
43
|
"tsup": "^8.5.1",
|
|
44
44
|
"typescript": "^5.9.3",
|
|
45
45
|
"vitest": "^4.0.18",
|