eslint-plugin-traceability 1.11.0 → 1.11.2

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 (84) hide show
  1. package/CHANGELOG.md +3 -4
  2. package/README.md +1 -1
  3. package/lib/src/index.d.ts +12 -1
  4. package/lib/src/index.js +43 -6
  5. package/lib/src/maintenance/commands.js +2 -3
  6. package/lib/src/maintenance/flags.js +111 -25
  7. package/lib/src/maintenance/update.js +1 -14
  8. package/lib/src/rules/helpers/require-story-core.d.ts +67 -0
  9. package/lib/src/rules/helpers/require-story-core.js +142 -23
  10. package/lib/src/rules/helpers/require-story-helpers.d.ts +9 -88
  11. package/lib/src/rules/helpers/require-story-helpers.js +118 -166
  12. package/lib/src/rules/helpers/require-story-io.js +51 -31
  13. package/lib/src/rules/helpers/valid-annotation-format-internal.d.ts +12 -13
  14. package/lib/src/rules/helpers/valid-annotation-format-internal.js +21 -16
  15. package/lib/src/rules/helpers/valid-annotation-format-validators.d.ts +29 -3
  16. package/lib/src/rules/helpers/valid-annotation-format-validators.js +29 -3
  17. package/lib/src/rules/helpers/valid-annotation-options.d.ts +3 -0
  18. package/lib/src/rules/helpers/valid-annotation-options.js +64 -21
  19. package/lib/src/rules/helpers/valid-annotation-utils.d.ts +3 -3
  20. package/lib/src/rules/helpers/valid-annotation-utils.js +10 -10
  21. package/lib/src/rules/helpers/valid-req-reference-helpers.d.ts +11 -0
  22. package/lib/src/rules/helpers/valid-req-reference-helpers.js +362 -0
  23. package/lib/src/rules/prefer-implements-annotation.js +7 -7
  24. package/lib/src/rules/require-story-annotation.d.ts +2 -0
  25. package/lib/src/rules/require-story-annotation.js +1 -1
  26. package/lib/src/rules/valid-req-reference.d.ts +4 -0
  27. package/lib/src/rules/valid-req-reference.js +5 -349
  28. package/lib/src/rules/valid-story-reference.d.ts +1 -1
  29. package/lib/src/rules/valid-story-reference.js +17 -10
  30. package/lib/src/utils/annotation-checker.js +31 -7
  31. package/lib/src/utils/branch-annotation-helpers.d.ts +2 -2
  32. package/lib/src/utils/branch-annotation-helpers.js +4 -4
  33. package/lib/src/utils/reqAnnotationDetection.js +36 -22
  34. package/lib/tests/cli-error-handling.test.js +2 -1
  35. package/lib/tests/config/eslint-config-validation.test.d.ts +8 -0
  36. package/lib/tests/config/eslint-config-validation.test.js +81 -0
  37. package/lib/tests/config/flat-config-presets-integration.test.js +1 -3
  38. package/lib/tests/config/require-story-annotation-config.test.d.ts +9 -0
  39. package/lib/tests/config/require-story-annotation-config.test.js +9 -0
  40. package/lib/tests/fixtures/stale/example.js +1 -1
  41. package/lib/tests/fixtures/update/example.js +1 -1
  42. package/lib/tests/integration/cli-integration.test.js +9 -1
  43. package/lib/tests/integration/dogfooding-validation.test.d.ts +1 -0
  44. package/lib/tests/integration/dogfooding-validation.test.js +94 -0
  45. package/lib/tests/maintenance/batch.test.js +1 -0
  46. package/lib/tests/maintenance/cli.test.js +38 -0
  47. package/lib/tests/maintenance/detect-isolated.test.js +6 -5
  48. package/lib/tests/maintenance/detect.test.js +1 -0
  49. package/lib/tests/maintenance/index.test.js +1 -0
  50. package/lib/tests/maintenance/report.test.js +1 -0
  51. package/lib/tests/maintenance/update-isolated.test.js +1 -0
  52. package/lib/tests/maintenance/update.test.js +1 -0
  53. package/lib/tests/perf/maintenance-cli-large-workspace.test.js +18 -0
  54. package/lib/tests/perf/require-branch-annotation-large-file.test.d.ts +1 -0
  55. package/lib/tests/perf/require-branch-annotation-large-file.test.js +67 -0
  56. package/lib/tests/plugin-default-export-and-configs.test.js +2 -0
  57. package/lib/tests/plugin-setup-error.test.d.ts +1 -0
  58. package/lib/tests/plugin-setup-error.test.js +1 -0
  59. package/lib/tests/plugin-setup.test.js +12 -1
  60. package/lib/tests/rules/auto-fix-behavior-008.test.js +16 -0
  61. package/lib/tests/rules/error-reporting.test.js +1 -0
  62. package/lib/tests/rules/prefer-implements-annotation.test.js +8 -0
  63. package/lib/tests/rules/require-branch-annotation.test.js +34 -0
  64. package/lib/tests/rules/require-story-core-edgecases.test.js +1 -0
  65. package/lib/tests/rules/require-story-core.autofix.test.js +1 -0
  66. package/lib/tests/rules/require-story-core.test.js +1 -0
  67. package/lib/tests/rules/require-story-helpers-edgecases.test.d.ts +1 -0
  68. package/lib/tests/rules/require-story-helpers-edgecases.test.js +1 -0
  69. package/lib/tests/rules/require-story-helpers.test.js +4 -3
  70. package/lib/tests/rules/require-story-io-behavior.test.d.ts +1 -0
  71. package/lib/tests/rules/require-story-io-behavior.test.js +1 -0
  72. package/lib/tests/rules/require-story-io.edgecases.test.d.ts +1 -0
  73. package/lib/tests/rules/require-story-io.edgecases.test.js +1 -0
  74. package/lib/tests/rules/require-story-visitors-edgecases.test.d.ts +1 -0
  75. package/lib/tests/rules/require-story-visitors-edgecases.test.js +1 -0
  76. package/lib/tests/rules/valid-annotation-format-internal.test.d.ts +8 -0
  77. package/lib/tests/rules/valid-annotation-format-internal.test.js +47 -0
  78. package/lib/tests/rules/valid-story-reference.test.js +2 -0
  79. package/lib/tests/utils/annotation-checker.test.js +2 -1
  80. package/lib/tests/utils/branch-annotation-helpers.test.js +2 -1
  81. package/package.json +2 -2
  82. package/user-docs/api-reference.md +115 -8
  83. package/user-docs/examples.md +1 -1
  84. package/user-docs/migration-guide.md +35 -2
