eslint-plugin-traceability 1.11.0 → 1.11.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +3 -4
  2. package/README.md +2 -2
  3. package/lib/src/maintenance/flags.js +111 -25
  4. package/lib/src/rules/helpers/require-story-core.d.ts +59 -0
  5. package/lib/src/rules/helpers/require-story-core.js +91 -1
  6. package/lib/src/rules/helpers/require-story-helpers.d.ts +2 -47
  7. package/lib/src/rules/helpers/require-story-helpers.js +135 -126
  8. package/lib/src/rules/helpers/require-story-io.js +51 -31
  9. package/lib/src/rules/helpers/valid-annotation-options.d.ts +3 -0
  10. package/lib/src/rules/helpers/valid-annotation-options.js +64 -21
  11. package/lib/src/utils/annotation-checker.js +31 -7
  12. package/lib/src/utils/reqAnnotationDetection.js +36 -22
  13. package/lib/tests/cli-error-handling.test.js +1 -0
  14. package/lib/tests/config/eslint-config-validation.test.d.ts +8 -0
  15. package/lib/tests/config/eslint-config-validation.test.js +8 -0
  16. package/lib/tests/config/flat-config-presets-integration.test.js +1 -3
  17. package/lib/tests/config/require-story-annotation-config.test.d.ts +9 -0
  18. package/lib/tests/config/require-story-annotation-config.test.js +9 -0
  19. package/lib/tests/integration/cli-integration.test.js +9 -1
  20. package/lib/tests/maintenance/batch.test.js +1 -0
  21. package/lib/tests/maintenance/cli.test.js +1 -0
  22. package/lib/tests/maintenance/detect-isolated.test.js +1 -0
  23. package/lib/tests/maintenance/detect.test.js +1 -0
  24. package/lib/tests/maintenance/index.test.js +1 -0
  25. package/lib/tests/maintenance/report.test.js +1 -0
  26. package/lib/tests/maintenance/update-isolated.test.js +1 -0
  27. package/lib/tests/maintenance/update.test.js +1 -0
  28. package/lib/tests/plugin-default-export-and-configs.test.js +2 -0
  29. package/lib/tests/plugin-setup-error.test.d.ts +1 -0
  30. package/lib/tests/plugin-setup-error.test.js +1 -0
  31. package/lib/tests/plugin-setup.test.js +1 -1
  32. package/lib/tests/rules/auto-fix-behavior-008.test.js +16 -0
  33. package/lib/tests/rules/error-reporting.test.js +1 -0
  34. package/lib/tests/rules/prefer-implements-annotation.test.js +8 -0
  35. package/lib/tests/rules/require-branch-annotation.test.js +2 -0
  36. package/lib/tests/rules/require-story-core-edgecases.test.js +1 -0
  37. package/lib/tests/rules/require-story-core.autofix.test.js +1 -0
  38. package/lib/tests/rules/require-story-core.test.js +1 -0
  39. package/lib/tests/rules/require-story-helpers-edgecases.test.d.ts +1 -0
  40. package/lib/tests/rules/require-story-helpers-edgecases.test.js +1 -0
  41. package/lib/tests/rules/require-story-helpers.test.js +4 -3
  42. package/lib/tests/rules/require-story-io-behavior.test.d.ts +1 -0
  43. package/lib/tests/rules/require-story-io-behavior.test.js +1 -0
  44. package/lib/tests/rules/require-story-io.edgecases.test.d.ts +1 -0
  45. package/lib/tests/rules/require-story-io.edgecases.test.js +1 -0
  46. package/lib/tests/rules/require-story-visitors-edgecases.test.d.ts +1 -0
  47. package/lib/tests/rules/require-story-visitors-edgecases.test.js +1 -0
  48. package/lib/tests/rules/valid-story-reference.test.js +2 -0
  49. package/lib/tests/utils/annotation-checker.test.js +2 -1
  50. package/lib/tests/utils/branch-annotation-helpers.test.js +2 -1
  51. package/package.json +1 -1
  52. package/user-docs/api-reference.md +117 -10
  53. package/user-docs/examples.md +2 -3
  54. package/user-docs/migration-guide.md +36 -3
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fallbackTextBeforeHasStory = exports.parentChainHasStory = exports.linesBeforeHasStory = exports.EXPORT_PRIORITY_VALUES = exports.DEFAULT_SCOPE = exports.getNodeName = exports.FALLBACK_WINDOW = exports.LOOKBACK_LINES = exports.STORY_PATH = void 0;
3
+ exports.fallbackTextBeforeHasStory = exports.parentChainHasStory = exports.linesBeforeHasStory = exports.EXPORT_PRIORITY_VALUES = exports.DEFAULT_SCOPE = exports.getNodeName = exports.STORY_PATH = void 0;
4
4
  exports.getAnnotationTemplate = getAnnotationTemplate;
