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.
- package/CHANGELOG.md +3 -4
- package/README.md +2 -2
- package/lib/src/maintenance/flags.js +111 -25
- package/lib/src/rules/helpers/require-story-core.d.ts +59 -0
- package/lib/src/rules/helpers/require-story-core.js +91 -1
- package/lib/src/rules/helpers/require-story-helpers.d.ts +2 -47
- package/lib/src/rules/helpers/require-story-helpers.js +135 -126
- package/lib/src/rules/helpers/require-story-io.js +51 -31
- package/lib/src/rules/helpers/valid-annotation-options.d.ts +3 -0
- package/lib/src/rules/helpers/valid-annotation-options.js +64 -21
- package/lib/src/utils/annotation-checker.js +31 -7
- package/lib/src/utils/reqAnnotationDetection.js +36 -22
- package/lib/tests/cli-error-handling.test.js +1 -0
- package/lib/tests/config/eslint-config-validation.test.d.ts +8 -0
- package/lib/tests/config/eslint-config-validation.test.js +8 -0
- package/lib/tests/config/flat-config-presets-integration.test.js +1 -3
- package/lib/tests/config/require-story-annotation-config.test.d.ts +9 -0
- package/lib/tests/config/require-story-annotation-config.test.js +9 -0
- package/lib/tests/integration/cli-integration.test.js +9 -1
- package/lib/tests/maintenance/batch.test.js +1 -0
- package/lib/tests/maintenance/cli.test.js +1 -0
- package/lib/tests/maintenance/detect-isolated.test.js +1 -0
- package/lib/tests/maintenance/detect.test.js +1 -0
- package/lib/tests/maintenance/index.test.js +1 -0
- package/lib/tests/maintenance/report.test.js +1 -0
- package/lib/tests/maintenance/update-isolated.test.js +1 -0
- package/lib/tests/maintenance/update.test.js +1 -0
- package/lib/tests/plugin-default-export-and-configs.test.js +2 -0
- package/lib/tests/plugin-setup-error.test.d.ts +1 -0
- package/lib/tests/plugin-setup-error.test.js +1 -0
- package/lib/tests/plugin-setup.test.js +1 -1
- package/lib/tests/rules/auto-fix-behavior-008.test.js +16 -0
- package/lib/tests/rules/error-reporting.test.js +1 -0
- package/lib/tests/rules/prefer-implements-annotation.test.js +8 -0
- package/lib/tests/rules/require-branch-annotation.test.js +2 -0
- package/lib/tests/rules/require-story-core-edgecases.test.js +1 -0
- package/lib/tests/rules/require-story-core.autofix.test.js +1 -0
- package/lib/tests/rules/require-story-core.test.js +1 -0
- package/lib/tests/rules/require-story-helpers-edgecases.test.d.ts +1 -0
- package/lib/tests/rules/require-story-helpers-edgecases.test.js +1 -0
- package/lib/tests/rules/require-story-helpers.test.js +4 -3
- package/lib/tests/rules/require-story-io-behavior.test.d.ts +1 -0
- package/lib/tests/rules/require-story-io-behavior.test.js +1 -0
- package/lib/tests/rules/require-story-io.edgecases.test.d.ts +1 -0
- package/lib/tests/rules/require-story-io.edgecases.test.js +1 -0
- package/lib/tests/rules/require-story-visitors-edgecases.test.d.ts +1 -0
- package/lib/tests/rules/require-story-visitors-edgecases.test.js +1 -0
- package/lib/tests/rules/valid-story-reference.test.js +2 -0
- package/lib/tests/utils/annotation-checker.test.js +2 -1
- package/lib/tests/utils/branch-annotation-helpers.test.js +2 -1
- package/package.json +1 -1
- package/user-docs/api-reference.md +117 -10
- package/user-docs/examples.md +2 -3
- 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.
|
|
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
|
|
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
|
|
207
|
-
while (
|
|
208
|
-
|
|
209
|
-
if (
|
|
210
|
-
return
|
|
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
|
-
|
|
217
|
-
if (
|
|
218
|
-
|
|
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
|
-
|
|
223
|
-
if (typeof
|
|
224
|
-
return
|
|
246
|
+
const directName = current.name;
|
|
247
|
+
if (typeof directName === "string" && directName.length > 0) {
|
|
248
|
+
return directName;
|
|
225
249
|
}
|
|
226
|
-
|
|
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
|
-
*
|
|
260
|
-
*
|
|
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
|
-
* @
|
|
263
|
-
* @
|
|
264
|
-
* @
|
|
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
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
301
|
-
|
|
300
|
+
if (node?.key?.type === "Identifier") {
|
|
301
|
+
return node.key;
|
|
302
302
|
}
|
|
303
|
+
return node;
|
|
303
304
|
}
|
|
304
305
|
/**
|
|
305
|
-
*
|
|
306
|
-
*
|
|
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
|
-
* @
|
|
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 {
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
*
|
|
137
|
-
*
|
|
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
|
-
* @
|
|
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
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -105,46 +105,88 @@ function resolveExample(nestedExample, flatExample, defaultExample) {
|
|
|
105
105
|
}
|
|
106
106
|
return defaultExample;
|
|
107
107
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
106
|
-
const name =
|
|
107
|
-
const nameNode = (node
|
|
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",
|