@@ -10,66 +10,38 @@
10
10
  import type { Rule } from "eslint";
11
11
  import { linesBeforeHasStory, parentChainHasStory, fallbackTextBeforeHasStory } from "./require-story-io";
12
12
  import { getNodeName } from "./require-story-utils";
13
- import { DEFAULT_SCOPE, EXPORT_PRIORITY_VALUES } from "./require-story-core";
13
+ import { DEFAULT_SCOPE, EXPORT_PRIORITY_VALUES, STORY_PATH } from "./require-story-core";
14
14
  /**
15
- * Path to the story file for annotations
16
- */
17
- declare const STORY_PATH = "docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md";
18
- /**
19
- * Derive the annotation template, optionally using an override.
20
- * When override is a non-empty string, its trimmed value is used.
21
- * Otherwise, the default template is returned.
15
+ * Shared configuration helpers
22
16
  */
17
+ interface ReportOptions {
18
+ annotationTemplateOverride?: string;
19
+ autoFixToggle?: boolean;
20
+ }
23
21
  declare function getAnnotationTemplate(override?: string): string;
24
- /**
25
- * Determine whether auto-fix should be applied.
26
- * Explicit false disables auto-fix; all other values enable it.
27
- */
28
22
  declare function shouldApplyAutoFix(autoFix: boolean | undefined): boolean;
