eslint-plugin-traceability 1.4.3 → 1.4.5
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/lib/src/maintenance/batch.js +1 -0
- package/lib/src/maintenance/detect.js +4 -0
- package/lib/src/maintenance/report.js +2 -0
- package/lib/src/maintenance/update.js +18 -0
- package/lib/src/maintenance/utils.js +33 -2
- package/lib/src/rules/helpers/require-story-core.d.ts +35 -0
- package/lib/src/rules/helpers/require-story-core.js +139 -0
- package/lib/src/rules/helpers/require-story-helpers.d.ts +124 -0
- package/lib/src/rules/helpers/require-story-helpers.js +293 -0
- package/lib/src/rules/helpers/require-story-io.d.ts +35 -0
- package/lib/src/rules/helpers/require-story-io.js +107 -0
- package/lib/src/rules/helpers/require-story-visitors.d.ts +13 -0
- package/lib/src/rules/helpers/require-story-visitors.js +163 -0
- package/lib/src/rules/require-story-annotation.d.ts +15 -0
- package/lib/src/rules/require-story-annotation.js +27 -268
- package/lib/src/rules/valid-req-reference.js +9 -0
- package/lib/src/rules/valid-story-reference.js +4 -0
- package/lib/src/utils/branch-annotation-helpers.js +91 -31
- package/lib/tests/rules/require-story-core.branches.test.d.ts +1 -0
- package/lib/tests/rules/require-story-core.branches.test.js +69 -0
- package/lib/tests/rules/require-story-core.test.d.ts +1 -0
- package/lib/tests/rules/require-story-core.test.js +58 -0
- package/lib/tests/rules/require-story-helpers.test.d.ts +1 -0
- package/lib/tests/rules/require-story-helpers.test.js +173 -0
- package/lib/tests/rules/require-story-io.edgecases.test.d.ts +6 -0
- package/lib/tests/rules/require-story-io.edgecases.test.js +50 -0
- package/package.json +3 -1
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fallbackTextBeforeHasStory = exports.parentChainHasStory = exports.linesBeforeHasStory = exports.EXPORT_PRIORITY_VALUES = exports.DEFAULT_SCOPE = exports.FALLBACK_WINDOW = exports.LOOKBACK_LINES = exports.ANNOTATION = exports.STORY_PATH = void 0;
|
|
4
|
+
exports.isExportedNode = isExportedNode;
|
|
5
|
+
exports.jsdocHasStory = jsdocHasStory;
|
|
6
|
+
exports.commentsBeforeHasStory = commentsBeforeHasStory;
|
|
7
|
+
exports.leadingCommentsHasStory = leadingCommentsHasStory;
|
|
8
|
+
exports.hasStoryAnnotation = hasStoryAnnotation;
|
|
9
|
+
exports.getNodeName = getNodeName;
|
|
10
|
+
exports.resolveTargetNode = resolveTargetNode;
|
|
11
|
+
exports.shouldProcessNode = shouldProcessNode;
|
|
12
|
+
exports.reportMissing = reportMissing;
|
|
13
|
+
exports.reportMethod = reportMethod;
|
|
14
|
+
const require_story_io_1 = require("./require-story-io");
|
|
15
|
+
Object.defineProperty(exports, "linesBeforeHasStory", { enumerable: true, get: function () { return require_story_io_1.linesBeforeHasStory; } });
|
|
16
|
+
Object.defineProperty(exports, "parentChainHasStory", { enumerable: true, get: function () { return require_story_io_1.parentChainHasStory; } });
|
|
17
|
+
Object.defineProperty(exports, "fallbackTextBeforeHasStory", { enumerable: true, get: function () { return require_story_io_1.fallbackTextBeforeHasStory; } });
|
|
18
|
+
const require_story_core_1 = require("./require-story-core");
|
|
19
|
+
Object.defineProperty(exports, "DEFAULT_SCOPE", { enumerable: true, get: function () { return require_story_core_1.DEFAULT_SCOPE; } });
|
|
20
|
+
Object.defineProperty(exports, "EXPORT_PRIORITY_VALUES", { enumerable: true, get: function () { return require_story_core_1.EXPORT_PRIORITY_VALUES; } });
|
|
21
|
+
/**
|
|
22
|
+
* Path to the story file for annotations
|
|
23
|
+
*/
|
|
24
|
+
const STORY_PATH = "docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md";
|
|
25
|
+
exports.STORY_PATH = STORY_PATH;
|
|
26
|
+
const ANNOTATION = `/** @story ${STORY_PATH} */`;
|
|
27
|
+
exports.ANNOTATION = ANNOTATION;
|
|
28
|
+
/**
|
|
29
|
+
* Number of physical source lines to inspect before a node when searching for @story text
|
|
30
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
31
|
+
* @req REQ-ANNOTATION-REQUIRED - Replace magic number for lookback lines with named constant
|
|
32
|
+
*/
|
|
33
|
+
const LOOKBACK_LINES = 4;
|
|
34
|
+
exports.LOOKBACK_LINES = LOOKBACK_LINES;
|
|
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
|
+
const FALLBACK_WINDOW = 800;
|
|
41
|
+
exports.FALLBACK_WINDOW = FALLBACK_WINDOW;
|
|
42
|
+
/**
|
|
43
|
+
* Determine if a node is in an export declaration
|
|
44
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
45
|
+
* @req REQ-ANNOTATION-REQUIRED - Check node ancestry to find export declarations
|
|
46
|
+
* @param {any} node - AST node to check for export ancestry
|
|
47
|
+
* @returns {boolean} true if node is within an export declaration
|
|
48
|
+
*/
|
|
49
|
+
function isExportedNode(node) {
|
|
50
|
+
let p = node.parent;
|
|
51
|
+
// @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
52
|
+
// @req REQ-ANNOTATION-REQUIRED - Walk parent chain to find Export declarations
|
|
53
|
+
while (p) {
|
|
54
|
+
if (p.type === "ExportNamedDeclaration" ||
|
|
55
|
+
p.type === "ExportDefaultDeclaration") {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
p = p.parent;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check whether the JSDoc associated with node contains @story
|
|
64
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
65
|
+
* @req REQ-ANNOTATION-REQUIRED - Extract JSDoc based detection into helper
|
|
66
|
+
* @param {any} sourceCode - ESLint sourceCode object
|
|
67
|
+
* @param {any} node - AST node to inspect
|
|
68
|
+
* @returns {boolean} true if JSDoc contains @story
|
|
69
|
+
*/
|
|
70
|
+
function jsdocHasStory(sourceCode, node) {
|
|
71
|
+
if (typeof sourceCode?.getJSDocComment !== "function") {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const jsdoc = sourceCode.getJSDocComment(node);
|
|
75
|
+
return !!(jsdoc &&
|
|
76
|
+
typeof jsdoc.value === "string" &&
|
|
77
|
+
jsdoc.value.includes("@story"));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check whether comments returned by sourceCode.getCommentsBefore contain @story
|
|
81
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
82
|
+
* @req REQ-ANNOTATION-REQUIRED - Extract comment-before detection into helper
|
|
83
|
+
* @param {any} sourceCode - ESLint sourceCode object
|
|
84
|
+
* @param {any} node - AST node to inspect
|
|
85
|
+
* @returns {boolean} true if any preceding comment contains @story
|
|
86
|
+
*/
|
|
87
|
+
function commentsBeforeHasStory(sourceCode, node) {
|
|
88
|
+
if (typeof sourceCode?.getCommentsBefore !== "function") {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const commentsBefore = sourceCode.getCommentsBefore(node) || [];
|
|
92
|
+
return (Array.isArray(commentsBefore) &&
|
|
93
|
+
commentsBefore.some((c) => typeof c.value === "string" && c.value.includes("@story")));
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Check whether leadingComments attached to the node contain @story
|
|
97
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
98
|
+
* @req REQ-ANNOTATION-REQUIRED - Extract leadingComments detection into helper
|
|
99
|
+
* @param {any} node - AST node to inspect
|
|
100
|
+
* @returns {boolean} true if any leading comment contains @story
|
|
101
|
+
*/
|
|
102
|
+
function leadingCommentsHasStory(node) {
|
|
103
|
+
const leadingComments = (node && node.leadingComments) || [];
|
|
104
|
+
return (Array.isArray(leadingComments) &&
|
|
105
|
+
leadingComments.some((c) => typeof c.value === "string" && c.value.includes("@story")));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Check if @story annotation already present in JSDoc or preceding comments
|
|
109
|
+
* Consolidates a variety of heuristics through smaller helpers.
|
|
110
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
111
|
+
* @req REQ-ANNOTATION-REQUIRED - Detect existing story annotations in JSDoc or comments
|
|
112
|
+
* @param {any} sourceCode - ESLint sourceCode object
|
|
113
|
+
* @param {any} node - AST node to inspect for existing annotations
|
|
114
|
+
* @returns {boolean} true if @story annotation already present
|
|
115
|
+
*/
|
|
116
|
+
function hasStoryAnnotation(sourceCode, node) {
|
|
117
|
+
try {
|
|
118
|
+
if (jsdocHasStory(sourceCode, node)) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
if (commentsBeforeHasStory(sourceCode, node)) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
if (leadingCommentsHasStory(node)) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
if ((0, require_story_io_1.linesBeforeHasStory)(sourceCode, node, LOOKBACK_LINES)) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if ((0, require_story_io_1.parentChainHasStory)(sourceCode, node)) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
if ((0, require_story_io_1.fallbackTextBeforeHasStory)(sourceCode, node)) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
/* noop */
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get the name of the function-like node
|
|
144
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
145
|
+
* @req REQ-ANNOTATION-REQUIRED - Resolve a human-friendly name for a function-like AST node
|
|
146
|
+
* @param {any} node - AST node representing a function-like construct
|
|
147
|
+
* @returns {string} the resolved name or "<unknown>"
|
|
148
|
+
*/
|
|
149
|
+
function getNodeName(node) {
|
|
150
|
+
let current = node;
|
|
151
|
+
while (current) {
|
|
152
|
+
if (current.type === "VariableDeclarator" &&
|
|
153
|
+
current.id &&
|
|
154
|
+
typeof current.id.name === "string") {
|
|
155
|
+
return current.id.name;
|
|
156
|
+
}
|
|
157
|
+
if ((current.type === "FunctionDeclaration" ||
|
|
158
|
+
current.type === "TSDeclareFunction") &&
|
|
159
|
+
current.id &&
|
|
160
|
+
typeof current.id.name === "string") {
|
|
161
|
+
return current.id.name;
|
|
162
|
+
}
|
|
163
|
+
if ((current.type === "MethodDefinition" ||
|
|
164
|
+
current.type === "TSMethodSignature") &&
|
|
165
|
+
current.key &&
|
|
166
|
+
typeof current.key.name === "string") {
|
|
167
|
+
return current.key.name;
|
|
168
|
+
}
|
|
169
|
+
current = current.parent;
|
|
170
|
+
}
|
|
171
|
+
return "<unknown>";
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Determine AST node where annotation should be inserted
|
|
175
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
176
|
+
* @req REQ-ANNOTATION-REQUIRED - Determine correct insertion target for annotation
|
|
177
|
+
* @param {any} sourceCode - ESLint sourceCode object (unused but kept for parity)
|
|
178
|
+
* @param {any} node - function-like AST node to resolve target for
|
|
179
|
+
* @returns {any} AST node that should receive the annotation
|
|
180
|
+
*/
|
|
181
|
+
function resolveTargetNode(sourceCode, node) {
|
|
182
|
+
if (node.type === "TSMethodSignature") {
|
|
183
|
+
// Interface method signature -> insert on interface
|
|
184
|
+
return node.parent.parent;
|
|
185
|
+
}
|
|
186
|
+
if (node.type === "FunctionExpression" ||
|
|
187
|
+
node.type === "ArrowFunctionExpression") {
|
|
188
|
+
const parent = node.parent;
|
|
189
|
+
if (parent.type === "VariableDeclarator") {
|
|
190
|
+
const varDecl = parent.parent;
|
|
191
|
+
if (varDecl.parent && varDecl.parent.type === "ExportNamedDeclaration") {
|
|
192
|
+
return varDecl.parent;
|
|
193
|
+
}
|
|
194
|
+
return varDecl;
|
|
195
|
+
}
|
|
196
|
+
if (parent.type === "ExportNamedDeclaration") {
|
|
197
|
+
return parent;
|
|
198
|
+
}
|
|
199
|
+
if (parent.type === "ExpressionStatement") {
|
|
200
|
+
return parent;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return node;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Check if this node is within scope and matches exportPriority
|
|
207
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
208
|
+
* @req REQ-ANNOTATION-REQUIRED - Determine whether a node should be processed by rule
|
|
209
|
+
* @param {any} node - AST node to evaluate
|
|
210
|
+
* @param {string[]} scope - allowed node types
|
|
211
|
+
* @param {string} [exportPriority='all'] - 'all' | 'exported' | 'non-exported' (default: 'all')
|
|
212
|
+
* @returns {boolean} whether node should be processed
|
|
213
|
+
*/
|
|
214
|
+
function shouldProcessNode(node, scope, exportPriority = "all") {
|
|
215
|
+
if (!scope.includes(node.type)) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
const exported = isExportedNode(node);
|
|
219
|
+
if (exportPriority === "exported" && !exported) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
if (exportPriority === "non-exported" && exported) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Report a missing @story annotation for a function-like node
|
|
229
|
+
* Provides a suggestion to add the annotation.
|
|
230
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
231
|
+
* @req REQ-ANNOTATION-REQUIRED - Implement reporting for missing annotations with suggestion
|
|
232
|
+
* @param {Rule.RuleContext} context - ESLint rule context used to report
|
|
233
|
+
* @param {any} sourceCode - ESLint sourceCode object
|
|
234
|
+
* @param {any} node - AST node that is missing the annotation
|
|
235
|
+
* @param {any} [passedTarget] - optional AST node to use as insertion target instead of resolving from node
|
|
236
|
+
*/
|
|
237
|
+
function reportMissing(context, sourceCode, node, passedTarget) {
|
|
238
|
+
try {
|
|
239
|
+
const functionName = getNodeName(node);
|
|
240
|
+
if (hasStoryAnnotation(sourceCode, node)) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const resolvedTarget = passedTarget ?? resolveTargetNode(sourceCode, node);
|
|
244
|
+
const name = functionName;
|
|
245
|
+
context.report({
|
|
246
|
+
node,
|
|
247
|
+
messageId: "missingStory",
|
|
248
|
+
data: { name },
|
|
249
|
+
suggest: [
|
|
250
|
+
{
|
|
251
|
+
desc: `Add JSDoc @story annotation for function '${name}', e.g., ${ANNOTATION}`,
|
|
252
|
+
fix: (0, require_story_core_1.createAddStoryFix)(resolvedTarget),
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
/* noop */
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Report a missing @story annotation for a method-like node
|
|
263
|
+
* Provides a suggestion to update the method/interface with the annotation.
|
|
264
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
265
|
+
* @req REQ-ANNOTATION-REQUIRED - Implement reporting for missing method/interface annotations with suggestion
|
|
266
|
+
* @param {Rule.RuleContext} context - ESLint rule context to report
|
|
267
|
+
* @param {any} sourceCode - ESLint sourceCode object
|
|
268
|
+
* @param {any} node - AST node that is missing the annotation
|
|
269
|
+
* @param {any} [passedTarget] - optional AST node to use as insertion target instead of resolving from node
|
|
270
|
+
*/
|
|
271
|
+
function reportMethod(context, sourceCode, node, passedTarget) {
|
|
272
|
+
try {
|
|
273
|
+
if (hasStoryAnnotation(sourceCode, node)) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const resolvedTarget = passedTarget ?? resolveTargetNode(sourceCode, node);
|
|
277
|
+
const name = getNodeName(node);
|
|
278
|
+
context.report({
|
|
279
|
+
node,
|
|
280
|
+
messageId: "missingStory",
|
|
281
|
+
data: { name },
|
|
282
|
+
suggest: [
|
|
283
|
+
{
|
|
284
|
+
desc: `Add JSDoc @story annotation for function '${name}', e.g., ${ANNOTATION}`,
|
|
285
|
+
fix: (0, require_story_core_1.createMethodFix)(resolvedTarget),
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
/* noop */
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IO helpers for require-story detection moved to reduce helper module size
|
|
3
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
4
|
+
* @req REQ-ANNOTATION-REQUIRED - Extract IO bound helpers to separate module
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Number of source lines to inspect before a node when searching for @story markers.
|
|
8
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
9
|
+
* @req REQ-ANNOTATION-REQUIRED - Expose lookback size as constant for reuse
|
|
10
|
+
*/
|
|
11
|
+
export declare const LOOKBACK_LINES = 4;
|
|
12
|
+
/**
|
|
13
|
+
* Number of characters to include in the fallback textual inspection window before a node.
|
|
14
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
15
|
+
* @req REQ-ANNOTATION-REQUIRED - Expose fallback window size as constant for reuse
|
|
16
|
+
*/
|
|
17
|
+
export declare const FALLBACK_WINDOW = 800;
|
|
18
|
+
/**
|
|
19
|
+
* Inspect a fixed number of physical source lines before the node for @story text
|
|
20
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
21
|
+
* @req REQ-ANNOTATION-REQUIRED - Extract line-based detection into helper
|
|
22
|
+
*/
|
|
23
|
+
export declare function linesBeforeHasStory(sourceCode: any, node: any, lookback?: number): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Walk parent chain and check comments before each parent and their leadingComments
|
|
26
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
27
|
+
* @req REQ-ANNOTATION-REQUIRED - Extract parent-chain comment detection into helper
|
|
28
|
+
*/
|
|
29
|
+
export declare function parentChainHasStory(sourceCode: any, node: any): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Fallback: inspect text immediately preceding the node in sourceCode.getText to find @story
|
|
32
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
33
|
+
* @req REQ-ANNOTATION-REQUIRED - Provide fallback textual inspection when other heuristics fail
|
|
34
|
+
*/
|
|
35
|
+
export declare function fallbackTextBeforeHasStory(sourceCode: any, node: any): boolean;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* IO helpers for require-story detection moved to reduce helper module size
|
|
4
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
5
|
+
* @req REQ-ANNOTATION-REQUIRED - Extract IO bound helpers to separate module
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.FALLBACK_WINDOW = exports.LOOKBACK_LINES = void 0;
|
|
9
|
+
exports.linesBeforeHasStory = linesBeforeHasStory;
|
|
10
|
+
exports.parentChainHasStory = parentChainHasStory;
|
|
11
|
+
exports.fallbackTextBeforeHasStory = fallbackTextBeforeHasStory;
|
|
12
|
+
/**
|
|
13
|
+
* Number of source lines to inspect before a node when searching for @story markers.
|
|
14
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
15
|
+
* @req REQ-ANNOTATION-REQUIRED - Expose lookback size as constant for reuse
|
|
16
|
+
*/
|
|
17
|
+
exports.LOOKBACK_LINES = 4;
|
|
18
|
+
/**
|
|
19
|
+
* Number of characters to include in the fallback textual inspection window before a node.
|
|
20
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
21
|
+
* @req REQ-ANNOTATION-REQUIRED - Expose fallback window size as constant for reuse
|
|
22
|
+
*/
|
|
23
|
+
exports.FALLBACK_WINDOW = 800;
|
|
24
|
+
/**
|
|
25
|
+
* Inspect a fixed number of physical source lines before the node for @story text
|
|
26
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
27
|
+
* @req REQ-ANNOTATION-REQUIRED - Extract line-based detection into helper
|
|
28
|
+
*/
|
|
29
|
+
function linesBeforeHasStory(sourceCode, node, lookback = exports.LOOKBACK_LINES) {
|
|
30
|
+
const lines = sourceCode && sourceCode.lines;
|
|
31
|
+
const startLine = node && node.loc && typeof node.loc.start?.line === "number"
|
|
32
|
+
? node.loc.start.line
|
|
33
|
+
: null;
|
|
34
|
+
if (!Array.isArray(lines) || typeof startLine !== "number") {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const from = Math.max(0, startLine - 1 - lookback);
|
|
38
|
+
const to = Math.max(0, startLine - 1);
|
|
39
|
+
for (let i = from; i < to; i++) {
|
|
40
|
+
const text = lines[i];
|
|
41
|
+
if (typeof text === "string" && text.includes("@story")) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Walk parent chain and check comments before each parent and their leadingComments
|
|
49
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
50
|
+
* @req REQ-ANNOTATION-REQUIRED - Extract parent-chain comment detection into helper
|
|
51
|
+
*/
|
|
52
|
+
function parentChainHasStory(sourceCode, node) {
|
|
53
|
+
let p = node && node.parent;
|
|
54
|
+
while (p) {
|
|
55
|
+
const pComments = typeof sourceCode?.getCommentsBefore === "function"
|
|
56
|
+
? sourceCode.getCommentsBefore(p) || []
|
|
57
|
+
: [];
|
|
58
|
+
if (Array.isArray(pComments) &&
|
|
59
|
+
pComments.some(
|
|
60
|
+
/**
|
|
61
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
62
|
+
* @req REQ-ANNOTATION-REQUIRED - Detect @story in parent comments via value inspection
|
|
63
|
+
*/
|
|
64
|
+
(c) => typeof c.value === "string" && c.value.includes("@story"))) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
const pLeading = p.leadingComments || [];
|
|
68
|
+
if (Array.isArray(pLeading) &&
|
|
69
|
+
pLeading.some(
|
|
70
|
+
/**
|
|
71
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
72
|
+
* @req REQ-ANNOTATION-REQUIRED - Detect @story in parent leadingComments via value inspection
|
|
73
|
+
*/
|
|
74
|
+
(c) => typeof c.value === "string" && c.value.includes("@story"))) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
p = p.parent;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Fallback: inspect text immediately preceding the node in sourceCode.getText to find @story
|
|
83
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
84
|
+
* @req REQ-ANNOTATION-REQUIRED - Provide fallback textual inspection when other heuristics fail
|
|
85
|
+
*/
|
|
86
|
+
function fallbackTextBeforeHasStory(sourceCode, node) {
|
|
87
|
+
if (typeof sourceCode?.getText !== "function" ||
|
|
88
|
+
!Array.isArray((node && node.range) || [])) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const range = node.range;
|
|
92
|
+
if (!Array.isArray(range) || typeof range[0] !== "number") {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
// Limit the fallback inspect window to a reasonable size
|
|
97
|
+
const start = Math.max(0, range[0] - exports.FALLBACK_WINDOW);
|
|
98
|
+
const textBefore = sourceCode.getText().slice(start, range[0]);
|
|
99
|
+
if (typeof textBefore === "string" && textBefore.includes("@story")) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
/* noop */
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visitor builders for require-story-annotation rule
|
|
3
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
4
|
+
* @req REQ-EXTRACT-VISITORS - Extract visitor logic into helper module to reduce rule file size
|
|
5
|
+
*/
|
|
6
|
+
import type { Rule } from "eslint";
|
|
7
|
+
/**
|
|
8
|
+
* Build visitor handlers for various function-like AST nodes.
|
|
9
|
+
* Returns merged listener object from smaller builders.
|
|
10
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
11
|
+
* @req REQ-BUILD-VISITORS - Provide visitor implementations for rule create()
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildVisitors(context: Rule.RuleContext, sourceCode: any, options: any): Rule.RuleListener;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Visitor builders for require-story-annotation rule
|
|
4
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
5
|
+
* @req REQ-EXTRACT-VISITORS - Extract visitor logic into helper module to reduce rule file size
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.buildVisitors = buildVisitors;
|
|
9
|
+
const require_story_helpers_1 = require("./require-story-helpers");
|
|
10
|
+
/**
|
|
11
|
+
* Build visitor for FunctionDeclaration nodes.
|
|
12
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
13
|
+
* @req REQ-BUILD-VISITORS-FNDECL - Provide visitor for FunctionDeclaration
|
|
14
|
+
*/
|
|
15
|
+
function buildFunctionDeclarationVisitor(context, sourceCode, options) {
|
|
16
|
+
/**
|
|
17
|
+
* Handle FunctionDeclaration nodes.
|
|
18
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
19
|
+
* @req REQ-ANNOTATION-REQUIRED - Report missing @story on function declarations
|
|
20
|
+
*/
|
|
21
|
+
function handleFunctionDeclaration(node) {
|
|
22
|
+
/**
|
|
23
|
+
* Debug logging for visitor entry
|
|
24
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
25
|
+
* @req REQ-DEBUG-LOG - Provide debug logging for visitor entry
|
|
26
|
+
*/
|
|
27
|
+
console.debug("require-story-annotation:FunctionDeclaration", typeof context.getFilename === "function"
|
|
28
|
+
? context.getFilename()
|
|
29
|
+
: "<unknown>", node && node.id ? node.id.name : "<anonymous>");
|
|
30
|
+
if (!options.shouldProcessNode(node))
|
|
31
|
+
return;
|
|
32
|
+
const target = (0, require_story_helpers_1.resolveTargetNode)(sourceCode, node);
|
|
33
|
+
(0, require_story_helpers_1.reportMissing)(context, sourceCode, node, target);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
FunctionDeclaration: handleFunctionDeclaration,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build visitor for FunctionExpression nodes.
|
|
41
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
42
|
+
* @req REQ-BUILD-VISITORS-FNEXPR - Provide visitor for FunctionExpression
|
|
43
|
+
*/
|
|
44
|
+
function buildFunctionExpressionVisitor(context, sourceCode, options) {
|
|
45
|
+
/**
|
|
46
|
+
* Handle FunctionExpression nodes.
|
|
47
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
48
|
+
* @req REQ-ANNOTATION-REQUIRED - Report missing @story on function expressions
|
|
49
|
+
*/
|
|
50
|
+
function handleFunctionExpression(node) {
|
|
51
|
+
if (!options.shouldProcessNode(node))
|
|
52
|
+
return;
|
|
53
|
+
/**
|
|
54
|
+
* Do not report when function expression is a MethodDefinition
|
|
55
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
56
|
+
* @req REQ-METHOD-SKIP - Skip MethodDefinition function expressions
|
|
57
|
+
*/
|
|
58
|
+
if (node.parent && node.parent.type === "MethodDefinition")
|
|
59
|
+
return;
|
|
60
|
+
const target = (0, require_story_helpers_1.resolveTargetNode)(sourceCode, node);
|
|
61
|
+
(0, require_story_helpers_1.reportMissing)(context, sourceCode, node, target);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
FunctionExpression: handleFunctionExpression,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build visitor for ArrowFunctionExpression nodes.
|
|
69
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
70
|
+
* @req REQ-BUILD-VISITORS-ARROW - Provide visitor for ArrowFunctionExpression
|
|
71
|
+
*/
|
|
72
|
+
function buildArrowFunctionVisitor(context, sourceCode, options) {
|
|
73
|
+
/**
|
|
74
|
+
* Handle ArrowFunctionExpression nodes.
|
|
75
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
76
|
+
* @req REQ-ANNOTATION-REQUIRED - Report missing @story on arrow functions
|
|
77
|
+
*/
|
|
78
|
+
function handleArrowFunctionExpression(node) {
|
|
79
|
+
if (!options.shouldProcessNode(node))
|
|
80
|
+
return;
|
|
81
|
+
const target = (0, require_story_helpers_1.resolveTargetNode)(sourceCode, node);
|
|
82
|
+
(0, require_story_helpers_1.reportMissing)(context, sourceCode, node, target);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
ArrowFunctionExpression: handleArrowFunctionExpression,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build visitor for TypeScript TSDeclareFunction nodes.
|
|
90
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
91
|
+
* @req REQ-BUILD-VISITORS-TSDECL - Provide visitor for TSDeclareFunction
|
|
92
|
+
*/
|
|
93
|
+
function buildTSDeclareFunctionVisitor(context, sourceCode, options) {
|
|
94
|
+
/**
|
|
95
|
+
* Handle TSDeclareFunction nodes.
|
|
96
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
97
|
+
* @req REQ-ANNOTATION-REQUIRED - Report missing @story on TS declare functions
|
|
98
|
+
*/
|
|
99
|
+
function handleTSDeclareFunction(node) {
|
|
100
|
+
if (!options.shouldProcessNode(node))
|
|
101
|
+
return;
|
|
102
|
+
(0, require_story_helpers_1.reportMissing)(context, sourceCode, node, node);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
TSDeclareFunction: handleTSDeclareFunction,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Build visitor for TypeScript TSMethodSignature nodes.
|
|
110
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
111
|
+
* @req REQ-BUILD-VISITORS-TSMSIG - Provide visitor for TSMethodSignature
|
|
112
|
+
*/
|
|
113
|
+
function buildTSMethodSignatureVisitor(context, sourceCode, options) {
|
|
114
|
+
/**
|
|
115
|
+
* Handle TSMethodSignature nodes.
|
|
116
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
117
|
+
* @req REQ-ANNOTATION-REQUIRED - Report missing @story on TS method signatures
|
|
118
|
+
*/
|
|
119
|
+
function handleTSMethodSignature(node) {
|
|
120
|
+
if (!options.shouldProcessNode(node))
|
|
121
|
+
return;
|
|
122
|
+
const target = (0, require_story_helpers_1.resolveTargetNode)(sourceCode, node);
|
|
123
|
+
(0, require_story_helpers_1.reportMissing)(context, sourceCode, node, target);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
TSMethodSignature: handleTSMethodSignature,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Build visitor for MethodDefinition nodes.
|
|
131
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
132
|
+
* @req REQ-BUILD-VISITORS-METHODDEF - Provide visitor for MethodDefinition
|
|
133
|
+
*/
|
|
134
|
+
function buildMethodDefinitionVisitor(context, sourceCode, options) {
|
|
135
|
+
/**
|
|
136
|
+
* Handle MethodDefinition nodes (class/object methods).
|
|
137
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
138
|
+
* @req REQ-ANNOTATION-REQUIRED - Report missing @story on class/object methods
|
|
139
|
+
*/
|
|
140
|
+
function handleMethodDefinition(node) {
|
|
141
|
+
if (!options.shouldProcessNode(node))
|
|
142
|
+
return;
|
|
143
|
+
(0, require_story_helpers_1.reportMethod)(context, sourceCode, node);
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
MethodDefinition: handleMethodDefinition,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Build visitor handlers for various function-like AST nodes.
|
|
151
|
+
* Returns merged listener object from smaller builders.
|
|
152
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
153
|
+
* @req REQ-BUILD-VISITORS - Provide visitor implementations for rule create()
|
|
154
|
+
*/
|
|
155
|
+
function buildVisitors(context, sourceCode, options) {
|
|
156
|
+
const fnDecl = buildFunctionDeclarationVisitor(context, sourceCode, options);
|
|
157
|
+
const fnExpr = buildFunctionExpressionVisitor(context, sourceCode, options);
|
|
158
|
+
const arrow = buildArrowFunctionVisitor(context, sourceCode, options);
|
|
159
|
+
const tsDecl = buildTSDeclareFunctionVisitor(context, sourceCode, options);
|
|
160
|
+
const tsSig = buildTSMethodSignatureVisitor(context, sourceCode, options);
|
|
161
|
+
const methodDef = buildMethodDefinitionVisitor(context, sourceCode, options);
|
|
162
|
+
return Object.assign({}, fnDecl, fnExpr, arrow, tsDecl, tsSig, methodDef);
|
|
163
|
+
}
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule module: require-story-annotation
|
|
3
|
+
*
|
|
4
|
+
* This file implements the ESLint rule that requires @story annotations
|
|
5
|
+
* on functions and methods according to configured scope and export priority.
|
|
6
|
+
*
|
|
7
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
8
|
+
* @req REQ-ANNOTATION-REQUIRED
|
|
9
|
+
*/
|
|
1
10
|
import type { Rule } from "eslint";
|
|
11
|
+
/**
|
|
12
|
+
* ESLint rule to require @story annotations on functions/methods.
|
|
13
|
+
*
|
|
14
|
+
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
15
|
+
* @req REQ-ANNOTATION-REQUIRED
|
|
16
|
+
*/
|
|
2
17
|
declare const rule: Rule.RuleModule;
|
|
3
18
|
export default rule;
|