5
5
  exports.shouldApplyAutoFix = shouldApplyAutoFix;
6
6
  exports.isExportedNode = isExportedNode;
@@ -22,11 +22,7 @@ Object.defineProperty(exports, "getNodeName", { enumerable: true, get: function
22
22
  const require_story_core_1 = require("./require-story-core");
23
23
  Object.defineProperty(exports, "DEFAULT_SCOPE", { enumerable: true, get: function () { return require_story_core_1.DEFAULT_SCOPE; } });
24
24
  Object.defineProperty(exports, "EXPORT_PRIORITY_VALUES", { enumerable: true, get: function () { return require_story_core_1.EXPORT_PRIORITY_VALUES; } });
25
- /**
26
- * Path to the story file for annotations
27
- */
28
- const STORY_PATH = "docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md";
29
- exports.STORY_PATH = STORY_PATH;
25
+ Object.defineProperty(exports, "STORY_PATH", { enumerable: true, get: function () { return require_story_core_1.STORY_PATH; } });
30
26
  /**
31
27
  * Derive the annotation template, optionally using an override.
32
28
  * When override is a non-empty string, its trimmed value is used.
@@ -36,7 +32,7 @@ function getAnnotationTemplate(override) {
36
32
  if (typeof override === "string" && override.trim().length > 0) {
37
33
  return override.trim();
38
34
  }
39
- return `/** @story ${STORY_PATH} */`;
35
+ return `/** @story ${require_story_core_1.STORY_PATH} */`;
40
36
  }
41
37
  /**
42
38
  * Determine whether auto-fix should be applied.
@@ -48,20 +44,6 @@ function shouldApplyAutoFix(autoFix) {
48
44
  }
49
45
  return true;
50
46
  }
51
- /**
52
- * Number of physical source lines to inspect before a node when searching for @story text
53
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
54
- * @req REQ-ANNOTATION-REQUIRED - Replace magic number for lookback lines with named constant
55
- */
56
- const LOOKBACK_LINES = 4;
57
- exports.LOOKBACK_LINES = LOOKBACK_LINES;
58
- /**
59
- * Window (in characters) to inspect before a node as a fallback when searching for @story text
60
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
61
- * @req REQ-ANNOTATION-REQUIRED - Replace magic number for fallback text window with named constant
62
- */
63
- const FALLBACK_WINDOW = 800;
64
- exports.FALLBACK_WINDOW = FALLBACK_WINDOW;
65
47
  /**
66
48
  * Determine if a node is in an export declaration
67
49
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
@@ -147,7 +129,7 @@ function hasStoryAnnotation(sourceCode, node) {
147
129
  if (leadingCommentsHasStory(node)) {
148
130
  return true;
149
131
  }
150
- if ((0, require_story_io_1.linesBeforeHasStory)(sourceCode, node, LOOKBACK_LINES)) {
132
+ if ((0, require_story_io_1.linesBeforeHasStory)(sourceCode, node)) {
151
133
  return true;
152
134
  }
153
135
  if ((0, require_story_io_1.parentChainHasStory)(sourceCode, node)) {
@@ -194,6 +176,54 @@ function resolveTargetNode(sourceCode, node) {
194
176
  }
195
177
  return node;
196
178
  }
179
+ /**
180
+ * Extract a direct Identifier name when available on the given node.
181
+ * This focuses only on plain Identifier nodes and ignores container shapes.
182
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
183
+ * @req REQ-ANNOTATION-REQUIRED - Extract direct Identifier-based names from nodes
184
+ * @param {any} node - AST node to inspect
185
+ * @returns {string | null} identifier name or null when not applicable
186
+ */
187
+ function getDirectIdentifierName(node) {
188
+ if (node &&
189
+ node.type === "Identifier" &&
190
+ typeof node.name === "string" &&
191
+ node.name.length > 0) {
192
+ return node.name;
193
+ }
194
+ return null;
195
+ }
196
+ /**
197
+ * Normalize container nodes that expose names via id/key properties.
198
+ * Supports common function and method containers, including literal keys.
199
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
200
+ * @req REQ-ANNOTATION-REQUIRED - Normalize container id/key-based names into a single helper
201
+ * @param {any} node - AST node that may contain id/key name information
202
+ * @returns {string | null} resolved container name or null when unavailable
203
+ */
204
+ function getContainerKeyOrIdName(node) {
205
+ if (!node) {
206
+ return null;
207
+ }
208
+ if (node.id) {
209
+ const idName = (0, require_story_utils_1.getNodeName)(node.id);
210
+ if (typeof idName === "string" && idName.length > 0) {
211
+ return idName;
212
+ }
213
+ }
214
+ if (node.key) {
215
+ const keyName = (0, require_story_utils_1.getNodeName)(node.key);
216
+ if (typeof keyName === "string" && keyName.length > 0) {
217
+ return keyName;
218
+ }
219
+ if (node.key.type === "Literal" &&
220
+ typeof node.key.value === "string" &&
221
+ node.key.value.length > 0) {
222
+ return node.key.value;
223
+ }
224
+ }
225
+ return null;
226
+ }
197
227
  /**
198
228
  * Small utility to walk the node and its parents to extract an Identifier or key name.
199
229
  * Walks up the parent chain and inspects common properties (id, key, name, Identifier nodes).
@@ -203,33 +233,21 @@ function resolveTargetNode(sourceCode, node) {
203
233
  * @returns {string} extracted name or "(anonymous)" when no name found
204
234
  */
205
235
  function extractName(node) {
206
- let n = node;
207
- while (n) {
208
- // Direct Identifier node
209
- if (n.type === "Identifier" && typeof n.name === "string") {
210
- return n.name;
211
- }
212
- // id property (FunctionDeclaration, etc.)
213
- if (n.id && n.id.type === "Identifier" && typeof n.id.name === "string") {
214
- return n.id.name;
236
+ let current = node;
237
+ while (current) {
238
+ const directIdentifierName = getDirectIdentifierName(current);
239
+ if (directIdentifierName) {
240
+ return directIdentifierName;
215
241
  }
216
- // key property (Property, MethodDefinition, etc.)
217
- if (n.key &&
218
- n.key.type === "Identifier" &&
219
- typeof n.key.name === "string") {
220
- return n.key.name;
242
+ const containerName = getContainerKeyOrIdName(current);
243
+ if (containerName) {
244
+ return containerName;
221
245
  }
222
- // name property (some nodes may have a 'name' string directly)
223
- if (typeof n.name === "string" && n.name.length > 0) {
224
- return n.name;
246
+ const directName = current.name;
247
+ if (typeof directName === "string" && directName.length > 0) {
248
+ return directName;
225
249
  }
226
- // computed keys may have an Identifier inside
227
- if (n.key &&
228
- n.key.type === "Literal" &&
229
- typeof n.key.value === "string") {
230
- return n.key.value;
231
- }
232
- n = n.parent;
250
+ current = current.parent;
233
251
  }
234
252
  return "(anonymous)";
235
253
  }
@@ -256,94 +274,85 @@ function shouldProcessNode(node, scope, exportPriority = "all") {
256
274
  return true;
257
275
  }
258
276
  /**
259
- * Report a missing @story annotation for a function-like node
260
- * Provides a suggestion to add the annotation.
277
+ * Resolve the effective function name to report for a node.
278
+ * Normalizes id/key handling before delegating to extractName.
261
279
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
262
- * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
263
- * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
264
- * @req REQ-ANNOTATION-REQUIRED - Implement reporting for missing annotations with suggestion
265
- * @req REQ-AUTOFIX-MISSING - Provide autofix for missing annotations while preserving suggestions
266
- * @req REQ-ERROR-SPECIFIC - Error reports must include both name and functionName in the data payload for specific function context
267
- * @param {Rule.RuleContext} context - ESLint rule context used to report
268
- * @param {any} sourceCode - ESLint sourceCode object
269
- * @param {{ node: any; target?: any; options?: ReportOptions }} config - configuration containing the node to report, optional insertion target, and optional report options
280
+ * @req REQ-ANNOTATION-REQUIRED - Centralize reported function name resolution
281
+ * @param {any} node - AST node used to derive the function name
282
+ * @returns {string} resolved function name
270
283
  */
271
- function reportMissing(context, sourceCode, config) {
272
- const { node, target: passedTarget, options = {} } = config;
273
- try {
274
- const functionName = extractName(node && (node.id || node.key) ? node.id || node.key : node);
275
- if (hasStoryAnnotation(sourceCode, node)) {
276
- return;
277
- }
278
- const resolvedTarget = passedTarget ?? resolveTargetNode(sourceCode, node);
279
- const name = functionName;
280
- const nameNode = (node.id && node.id.type === "Identifier" && node.id) ||
281
- (node.key && node.key.type === "Identifier" && node.key) ||
282
- node;
283
- const effectiveTemplate = getAnnotationTemplate(options.annotationTemplateOverride);
284
- const allowFix = shouldApplyAutoFix(options.autoFixToggle);
285
- context.report({
286
- node: nameNode,
287
- messageId: "missingStory",
288
- data: { name, functionName: name },
289
- fix: allowFix
290
- ? (0, require_story_core_1.createAddStoryFix)(resolvedTarget, effectiveTemplate)
291
- : undefined,
292
- suggest: [
293
- {
294
- desc: `Add JSDoc @story annotation for function '${name}', e.g., ${effectiveTemplate}`,
295
- fix: (0, require_story_core_1.createAddStoryFix)(resolvedTarget, effectiveTemplate),
296
- },
297
- ],
298
- });
284
+ function getReportedFunctionName(node) {
285
+ const candidate = node && (node.id || node.key) ? node.id || node.key : node;
286
+ return extractName(candidate);
287
+ }
288
+ /**
289
+ * Determine the most appropriate AST node to anchor error location for a report.
290
+ * Prefers Identifier nodes from id/key properties when available.
291
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
292
+ * @req REQ-ANNOTATION-REQUIRED - Normalize name node selection for error reporting
293
+ * @param {any} node - AST node used for error anchoring
294
+ * @returns {any} node to use as the report location
295
+ */
296
+ function getNameNodeForReport(node) {
297
+ if (node?.id?.type === "Identifier") {
298
+ return node.id;
299
299
  }
300
- catch {
301
- /* noop */
300
+ if (node?.key?.type === "Identifier") {
301
+ return node.key;
302
302
  }
303
+ return node;
303
304
  }
304
305
  /**
305
- * Report a missing @story annotation for a method-like node
306
- * Provides a suggestion to update the method/interface with the annotation.
307
- * The error data payload uses both name and functionName for consistent, specific error context.
306
+ * Resolve the node that should receive the @story annotation,
307
+ * respecting an explicitly passed target when provided.
308
308
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
309
- * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
310
- * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
311
- * @req REQ-ANNOTATION-REQUIRED - Implement reporting for missing method/interface annotations with suggestion
312
- * @req REQ-AUTOFIX-MISSING - Provide autofix for missing method/interface annotations while preserving suggestions
313
- * @req REQ-ERROR-SPECIFIC - Method error reports must include both name and functionName in the data payload for specific function context
314
- * @req REQ-ERROR-LOCATION - Method error reports must use the method name node to anchor error location
315
- * @req REQ-ERROR-CONTEXT - Method error reports must include functionName data for consistent error context
316
- * @param {Rule.RuleContext} context - ESLint rule context to report
309
+ * @req REQ-ANNOTATION-REQUIRED - Centralize annotation target node resolution
317
310
  * @param {any} sourceCode - ESLint sourceCode object
318
- * @param {{ node: any; target?: any; options?: ReportOptions }} config - configuration containing the node to report, optional insertion target, and optional report options
311
+ * @param {any} node - original function-like AST node
312
+ * @param {any} passedTarget - optional explicit annotation target
313
+ * @returns {any} node that should receive the annotation
319
314
  */
315
+ function resolveAnnotationTargetNode(sourceCode, node, passedTarget) {
316
+ return passedTarget ?? resolveTargetNode(sourceCode, node);
317
+ }
318
+ /**
319
+ * Build the effective annotation template and autofix toggle
320
+ * from the provided report options.
321
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
322
+ * @req REQ-ANNOTATION-REQUIRED - Normalize template and autofix configuration
323
+ * @param {ReportOptions} [options] - optional report configuration
324
+ * @returns {{ effectiveTemplate: string; allowFix: boolean }} template and autofix flags
325
+ */
326
+ function buildTemplateConfig(options) {
327
+ const effectiveTemplate = getAnnotationTemplate(options?.annotationTemplateOverride);
328
+ const allowFix = shouldApplyAutoFix(options?.autoFixToggle);
329
+ return { effectiveTemplate, allowFix };
330
+ }
331
+ function reportMissing(context, sourceCode, config) {
332
+ (0, require_story_core_1.coreReportMissing)({
333
+ hasStoryAnnotation,
334
+ getReportedFunctionName,
335
+ resolveAnnotationTargetNode,
336
+ getNameNodeForReport,
337
+ buildTemplateConfig,
338
+ extractName,
339
+ getAnnotationTemplate,
340
+ shouldApplyAutoFix,
341
+ createAddStoryFix: require_story_core_1.createAddStoryFix,
342
+ createMethodFix: require_story_core_1.createMethodFix,
343
+ }, context, sourceCode, config);
344
+ }
320
345
  function reportMethod(context, sourceCode, config) {
321
- const { node, target: passedTarget, options = {} } = config;
322
- try {
323
- if (hasStoryAnnotation(sourceCode, node)) {
324
- return;
325
- }
326
- const resolvedTarget = passedTarget ?? resolveTargetNode(sourceCode, node);
327
- const name = extractName(node);
328
- const nameNode = (node.key && node.key.type === "Identifier" && node.key) || node;
329
- const effectiveTemplate = getAnnotationTemplate(options.annotationTemplateOverride);
330
- const allowFix = shouldApplyAutoFix(options.autoFixToggle);
331
- context.report({
332
- node: nameNode,
333
- messageId: "missingStory",
334
- data: { name, functionName: name },
335
- fix: allowFix
336
- ? (0, require_story_core_1.createMethodFix)(resolvedTarget, effectiveTemplate)
337
- : undefined,
338
- suggest: [
339
- {
340
- desc: `Add JSDoc @story annotation for function '${name}', e.g., ${effectiveTemplate}`,
341
- fix: (0, require_story_core_1.createMethodFix)(resolvedTarget, effectiveTemplate),
342
- },
343
- ],
344
- });
345
- }
346
- catch {
347
- /* noop */
348
- }
346
+ (0, require_story_core_1.coreReportMethod)({
347
+ hasStoryAnnotation,
348
+ getReportedFunctionName,
349
+ resolveAnnotationTargetNode,
350
+ getNameNodeForReport,
351
+ buildTemplateConfig,
352
+ extractName,
353
+ getAnnotationTemplate,
354
+ shouldApplyAutoFix,
355
+ createAddStoryFix: require_story_core_1.createAddStoryFix,
356
+ createMethodFix: require_story_core_1.createMethodFix,
357
+ }, context, sourceCode, config);
349
358
  }
@@ -133,42 +133,32 @@ function parentChainHasStory(sourceCode, node) {
133
133
  return false;
134
134
  }
135
135
  /**
136
- * Fallback: inspect text immediately preceding the node in sourceCode.getText to find @story
137
- * Also accepts @supports annotations as satisfying story presence for this rule.
136
+ * Safely compute the starting range index for fallback text scanning.
137
+ * Centralizes guards around sourceCode.getText and node.range structure.
138
138
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
139
- * @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
140
- * @req REQ-ANNOTATION-REQUIRED - Provide fallback textual inspection when other heuristics fail
141
- * @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Treat @supports annotations as satisfying story presence in fallback checks
139
+ * @req REQ-ANNOTATION-REQUIRED - Centralize guards for fallback range computation
142
140
  */
143
- function fallbackTextBeforeHasStory(sourceCode, node) {
144
- // Skip fallback text inspection when the sourceCode API or node range information is not available.
145
- // @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
146
- // @req REQ-ANNOTATION-REQUIRED - Avoid throwing when source text or range metadata cannot be read
147
- if (typeof sourceCode?.getText !== "function" ||
148
- !Array.isArray((node && node.range) || [])) {
149
- return false;
141
+ function getFallbackRangeStart(sourceCode, node) {
142
+ if (typeof sourceCode?.getText !== "function") {
143
+ return null;
150
144
  }
151
- const range = node.range;
152
- // Guard against malformed range values that cannot provide a numeric start index for slicing.
153
- // @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
154
- // @req REQ-ANNOTATION-REQUIRED - Validate node range structure before computing fallback window
145
+ const range = (node && node.range) || null;
155
146
  if (!Array.isArray(range) || typeof range[0] !== "number") {
156
- return false;
147
+ return null;
157
148
  }
149
+ return range[0];
150
+ }
151
+ /**
152
+ * Safely slice a bounded fallback text window immediately preceding the node start index.
153
+ * Restricts scanning to a fixed-size window and treats IO/slicing failures as non-fatal.
154
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
155
+ * @req REQ-ANNOTATION-REQUIRED - Restrict fallback text scanning to a safe, fixed-size window and handle failures gracefully
156
+ */
157
+ function getFallbackTextWindow(sourceCode, nodeStartIndex) {
158
+ const start = Math.max(0, nodeStartIndex - exports.FALLBACK_WINDOW);
158
159
  try {
159
- // Limit the fallback inspection window to a bounded region immediately preceding the node.
160
- // @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
161
- // @req REQ-ANNOTATION-REQUIRED - Restrict fallback text scanning to a safe, fixed-size window
162
- const start = Math.max(0, range[0] - exports.FALLBACK_WINDOW);
163
- const textBefore = sourceCode.getText().slice(start, range[0]);
164
- // Detect any @story or @supports marker that appears within the bounded fallback window.
165
- // @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
166
- // @req REQ-ANNOTATION-REQUIRED - Recognize story annotations discovered via fallback text scanning
167
- // @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Recognize @supports annotations discovered via fallback text scanning
168
- if (typeof textBefore === "string" &&
169
- (textBefore.includes("@story") || textBefore.includes("@supports"))) {
170
- return true;
171
- }
160
+ const textBefore = sourceCode.getText().slice(start, nodeStartIndex);
161
+ return typeof textBefore === "string" ? textBefore : null;
172
162
  }
173
163
  catch {
174
164
  /*
@@ -176,6 +166,36 @@ function fallbackTextBeforeHasStory(sourceCode, node) {
176
166
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
177
167
  * @req REQ-ANNOTATION-REQUIRED - Treat fallback text inspection failures as "no annotation" instead of raising
178
168
  */
169
+ return null;
179
170
  }
180
- return false;
171
+ }
172
+ /**
173
+ * Detect whether the provided fallback text window contains a story marker.
174
+ * Recognizes both @story and @supports annotations in the inspected text.
175
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
176
+ * @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
177
+ * @req REQ-ANNOTATION-REQUIRED - Recognize story annotations discovered via fallback text scanning
178
+ * @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Recognize @supports annotations discovered via fallback text scanning
179
+ */
180
+ function fallbackTextHasMarker(textBefore) {
181
+ if (typeof textBefore !== "string") {
182
+ return false;
183
+ }
184
+ return textBefore.includes("@story") || textBefore.includes("@supports");
185
+ }
186
+ /**
187
+ * Fallback: inspect text immediately preceding the node in sourceCode.getText to find @story
188
+ * Also accepts @supports annotations as satisfying story presence for this rule.
189
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
190
+ * @story docs/stories/010.2-DEV-MULTI-STORY-SUPPORT.story.md
191
+ * @req REQ-ANNOTATION-REQUIRED - Provide fallback textual inspection when other heuristics fail
192
+ * @req REQ-REQUIRE-ACCEPTS-IMPLEMENTS - Treat @supports annotations as satisfying story presence in fallback checks
193
+ */
194
+ function fallbackTextBeforeHasStory(sourceCode, node) {
195
+ const nodeStartIndex = getFallbackRangeStart(sourceCode, node);
196
+ if (nodeStartIndex === null) {
197
+ return false;
198
+ }
199
+ const textBefore = getFallbackTextWindow(sourceCode, nodeStartIndex);
200
+ return fallbackTextHasMarker(textBefore);
181
201
  }
@@ -119,6 +119,9 @@ export declare function getRuleSchema(): {
119
119
  requirementIdExample: {
120
120
  type: string;
121
121
  };
122
+ autoFix: {
123
+ type: string;
124
+ };
122
125
  };
123
126
  additionalProperties: boolean;
124
127
  }[];
@@ -105,46 +105,88 @@ function resolveExample(nestedExample, flatExample, defaultExample) {
105
105
  }
106
106
  return defaultExample;
107
107
  }
108
- /**
109
- * Resolve user options into concrete, validated configuration.
110
- * Falls back to existing defaults when options are not provided or invalid.
111
- */
112
- function resolveOptions(rawOptions) {
113
- optionErrors = [];
114
- const user = normalizeUserOptions(rawOptions);
115
- const nestedStoryPattern = user?.story?.pattern;
116
- const flatStoryPattern = user?.storyPathPattern;
117
- const nestedStoryExample = user?.story?.example;
118
- const flatStoryExample = user?.storyPathExample;
119
- const nestedReqPattern = user?.req?.pattern;
120
- const flatReqPattern = user?.requirementIdPattern;
121
- const nestedReqExample = user?.req?.example;
122
- const flatReqExample = user?.requirementIdExample;
108
+ function getUserOptions(rawOptions) {
109
+ return normalizeUserOptions(rawOptions);
110
+ }
111
+ function resolveAutoFixFlag(user) {
123
112
  const autoFixFlag = user?.autoFix;
124
- const autoFix = typeof autoFixFlag === "boolean" ? autoFixFlag : true;
125
- const storyPattern = resolvePattern({
113
+ return typeof autoFixFlag === "boolean" ? autoFixFlag : true;
114
+ }
115
+ function resolveStoryPattern(nestedStoryPattern, flatStoryPattern) {
116
+ return resolvePattern({
126
117
  nestedPattern: nestedStoryPattern,
127
118
  nestedFieldName: "story.pattern",
128
119
  flatPattern: flatStoryPattern,
129
120
  flatFieldName: "storyPathPattern",
130
121
  defaultPattern: getDefaultStoryPattern(),
131
122
  });
132
- const reqPattern = resolvePattern({
123
+ }
124
+ function resolveReqPattern(nestedReqPattern, flatReqPattern) {
125
+ return resolvePattern({
133
126
  nestedPattern: nestedReqPattern,
134
127
  nestedFieldName: "req.pattern",
135
128
  flatPattern: flatReqPattern,
136
129
  flatFieldName: "requirementIdPattern",
137
130
  defaultPattern: getDefaultReqPattern(),
138
131
  });
139
- const storyExample = resolveExample(nestedStoryExample, flatStoryExample, getDefaultStoryExample());
140
- const reqExample = resolveExample(nestedReqExample, flatReqExample, getDefaultReqExample());
141
- resolvedDefaults = {
132
+ }
133
+ function resolveStoryExample(nestedStoryExample, flatStoryExample) {
134
+ return resolveExample(nestedStoryExample, flatStoryExample, getDefaultStoryExample());
135
+ }
136
+ function resolveReqExample(nestedReqExample, flatReqExample) {
137
+ return resolveExample(nestedReqExample, flatReqExample, getDefaultReqExample());
138
+ }
139
+ function getStoryPatternInputs(user) {
140
+ return {
141
+ nestedStoryPattern: user?.story?.pattern,
142
+ flatStoryPattern: user?.storyPathPattern,
143
+ };
144
+ }
145
+ function getStoryExampleInputs(user) {
146
+ return {
147
+ nestedStoryExample: user?.story?.example,
148
+ flatStoryExample: user?.storyPathExample,
149
+ };
150
+ }
151
+ function getReqPatternInputs(user) {
152
+ return {
153
+ nestedReqPattern: user?.req?.pattern,
154
+ flatReqPattern: user?.requirementIdPattern,
155
+ };
156
+ }
157
+ function getReqExampleInputs(user) {
158
+ return {
159
+ nestedReqExample: user?.req?.example,
160
+ flatReqExample: user?.requirementIdExample,
161
+ };
162
+ }
163
+ function resolveOptionsInternal(user) {
164
+ const { nestedStoryPattern, flatStoryPattern } = getStoryPatternInputs(user);
165
+ const { nestedStoryExample, flatStoryExample } = getStoryExampleInputs(user);
166
+ const { nestedReqPattern, flatReqPattern } = getReqPatternInputs(user);
167
+ const { nestedReqExample, flatReqExample } = getReqExampleInputs(user);
168
+ const autoFix = resolveAutoFixFlag(user);
169
+ const storyPattern = resolveStoryPattern(nestedStoryPattern, flatStoryPattern);
170
+ const reqPattern = resolveReqPattern(nestedReqPattern, flatReqPattern);
171
+ const storyExample = resolveStoryExample(nestedStoryExample, flatStoryExample);
172
+ const reqExample = resolveReqExample(nestedReqExample, flatReqExample);
173
+ return {
142
174
  storyPattern,
143
175
  storyExample,
144
176
  reqPattern,
145
177
  reqExample,
146
178
  autoFix,
147
179
  };
180
+ }
181
+ /**
182
+ * Resolve user options into concrete, validated configuration.
183
+ * Falls back to existing defaults when options are not provided or invalid.
184
+ */
185
+ function resolveOptions(rawOptions) {
186
+ optionErrors = [];
187
+ const user = getUserOptions(rawOptions);
188
+ const resolved = resolveOptionsInternal(user);
189
+ resolvedDefaults = resolved;
148
190
  return resolvedDefaults;
149
191
  }
150
192
  /**
@@ -175,6 +217,7 @@ function getRuleSchema() {
175
217
  storyPathExample: { type: "string" },
176
218
  requirementIdPattern: { type: "string" },
177
219
  requirementIdExample: { type: "string" },
220
+ autoFix: { type: "boolean" },
178
221
  },
179
222
  additionalProperties: false,
180
223
  },
@@ -90,6 +90,34 @@ function createMissingReqFix(node) {
90
90
  return fixer.insertTextBefore(target, "/** @req <REQ-ID> */\n");
91
91
  };
92
92
  }
93
+ /**
94
+ * Resolve the display name used when reporting a missing @req annotation.
95
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
96
+ * @req REQ-ANNOTATION-REPORTING - Use consistent naming when reporting missing @req
97
+ * @req REQ-ERROR-SPECIFIC - Derive a specific, human-readable name for the node
98
+ */
99
+ function getReportedName(contextNode, parentNode) {
100
+ const rawName = (0, require_story_utils_1.getNodeName)(contextNode) ?? (0, require_story_utils_1.getNodeName)(parentNode);
101
+ return rawName ?? "(anonymous)";
102
+ }
103
+ /**
104
+ * Determine the AST sub-node that should be used as the location for reporting.
105
+ * Prefers Identifier nodes (id or key) over the broader function-like node.
106
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
107
+ * @req REQ-ANNOTATION-REPORTING - Report missing @req on the most relevant node
108
+ * @req REQ-ERROR-SPECIFIC - Target the identifier when available for precise errors
109
+ */
110
+ function getNameNodeForReqReport(node) {
111
+ const candidateId = node.id;
112
+ if (candidateId && candidateId.type === "Identifier") {
113
+ return candidateId;
114
+ }
115
+ const candidateKey = node.key;
116
+ if (candidateKey && candidateKey.type === "Identifier") {
117
+ return candidateKey;
118
+ }
119
+ return node;
120
+ }
93
121
  /**
94
122
  * Helper to report a missing @req annotation via the ESLint context API.
95
123
  * Uses getNodeName to provide a readable name for the node.
@@ -102,13 +130,9 @@ function createMissingReqFix(node) {
102
130
  * @req REQ-ERROR-CONTEXT - Include contextual hints to help understand the error
103
131
  */
104
132
  function reportMissing(context, node, enableFix = true) {
105
- const rawName = (0, require_story_utils_1.getNodeName)(node) ?? (node && (0, require_story_utils_1.getNodeName)(node.parent));
106
- const name = rawName ?? "(anonymous)";
107
- const nameNode = (node && node.id && node.id.type === "Identifier"
108
- ? node.id
109
- : node && node.key && node.key.type === "Identifier"
110
- ? node.key
111
- : node) ?? node;
133
+ const parentNode = node?.parent;
134
+ const name = getReportedName(node, parentNode);
135
+ const nameNode = getNameNodeForReqReport(node);
112
136
  const reportOptions = {
113
137
  node: nameNode,
114
138
  messageId: "missingReq",