29
- /**
30
- * Number of physical source lines to inspect before a node when searching for @story text
31
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
32
- * @req REQ-ANNOTATION-REQUIRED - Replace magic number for lookback lines with named constant
33
- */
34
- declare const LOOKBACK_LINES = 4;
35
- /**
36
- * Window (in characters) to inspect before a node as a fallback when searching for @story text
37
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
38
- * @req REQ-ANNOTATION-REQUIRED - Replace magic number for fallback text window with named constant
39
- */
40
- declare const FALLBACK_WINDOW = 800;
41
23
  /**
42
24
  * Determine if a node is in an export declaration
43
25
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
44
26
  * @req REQ-ANNOTATION-REQUIRED - Check node ancestry to find export declarations
45
- * @param {any} node - AST node to check for export ancestry
46
- * @returns {boolean} true if node is within an export declaration
47
27
  */
48
28
  declare function isExportedNode(node: any): boolean;
49
29
  /**
50
30
  * Check whether the JSDoc associated with node contains @story
51
31
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
52
32
  * @req REQ-ANNOTATION-REQUIRED - Extract JSDoc based detection into helper
53
- * @param {any} sourceCode - ESLint sourceCode object
54
- * @param {any} node - AST node to inspect
55
- * @returns {boolean} true if JSDoc contains @story
56
33
  */
57
34
  declare function jsdocHasStory(sourceCode: any, node: any): boolean;
58
35
  /**
59
36
  * Check whether comments returned by sourceCode.getCommentsBefore contain @story
60
37
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
61
38
  * @req REQ-ANNOTATION-REQUIRED - Extract comment-before detection into helper
62
- * @param {any} sourceCode - ESLint sourceCode object
63
- * @param {any} node - AST node to inspect
64
- * @returns {boolean} true if any preceding comment contains @story
65
39
  */
66
40
  declare function commentsBeforeHasStory(sourceCode: any, node: any): boolean;
67
41
  /**
68
42
  * Check whether leadingComments attached to the node contain @story
69
43
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
70
44
  * @req REQ-ANNOTATION-REQUIRED - Extract leadingComments detection into helper
71
- * @param {any} node - AST node to inspect
72
- * @returns {boolean} true if any leading comment contains @story
73
45
  */
74
46
  declare function leadingCommentsHasStory(node: any): boolean;
75
47
  /**
@@ -77,85 +49,34 @@ declare function leadingCommentsHasStory(node: any): boolean;
77
49
  * Consolidates a variety of heuristics through smaller helpers.
78
50
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
79
51
  * @req REQ-ANNOTATION-REQUIRED - Detect existing story annotations in JSDoc or comments
80
- * @param {any} sourceCode - ESLint sourceCode object
81
- * @param {any} node - AST node to inspect for existing annotations
82
- * @returns {boolean} true if @story annotation already present
83
52
  */
84
53
  declare function hasStoryAnnotation(sourceCode: any, node: any): boolean;
85
54
  /**
86
55
  * Determine AST node where annotation should be inserted
87
56
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
88
57
  * @req REQ-ANNOTATION-REQUIRED - Determine correct insertion target for annotation
89
- * @param {any} sourceCode - ESLint sourceCode object (unused but kept for parity)
90
- * @param {any} node - function-like AST node to resolve target for
91
- * @returns {any} AST node that should receive the annotation
92
58
  */
93
59
  declare function resolveTargetNode(sourceCode: any, node: any): any;
94
60
  /**
95
61
  * Small utility to walk the node and its parents to extract an Identifier or key name.
96
- * Walks up the parent chain and inspects common properties (id, key, name, Identifier nodes).
97
62
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
98
63
  * @req REQ-ANNOTATION-REQUIRED - Walk node and parents to find Identifier/Key name
99
- * @param {any} node - AST node to extract a name from
100
- * @returns {string} extracted name or "(anonymous)" when no name found
101
64
  */
102
65
  declare function extractName(node: any): string;
103
- /**
104
- * Check if this node is within scope and matches exportPriority
105
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
106
- * @req REQ-ANNOTATION-REQUIRED - Determine whether a node should be processed by rule
107
- * @param {any} node - AST node to evaluate
108
- * @param {string[]} scope - allowed node types
109
- * @param {string} [exportPriority='all'] - 'all' | 'exported' | 'non-exported' (default: 'all')
110
- * @returns {boolean} whether node should be processed
111
- */
112
66
  declare function shouldProcessNode(node: any, scope: string[], exportPriority?: string): boolean;
113
- interface ReportOptions {
114
- annotationTemplateOverride?: string;
115
- autoFixToggle?: boolean;
116
- }
117
- /**
118
- * Report a missing @story annotation for a function-like node
119
- * Provides a suggestion to add the annotation.
120
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
121
- * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
122
- * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
123
- * @req REQ-ANNOTATION-REQUIRED - Implement reporting for missing annotations with suggestion
124
- * @req REQ-AUTOFIX-MISSING - Provide autofix for missing annotations while preserving suggestions
125
- * @req REQ-ERROR-SPECIFIC - Error reports must include both name and functionName in the data payload for specific function context
126
- * @param {Rule.RuleContext} context - ESLint rule context used to report
127
- * @param {any} sourceCode - ESLint sourceCode object
128
- * @param {{ node: any; target?: any; options?: ReportOptions }} config - configuration containing the node to report, optional insertion target, and optional report options
129
- */
130
67
  declare function reportMissing(context: Rule.RuleContext, sourceCode: any, config: {
131
68
  node: any;
132
69
  target?: any;
133
70
  options?: ReportOptions;
134
71
  }): void;
135
- /**
136
- * Report a missing @story annotation for a method-like node
137
- * Provides a suggestion to update the method/interface with the annotation.
138
- * The error data payload uses both name and functionName for consistent, specific error context.
139
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
140
- * @story docs/stories/008.0-DEV-AUTO-FIX.story.md
141
- * @story docs/stories/007.0-DEV-ERROR-REPORTING.story.md
142
- * @req REQ-ANNOTATION-REQUIRED - Implement reporting for missing method/interface annotations with suggestion
143
- * @req REQ-AUTOFIX-MISSING - Provide autofix for missing method/interface annotations while preserving suggestions
144
- * @req REQ-ERROR-SPECIFIC - Method error reports must include both name and functionName in the data payload for specific function context
145
- * @req REQ-ERROR-LOCATION - Method error reports must use the method name node to anchor error location
146
- * @req REQ-ERROR-CONTEXT - Method error reports must include functionName data for consistent error context
147
- * @param {Rule.RuleContext} context - ESLint rule context to report
148
- * @param {any} sourceCode - ESLint sourceCode object
149
- * @param {{ node: any; target?: any; options?: ReportOptions }} config - configuration containing the node to report, optional insertion target, and optional report options
150
- */
151
72
  declare function reportMethod(context: Rule.RuleContext, sourceCode: any, config: {
152
73
  node: any;
153
74
  target?: any;
154
75
  options?: ReportOptions;
155
76
  }): void;
156
77
  /**
157
- * Explicit exports for require-story-annotation consumers
78
+ * Explicit exports for require-story-annotation helpers.
158
79
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
159
- * @req REQ-ANNOTATION-REQUIRED - Explicitly export helper functions and constants used by requiring modules
80
+ * @req REQ-ANNOTATION-REQUIRED
160
81
  */
161
- export { STORY_PATH, getAnnotationTemplate, shouldApplyAutoFix, LOOKBACK_LINES, FALLBACK_WINDOW, isExportedNode, jsdocHasStory, commentsBeforeHasStory, leadingCommentsHasStory, hasStoryAnnotation, getNodeName, extractName, resolveTargetNode, shouldProcessNode, reportMissing, reportMethod, DEFAULT_SCOPE, EXPORT_PRIORITY_VALUES, linesBeforeHasStory, parentChainHasStory, fallbackTextBeforeHasStory, };
82
+ export { STORY_PATH, getAnnotationTemplate, shouldApplyAutoFix, isExportedNode, jsdocHasStory, commentsBeforeHasStory, leadingCommentsHasStory, hasStoryAnnotation, getNodeName, extractName, resolveTargetNode, shouldProcessNode, DEFAULT_SCOPE, EXPORT_PRIORITY_VALUES, linesBeforeHasStory, parentChainHasStory, fallbackTextBeforeHasStory, reportMissing, reportMethod, };
@@ -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,26 +22,13 @@ 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;
30
- /**
31
- * Derive the annotation template, optionally using an override.
32
- * When override is a non-empty string, its trimmed value is used.
33
- * Otherwise, the default template is returned.
34
- */
25
+ Object.defineProperty(exports, "STORY_PATH", { enumerable: true, get: function () { return require_story_core_1.STORY_PATH; } });
35
26
  function getAnnotationTemplate(override) {
36
27
  if (typeof override === "string" && override.trim().length > 0) {
37
28
  return override.trim();
38
29
  }
39
- return `/** @story ${STORY_PATH} */`;
30
+ return `/** @story ${require_story_core_1.STORY_PATH} */`;
40
31
  }
41
- /**
42
- * Determine whether auto-fix should be applied.
43
- * Explicit false disables auto-fix; all other values enable it.
44
- */
45
32
  function shouldApplyAutoFix(autoFix) {
46
33
  if (autoFix === false) {
47
34
  return false;
@@ -49,30 +36,21 @@ function shouldApplyAutoFix(autoFix) {
49
36
  return true;
50
37
  }
51
38
  /**
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
39
+ * Build the effective annotation template and autofix toggle
40
+ * from the provided report options.
62
41
  */
63
- const FALLBACK_WINDOW = 800;
64
- exports.FALLBACK_WINDOW = FALLBACK_WINDOW;
42
+ function buildTemplateConfig(options) {
43
+ const effectiveTemplate = getAnnotationTemplate(options?.annotationTemplateOverride);
44
+ const allowFix = shouldApplyAutoFix(options?.autoFixToggle);
45
+ return { effectiveTemplate, allowFix };
46
+ }
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
68
50
  * @req REQ-ANNOTATION-REQUIRED - Check node ancestry to find export declarations
69
- * @param {any} node - AST node to check for export ancestry
70
- * @returns {boolean} true if node is within an export declaration
71
51
  */
72
52
  function isExportedNode(node) {
73
53
  let p = node.parent;
74
- // @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
75
- // @req REQ-ANNOTATION-REQUIRED - Walk parent chain to find Export declarations
76
54
  while (p) {
77
55
  if (p.type === "ExportNamedDeclaration" ||
78
56
  p.type === "ExportDefaultDeclaration") {
@@ -86,9 +64,6 @@ function isExportedNode(node) {
86
64
  * Check whether the JSDoc associated with node contains @story
87
65
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
88
66
  * @req REQ-ANNOTATION-REQUIRED - Extract JSDoc based detection into helper
89
- * @param {any} sourceCode - ESLint sourceCode object
90
- * @param {any} node - AST node to inspect
91
- * @returns {boolean} true if JSDoc contains @story
92
67
  */
93
68
  function jsdocHasStory(sourceCode, node) {
94
69
  if (typeof sourceCode?.getJSDocComment !== "function") {
@@ -103,9 +78,6 @@ function jsdocHasStory(sourceCode, node) {
103
78
  * Check whether comments returned by sourceCode.getCommentsBefore contain @story
104
79
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
105
80
  * @req REQ-ANNOTATION-REQUIRED - Extract comment-before detection into helper
106
- * @param {any} sourceCode - ESLint sourceCode object
107
- * @param {any} node - AST node to inspect
108
- * @returns {boolean} true if any preceding comment contains @story
109
81
  */
110
82
  function commentsBeforeHasStory(sourceCode, node) {
111
83
  if (typeof sourceCode?.getCommentsBefore !== "function") {
@@ -119,8 +91,6 @@ function commentsBeforeHasStory(sourceCode, node) {
119
91
  * Check whether leadingComments attached to the node contain @story
120
92
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
121
93
  * @req REQ-ANNOTATION-REQUIRED - Extract leadingComments detection into helper
122
- * @param {any} node - AST node to inspect
123
- * @returns {boolean} true if any leading comment contains @story
124
94
  */
125
95
  function leadingCommentsHasStory(node) {
126
96
  const leadingComments = (node && node.leadingComments) || [];
@@ -132,9 +102,6 @@ function leadingCommentsHasStory(node) {
132
102
  * Consolidates a variety of heuristics through smaller helpers.
133
103
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
134
104
  * @req REQ-ANNOTATION-REQUIRED - Detect existing story annotations in JSDoc or comments
135
- * @param {any} sourceCode - ESLint sourceCode object
136
- * @param {any} node - AST node to inspect for existing annotations
137
- * @returns {boolean} true if @story annotation already present
138
105
  */
139
106
  function hasStoryAnnotation(sourceCode, node) {
140
107
  try {
@@ -147,7 +114,7 @@ function hasStoryAnnotation(sourceCode, node) {
147
114
  if (leadingCommentsHasStory(node)) {
148
115
  return true;
149
116
  }
150
- if ((0, require_story_io_1.linesBeforeHasStory)(sourceCode, node, LOOKBACK_LINES)) {
117
+ if ((0, require_story_io_1.linesBeforeHasStory)(sourceCode, node)) {
151
118
  return true;
152
119
  }
153
120
  if ((0, require_story_io_1.parentChainHasStory)(sourceCode, node)) {
@@ -157,8 +124,10 @@ function hasStoryAnnotation(sourceCode, node) {
157
124
  return true;
158
125
  }
159
126
  }
160
- catch {
161
- /* noop */
127
+ catch (error) {
128
+ if (process.env.TRACEABILITY_DEBUG === "1") {
129
+ console.error("[traceability] hasStoryAnnotation failed for node", error?.message ?? error);
130
+ }
162
131
  }
163
132
  return false;
164
133
  }
@@ -166,9 +135,6 @@ function hasStoryAnnotation(sourceCode, node) {
166
135
  * Determine AST node where annotation should be inserted
167
136
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
168
137
  * @req REQ-ANNOTATION-REQUIRED - Determine correct insertion target for annotation
169
- * @param {any} sourceCode - ESLint sourceCode object (unused but kept for parity)
170
- * @param {any} node - function-like AST node to resolve target for
171
- * @returns {any} AST node that should receive the annotation
172
138
  */
173
139
  function resolveTargetNode(sourceCode, node) {
174
140
  if (node.type === "TSMethodSignature") {
@@ -194,54 +160,72 @@ function resolveTargetNode(sourceCode, node) {
194
160
  }
195
161
  return node;
196
162
  }
163
+ /**
164
+ * Extract a direct Identifier name when available on the given node.
165
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
166
+ * @req REQ-ANNOTATION-REQUIRED - Extract direct Identifier-based names from nodes
167
+ */
168
+ function getDirectIdentifierName(node) {
169
+ if (node &&
170
+ node.type === "Identifier" &&
171
+ typeof node.name === "string" &&
172
+ node.name.length > 0) {
173
+ return node.name;
174
+ }
175
+ return null;
176
+ }
177
+ /**
178
+ * Normalize container nodes that expose names via id/key properties.
179
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
180
+ * @req REQ-ANNOTATION-REQUIRED - Normalize container id/key-based names into a single helper
181
+ */
182
+ function getContainerKeyOrIdName(node) {
183
+ if (!node) {
184
+ return null;
185
+ }
186
+ if (node.id) {
187
+ const idName = (0, require_story_utils_1.getNodeName)(node.id);
188
+ if (typeof idName === "string" && idName.length > 0) {
189
+ return idName;
190
+ }
191
+ }
192
+ if (node.key) {
193
+ const keyName = (0, require_story_utils_1.getNodeName)(node.key);
194
+ if (typeof keyName === "string" && keyName.length > 0) {
195
+ return keyName;
196
+ }
197
+ if (node.key.type === "Literal" &&
198
+ typeof node.key.value === "string" &&
199
+ node.key.value.length > 0) {
200
+ return node.key.value;
201
+ }
202
+ }
203
+ return null;
204
+ }
197
205
  /**
198
206
  * Small utility to walk the node and its parents to extract an Identifier or key name.
199
- * Walks up the parent chain and inspects common properties (id, key, name, Identifier nodes).
200
207
  * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
201
208
  * @req REQ-ANNOTATION-REQUIRED - Walk node and parents to find Identifier/Key name
202
- * @param {any} node - AST node to extract a name from
203
- * @returns {string} extracted name or "(anonymous)" when no name found
204
209
  */
205
210
  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
+ let current = node;
212
+ while (current) {
213
+ const directIdentifierName = getDirectIdentifierName(current);
214
+ if (directIdentifierName) {
215
+ return directIdentifierName;
211
216
  }
212
- // id property (FunctionDeclaration, etc.)
213
- if (n.id && n.id.type === "Identifier" && typeof n.id.name === "string") {
214
- return n.id.name;
217
+ const containerName = getContainerKeyOrIdName(current);
218
+ if (containerName) {
219
+ return containerName;
215
220
  }
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;
221
+ const directName = current.name;
222
+ if (typeof directName === "string" && directName.length > 0) {
223
+ return directName;
221
224
  }
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;
225
- }
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;
225
+ current = current.parent;
233
226
  }
234
227
  return "(anonymous)";
235
228
  }
236
- /**
237
- * Check if this node is within scope and matches exportPriority
238
- * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
239
- * @req REQ-ANNOTATION-REQUIRED - Determine whether a node should be processed by rule
240
- * @param {any} node - AST node to evaluate
241
- * @param {string[]} scope - allowed node types
242
- * @param {string} [exportPriority='all'] - 'all' | 'exported' | 'non-exported' (default: 'all')
243
- * @returns {boolean} whether node should be processed
244
- */
245
229
  function shouldProcessNode(node, scope, exportPriority = "all") {
246
230
  if (!scope.includes(node.type)) {
247
231
  return false;
@@ -256,94 +240,62 @@ function shouldProcessNode(node, scope, exportPriority = "all") {
256
240
  return true;
257
241
  }
258
242
  /**
259
- * Report a missing @story annotation for a function-like node
260
- * Provides a suggestion to add the annotation.
243
+ * Resolve the effective function name to report for a node.
261
244
  * @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
245
+ * @req REQ-ANNOTATION-REQUIRED - Centralize reported function name resolution
270
246
  */
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
- });
247
+ function getReportedFunctionName(node) {
248
+ const candidate = node && (node.id || node.key) ? node.id || node.key : node;
249
+ return extractName(candidate);
250
+ }
251
+ /**
252
+ * Determine the most appropriate AST node to anchor error location for a report.
253
+ * @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
254
+ * @req REQ-ANNOTATION-REQUIRED - Normalize name node selection for error reporting
255
+ */
256
+ function getNameNodeForReport(node) {
257
+ if (node?.id?.type === "Identifier") {
258
+ return node.id;
299
259
  }
300
- catch {
301
- /* noop */
260
+ if (node?.key?.type === "Identifier") {
261
+ return node.key;
302
262
  }
263
+ return node;
303
264
  }
304
265
  /**
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.
266
+ * Resolve the node that should receive the @story annotation,
267
+ * respecting an explicitly passed target when provided.
308
268
  * @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
317
- * @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
269
+ * @req REQ-ANNOTATION-REQUIRED - Centralize annotation target node resolution
319
270
  */
271
+ function resolveAnnotationTargetNode(sourceCode, node, passedTarget) {
272
+ return passedTarget ?? resolveTargetNode(sourceCode, node);
273
+ }
274
+ function reportMissing(context, sourceCode, config) {
275
+ (0, require_story_core_1.coreReportMissing)({
276
+ hasStoryAnnotation,
277
+ getReportedFunctionName,
278
+ resolveAnnotationTargetNode,
279
+ getNameNodeForReport,
280
+ buildTemplateConfig,
281
+ extractName,
282
+ getAnnotationTemplate,
283
+ shouldApplyAutoFix,
284
+ createAddStoryFix: require_story_core_1.createAddStoryFix,
285
+ createMethodFix: require_story_core_1.createMethodFix,
286
+ }, context, sourceCode, config);
287
+ }
320
288
  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
- }
289
+ (0, require_story_core_1.coreReportMethod)({
290
+ hasStoryAnnotation,
291
+ getReportedFunctionName,
292
+ resolveAnnotationTargetNode,
293
+ getNameNodeForReport,
294
+ buildTemplateConfig,
295
+ extractName,
296
+ getAnnotationTemplate,
297
+ shouldApplyAutoFix,
298
+ createAddStoryFix: require_story_core_1.createAddStoryFix,
299
+ createMethodFix: require_story_core_1.createMethodFix,
300
+ }, context, sourceCode, config);
349
301
  }
@@ -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
  }