eslint-plugin-traceability 1.21.1 → 1.22.0
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 +7 -2
- package/README.md +3 -4
- package/lib/src/maintenance/batch.js +0 -2
- package/lib/src/maintenance/cli.js +8 -11
- package/lib/src/maintenance/commands.d.ts +2 -2
- package/lib/src/maintenance/commands.js +2 -3
- package/lib/src/maintenance/detect.js +7 -8
- package/lib/src/maintenance/report.js +2 -3
- package/lib/src/maintenance/storyParser.d.ts +16 -0
- package/lib/src/maintenance/storyParser.js +167 -0
- package/lib/src/maintenance/update.js +0 -1
- package/lib/src/rules/helpers/pattern-validators.d.ts +42 -0
- package/lib/src/rules/helpers/pattern-validators.js +65 -0
- package/lib/src/rules/helpers/prefer-implements-inline.d.ts +16 -0
- package/lib/src/rules/helpers/prefer-implements-inline.js +146 -0
- package/lib/src/rules/helpers/require-story-comment-detection.d.ts +47 -0
- package/lib/src/rules/helpers/require-story-comment-detection.js +141 -0
- package/lib/src/rules/helpers/require-story-core.d.ts +6 -6
- package/lib/src/rules/helpers/require-story-core.js +10 -11
- package/lib/src/rules/helpers/require-story-helpers.d.ts +5 -63
- package/lib/src/rules/helpers/require-story-helpers.js +29 -337
- package/lib/src/rules/helpers/require-story-name-extraction.d.ts +35 -0
- package/lib/src/rules/helpers/require-story-name-extraction.js +107 -0
- package/lib/src/rules/helpers/require-story-node-utils.d.ts +43 -0
- package/lib/src/rules/helpers/require-story-node-utils.js +115 -0
- package/lib/src/rules/helpers/valid-annotation-format-internal.js +11 -3
- package/lib/src/rules/helpers/valid-annotation-options.d.ts +0 -10
- package/lib/src/rules/helpers/valid-annotation-options.js +22 -92
- package/lib/src/rules/helpers/valid-req-reference-helpers.js +0 -1
- package/lib/src/rules/no-redundant-annotation.js +4 -238
- package/lib/src/rules/prefer-implements-annotation.d.ts +12 -0
- package/lib/src/rules/prefer-implements-annotation.js +9 -164
- package/lib/src/rules/require-traceability.d.ts +8 -0
- package/lib/src/rules/require-traceability.js +8 -0
- package/lib/src/utils/annotation-checker.d.ts +3 -2
- package/lib/src/utils/annotation-checker.js +3 -2
- package/lib/src/utils/branch-annotation-catch-helpers.d.ts +22 -0
- package/lib/src/utils/branch-annotation-catch-helpers.js +70 -0
- package/lib/src/utils/branch-annotation-helpers.js +11 -187
- package/lib/src/utils/branch-annotation-if-helpers.d.ts +1 -0
- package/lib/src/utils/branch-annotation-if-helpers.js +59 -0
- package/lib/src/utils/branch-annotation-indent-helpers.d.ts +1 -1
- package/lib/src/utils/branch-annotation-switch-helpers.d.ts +8 -2
- package/lib/src/utils/branch-annotation-switch-helpers.js +10 -4
- package/lib/src/utils/branch-validation.d.ts +9 -0
- package/lib/src/utils/branch-validation.js +58 -0
- package/lib/src/utils/comment-text-helpers.d.ts +31 -0
- package/lib/src/utils/comment-text-helpers.js +54 -0
- package/lib/src/utils/redundancy-detector.d.ts +85 -0
- package/lib/src/utils/redundancy-detector.js +235 -0
- package/lib/tests/maintenance/storyParser.test.d.ts +8 -0
- package/lib/tests/maintenance/storyParser.test.js +505 -0
- package/lib/tests/rules/no-redundant-annotation.test.js +1 -0
- package/lib/tests/rules/require-story-helpers.test.js +3 -2
- package/lib/tests/rules/valid-req-reference.test.js +2 -0
- package/package.json +18 -10
- package/user-docs/api-reference.md +2 -2
|
@@ -7,6 +7,7 @@ exports.scanElseIfBetweenConditionAndBody = scanElseIfBetweenConditionAndBody;
|
|
|
7
7
|
exports.scanElseIfInsideBlockComments = scanElseIfInsideBlockComments;
|
|
8
8
|
exports.getInsideElseIfCommentText = getInsideElseIfCommentText;
|
|
9
9
|
exports.gatherElseIfCommentText = gatherElseIfCommentText;
|
|
10
|
+
exports.gatherSimpleIfCommentText = gatherSimpleIfCommentText;
|
|
10
11
|
const branch_annotation_helpers_1 = require("./branch-annotation-helpers");
|
|
11
12
|
/**
|
|
12
13
|
* Small shared helpers for IfStatement/else-if specific annotation handling.
|
|
@@ -135,3 +136,61 @@ function gatherElseIfCommentText(sourceCode, node, parent, options) {
|
|
|
135
136
|
}
|
|
136
137
|
return beforeText;
|
|
137
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Try to get comments from inside a node using getCommentsInside if available.
|
|
141
|
+
*/
|
|
142
|
+
function tryGetCommentsInsideNode(sourceCode, consequent) {
|
|
143
|
+
const getCommentsInside = sourceCode.getCommentsInside;
|
|
144
|
+
if (typeof getCommentsInside !== "function") {
|
|
145
|
+
return "";
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const insideComments = getCommentsInside(consequent) || [];
|
|
149
|
+
const extractCommentValue = (c) => c.value;
|
|
150
|
+
return insideComments.map(extractCommentValue).join(" ");
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return "";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Scan for comments at the start of a block using line-based fallback.
|
|
158
|
+
*/
|
|
159
|
+
function scanBlockStartComments(sourceCode, consequent) {
|
|
160
|
+
if (!consequent.loc ||
|
|
161
|
+
!consequent.loc.start ||
|
|
162
|
+
!consequent.loc.end ||
|
|
163
|
+
typeof consequent.loc.start.line !== "number" ||
|
|
164
|
+
typeof consequent.loc.end.line !== "number") {
|
|
165
|
+
return "";
|
|
166
|
+
}
|
|
167
|
+
const lines = sourceCode.lines;
|
|
168
|
+
const startIndex = consequent.loc.start.line - 1;
|
|
169
|
+
const endIndex = consequent.loc.end.line - 1;
|
|
170
|
+
const comments = [];
|
|
171
|
+
const lastIndex = Math.min(endIndex, lines.length - 1);
|
|
172
|
+
let i = startIndex + 1;
|
|
173
|
+
while (i <= lastIndex) {
|
|
174
|
+
const line = lines[i];
|
|
175
|
+
if (!line || !line.trim() || !/^\s*(\/\/|\/\*)/.test(line)) {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
comments.push(line.trim());
|
|
179
|
+
i++;
|
|
180
|
+
}
|
|
181
|
+
return comments.join(" ");
|
|
182
|
+
}
|
|
183
|
+
function gatherSimpleIfCommentText(sourceCode, node, annotationPlacement, beforeText) {
|
|
184
|
+
if (annotationPlacement !== "inside") {
|
|
185
|
+
return beforeText;
|
|
186
|
+
}
|
|
187
|
+
if (!node.consequent || node.consequent.type !== "BlockStatement") {
|
|
188
|
+
return "";
|
|
189
|
+
}
|
|
190
|
+
const consequent = node.consequent;
|
|
191
|
+
const insideText = tryGetCommentsInsideNode(sourceCode, consequent);
|
|
192
|
+
if (insideText) {
|
|
193
|
+
return insideText;
|
|
194
|
+
}
|
|
195
|
+
return scanBlockStartComments(sourceCode, consequent);
|
|
196
|
+
}
|
|
@@ -7,7 +7,7 @@ import type { AnnotationPlacement } from "./branch-annotation-helpers";
|
|
|
7
7
|
* within ESLint's max-lines-per-function limits.
|
|
8
8
|
*
|
|
9
9
|
* @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
10
|
-
* @supports REQ-INSIDE-BRACE-PLACEMENT REQ-PLACEMENT-CONFIG REQ-INDENTATION-CORRECT
|
|
10
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-INSIDE-BRACE-PLACEMENT REQ-PLACEMENT-CONFIG REQ-INDENTATION-CORRECT
|
|
11
11
|
*/
|
|
12
12
|
type SourceCode = ReturnType<Rule.RuleContext["getSourceCode"]>;
|
|
13
13
|
type IndentHelperContext = {
|
|
@@ -3,9 +3,15 @@ import { type AnnotationPlacement } from "./branch-annotation-helpers";
|
|
|
3
3
|
/**
|
|
4
4
|
* Gather annotation text for SwitchCase branches, honoring the configured placement
|
|
5
5
|
* while preserving legacy before-branch behavior in the default mode.
|
|
6
|
+
*
|
|
6
7
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
7
8
|
* @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
8
|
-
* @supports REQ-PLACEMENT-CONFIG
|
|
9
|
-
* @supports REQ-INSIDE-BRACE-PLACEMENT
|
|
9
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PLACEMENT-CONFIG
|
|
10
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-INSIDE-BRACE-PLACEMENT
|
|
11
|
+
* @param sourceCode - ESLint source code object for accessing line/comment data
|
|
12
|
+
* @param node - SwitchCase AST node to gather annotations from
|
|
13
|
+
* @param annotationPlacement - Placement mode ("inside" or "before")
|
|
14
|
+
* @param beforeText - Pre-gathered text from before the case statement
|
|
15
|
+
* @returns Combined annotation text based on placement mode and detected annotations
|
|
10
16
|
*/
|
|
11
17
|
export declare function gatherSwitchCaseCommentText(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, node: any, annotationPlacement: AnnotationPlacement, beforeText: string): string;
|
|
@@ -6,8 +6,8 @@ const branch_annotation_helpers_1 = require("./branch-annotation-helpers");
|
|
|
6
6
|
* Gather comment text from the first contiguous comment lines "inside" a SwitchCase body.
|
|
7
7
|
* Prefers a BlockStatement consequent when present, with a fallback to the entire case range.
|
|
8
8
|
* @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
9
|
-
* @supports REQ-INSIDE-BRACE-PLACEMENT
|
|
10
|
-
* @supports REQ-PLACEMENT-CONFIG
|
|
9
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-INSIDE-BRACE-PLACEMENT
|
|
10
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PLACEMENT-CONFIG
|
|
11
11
|
*/
|
|
12
12
|
function getInsideSwitchCaseCommentText(sourceCode, node) {
|
|
13
13
|
const lines = sourceCode.lines;
|
|
@@ -44,10 +44,16 @@ function getInsideSwitchCaseCommentText(sourceCode, node) {
|
|
|
44
44
|
/**
|
|
45
45
|
* Gather annotation text for SwitchCase branches, honoring the configured placement
|
|
46
46
|
* while preserving legacy before-branch behavior in the default mode.
|
|
47
|
+
*
|
|
47
48
|
* @story docs/stories/003.0-DEV-FUNCTION-ANNOTATIONS.story.md
|
|
48
49
|
* @story docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md
|
|
49
|
-
* @supports REQ-PLACEMENT-CONFIG
|
|
50
|
-
* @supports REQ-INSIDE-BRACE-PLACEMENT
|
|
50
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-PLACEMENT-CONFIG
|
|
51
|
+
* @supports docs/stories/028.0-DEV-ANNOTATION-PLACEMENT-STANDARDIZATION.story.md REQ-INSIDE-BRACE-PLACEMENT
|
|
52
|
+
* @param sourceCode - ESLint source code object for accessing line/comment data
|
|
53
|
+
* @param node - SwitchCase AST node to gather annotations from
|
|
54
|
+
* @param annotationPlacement - Placement mode ("inside" or "before")
|
|
55
|
+
* @param beforeText - Pre-gathered text from before the case statement
|
|
56
|
+
* @returns Combined annotation text based on placement mode and detected annotations
|
|
51
57
|
*/
|
|
52
58
|
function gatherSwitchCaseCommentText(sourceCode, node, annotationPlacement, beforeText) {
|
|
53
59
|
if (annotationPlacement === "inside") {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Rule } from "eslint";
|
|
2
|
+
import type { BranchType } from "./branch-annotation-helpers";
|
|
3
|
+
/**
|
|
4
|
+
* Validate branchTypes configuration option and return branch types to enforce,
|
|
5
|
+
* or return an ESLint listener if configuration is invalid.
|
|
6
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
7
|
+
* @req REQ-CONFIGURABLE-SCOPE - Allow configuration of branch types for annotation enforcement
|
|
8
|
+
*/
|
|
9
|
+
export declare function validateBranchTypes(context: Rule.RuleContext, DEFAULT_BRANCH_TYPES: readonly BranchType[]): BranchType[] | Rule.RuleListener;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateBranchTypes = validateBranchTypes;
|
|
4
|
+
/**
|
|
5
|
+
* Validate branchTypes configuration option and return branch types to enforce,
|
|
6
|
+
* or return an ESLint listener if configuration is invalid.
|
|
7
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
8
|
+
* @req REQ-CONFIGURABLE-SCOPE - Allow configuration of branch types for annotation enforcement
|
|
9
|
+
*/
|
|
10
|
+
function validateBranchTypes(context, DEFAULT_BRANCH_TYPES) {
|
|
11
|
+
const options = context.options[0] || {};
|
|
12
|
+
/**
|
|
13
|
+
* Conditional branch checking whether branchTypes option was provided.
|
|
14
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
15
|
+
* @req REQ-TRACEABILITY-CONDITIONAL - Trace configuration branch existence check
|
|
16
|
+
*/
|
|
17
|
+
if (Array.isArray(options.branchTypes)) {
|
|
18
|
+
/**
|
|
19
|
+
* Predicate to determine whether a provided branch type is invalid.
|
|
20
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
21
|
+
* @req REQ-TRACEABILITY-FILTER-CALLBACK - Trace filter callback for invalid branch type detection
|
|
22
|
+
*/
|
|
23
|
+
function isInvalidType(t) {
|
|
24
|
+
return !DEFAULT_BRANCH_TYPES.includes(t);
|
|
25
|
+
}
|
|
26
|
+
const invalidTypes = options.branchTypes.filter(isInvalidType);
|
|
27
|
+
/**
|
|
28
|
+
* Conditional branch checking whether any invalid types were found.
|
|
29
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
30
|
+
* @req REQ-TRACEABILITY-INVALID-DETECTION - Trace handling when invalid types are detected
|
|
31
|
+
*/
|
|
32
|
+
if (invalidTypes.length > 0) {
|
|
33
|
+
/**
|
|
34
|
+
* Program listener produced when configuration is invalid.
|
|
35
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
36
|
+
* @req REQ-TRACEABILITY-PROGRAM-LISTENER - Trace Program listener reporting invalid config values
|
|
37
|
+
*/
|
|
38
|
+
function ProgramHandler(node) {
|
|
39
|
+
/**
|
|
40
|
+
* Report a single invalid type for the given Program node.
|
|
41
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
42
|
+
* @req REQ-TRACEABILITY-FOR-EACH-CALLBACK - Trace reporting for each invalid type
|
|
43
|
+
*/
|
|
44
|
+
function reportInvalidType(t) {
|
|
45
|
+
context.report({
|
|
46
|
+
node,
|
|
47
|
+
message: `Value "${t}" should be equal to one of the allowed values: ${DEFAULT_BRANCH_TYPES.join(", ")}`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
invalidTypes.forEach(reportInvalidType);
|
|
51
|
+
}
|
|
52
|
+
return { Program: ProgramHandler };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return Array.isArray(options.branchTypes)
|
|
56
|
+
? options.branchTypes
|
|
57
|
+
: Array.from(DEFAULT_BRANCH_TYPES);
|
|
58
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level comment text extraction helpers for branch annotation processing.
|
|
3
|
+
*
|
|
4
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
5
|
+
* @req REQ-COMMENT-ASSOCIATION - Extract and normalize comment text from various comment node types
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Extract the raw value from a comment node.
|
|
9
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
10
|
+
* @req REQ-TRACEABILITY-MAP-CALLBACK - Trace mapping of comment nodes to their text values
|
|
11
|
+
*/
|
|
12
|
+
export declare function extractCommentValue(_c: any): string;
|
|
13
|
+
/**
|
|
14
|
+
* Extract trimmed comment text for a given source line index or return null
|
|
15
|
+
* when the line is blank or not a comment. This helper centralizes the
|
|
16
|
+
* formatter-aware rules used by branch helpers when scanning for contiguous
|
|
17
|
+
* comment lines around branches.
|
|
18
|
+
* @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-COMMENT-ASSOCIATION
|
|
19
|
+
* @supports docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md REQ-DUAL-POSITION-DETECTION REQ-FALLBACK-LOGIC
|
|
20
|
+
* @supports docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md REQ-DUAL-POSITION-DETECTION-ELSE-IF REQ-FALLBACK-LOGIC-ELSE-IF
|
|
21
|
+
*/
|
|
22
|
+
export declare function getCommentTextAtLine(lines: string[], index: number): string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Collect a single contiguous comment line at the given index, appending its
|
|
25
|
+
* trimmed text to the accumulator. Returns true when a valid comment was
|
|
26
|
+
* collected and false when scanning should stop (blank or non-comment line).
|
|
27
|
+
* @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-COMMENT-ASSOCIATION
|
|
28
|
+
* @supports docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md REQ-DUAL-POSITION-DETECTION REQ-FALLBACK-LOGIC
|
|
29
|
+
* @supports docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md REQ-DUAL-POSITION-DETECTION-ELSE-IF REQ-FALLBACK-LOGIC-ELSE-IF
|
|
30
|
+
*/
|
|
31
|
+
export declare function collectCommentLine(lines: string[], index: number, comments: string[]): boolean;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Low-level comment text extraction helpers for branch annotation processing.
|
|
4
|
+
*
|
|
5
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
6
|
+
* @req REQ-COMMENT-ASSOCIATION - Extract and normalize comment text from various comment node types
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.extractCommentValue = extractCommentValue;
|
|
10
|
+
exports.getCommentTextAtLine = getCommentTextAtLine;
|
|
11
|
+
exports.collectCommentLine = collectCommentLine;
|
|
12
|
+
/**
|
|
13
|
+
* Extract the raw value from a comment node.
|
|
14
|
+
* @story docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md
|
|
15
|
+
* @req REQ-TRACEABILITY-MAP-CALLBACK - Trace mapping of comment nodes to their text values
|
|
16
|
+
*/
|
|
17
|
+
function extractCommentValue(_c) {
|
|
18
|
+
return _c.value;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Extract trimmed comment text for a given source line index or return null
|
|
22
|
+
* when the line is blank or not a comment. This helper centralizes the
|
|
23
|
+
* formatter-aware rules used by branch helpers when scanning for contiguous
|
|
24
|
+
* comment lines around branches.
|
|
25
|
+
* @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-COMMENT-ASSOCIATION
|
|
26
|
+
* @supports docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md REQ-DUAL-POSITION-DETECTION REQ-FALLBACK-LOGIC
|
|
27
|
+
* @supports docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md REQ-DUAL-POSITION-DETECTION-ELSE-IF REQ-FALLBACK-LOGIC-ELSE-IF
|
|
28
|
+
*/
|
|
29
|
+
function getCommentTextAtLine(lines, index) {
|
|
30
|
+
const line = lines[index];
|
|
31
|
+
if (!line || !line.trim()) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (!/^\s*(\/\/|\/\*)/.test(line)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return line.trim();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Collect a single contiguous comment line at the given index, appending its
|
|
41
|
+
* trimmed text to the accumulator. Returns true when a valid comment was
|
|
42
|
+
* collected and false when scanning should stop (blank or non-comment line).
|
|
43
|
+
* @supports docs/stories/004.0-DEV-BRANCH-ANNOTATIONS.story.md REQ-COMMENT-ASSOCIATION
|
|
44
|
+
* @supports docs/stories/025.0-DEV-CATCH-ANNOTATION-POSITION.story.md REQ-DUAL-POSITION-DETECTION REQ-FALLBACK-LOGIC
|
|
45
|
+
* @supports docs/stories/026.0-DEV-ELSE-IF-ANNOTATION-POSITION.story.md REQ-DUAL-POSITION-DETECTION-ELSE-IF REQ-FALLBACK-LOGIC-ELSE-IF
|
|
46
|
+
*/
|
|
47
|
+
function collectCommentLine(lines, index, comments) {
|
|
48
|
+
const commentText = getCommentTextAtLine(lines, index);
|
|
49
|
+
if (!commentText) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
comments.push(commentText);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Rule } from "eslint";
|
|
2
|
+
import { type RedundancyRuleOptions } from "./annotation-scope-analyzer";
|
|
3
|
+
/**
|
|
4
|
+
* Collect comments around a scope node using JSDoc, leading comments,
|
|
5
|
+
* and any comments that appear immediately before the node.
|
|
6
|
+
*
|
|
7
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-ANALYSIS REQ-SCOPE-INHERITANCE
|
|
8
|
+
*/
|
|
9
|
+
export declare function getScopeCommentsFromJSDocAndLeading(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]> | any, scopeNode: any): any[];
|
|
10
|
+
/**
|
|
11
|
+
* Extract traceability pairs (story-req combinations) from comments
|
|
12
|
+
* directly attached to the provided scope node.
|
|
13
|
+
*
|
|
14
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-ANALYSIS REQ-SCOPE-INHERITANCE
|
|
15
|
+
*/
|
|
16
|
+
export declare function getScopePairs(sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>, scopeNode: any, parent: any): Set<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Collect statement's immediate comments (leading, trailing, JSDoc).
|
|
19
|
+
*
|
|
20
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-STATEMENT-SIGNIFICANCE REQ-SAFE-REMOVAL
|
|
21
|
+
*/
|
|
22
|
+
export declare function getStatementComments(context: Rule.RuleContext, node: any): any[];
|
|
23
|
+
/**
|
|
24
|
+
* Debug log scope pairs when TRACEABILITY_DEBUG is enabled.
|
|
25
|
+
*
|
|
26
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-ANALYSIS
|
|
27
|
+
*/
|
|
28
|
+
export declare function debugScopePairs(scopeNode: any, scopePairs: Set<string>): void;
|
|
29
|
+
/**
|
|
30
|
+
* Recursively collect traceability pairs from the given scope node and
|
|
31
|
+
* its ancestors up to maxDepth levels.
|
|
32
|
+
*
|
|
33
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-INHERITANCE REQ-CONFIGURABLE-STRICTNESS
|
|
34
|
+
*/
|
|
35
|
+
export declare function collectScopePairs(context: Rule.RuleContext, scopeNode: any, maxDepth: number): Set<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Gather pairs and comments from a statement for redundancy checking.
|
|
38
|
+
*
|
|
39
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-STATEMENT-SIGNIFICANCE REQ-CONFIGURABLE-STRICTNESS REQ-DIFFERENT-REQUIREMENTS
|
|
40
|
+
*/
|
|
41
|
+
export declare function getStatementPairsForRedundancy(context: Rule.RuleContext, stmt: any, scopePairs: Set<string>, options: RedundancyRuleOptions): {
|
|
42
|
+
comments: any[];
|
|
43
|
+
pairs: Set<string>;
|
|
44
|
+
} | null;
|
|
45
|
+
/**
|
|
46
|
+
* Determine whether statement pairs are redundant within scope pairs
|
|
47
|
+
* according to configured options.
|
|
48
|
+
*
|
|
49
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-CONFIGURABLE-STRICTNESS
|
|
50
|
+
*/
|
|
51
|
+
export declare function isStatementRedundantWithinScope(stmtPairs: Set<string>, scopePairs: Set<string>, options: RedundancyRuleOptions): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Filter a list of comments down to those that contain traceability
|
|
54
|
+
* annotations relevant for redundancy detection.
|
|
55
|
+
*
|
|
56
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SAFE-REMOVAL REQ-REDUNDANCY-PATTERNS
|
|
57
|
+
*/
|
|
58
|
+
export declare function getAnnotationCommentsFromStatement(comments: any[]): any[];
|
|
59
|
+
/**
|
|
60
|
+
* Determine whether a statement is redundant relative to the provided
|
|
61
|
+
* scopePairs and options, using helper functions to gather statement
|
|
62
|
+
* pairs, apply redundancy rules, and collect the associated annotation
|
|
63
|
+
* comments. Returns null when the statement should not be treated as
|
|
64
|
+
* redundant.
|
|
65
|
+
*
|
|
66
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-SAFE-REMOVAL REQ-STATEMENT-SIGNIFICANCE REQ-CONFIGURABLE-STRICTNESS
|
|
67
|
+
*/
|
|
68
|
+
export declare function getRedundantStatementContext(context: Rule.RuleContext, stmt: any, scopePairs: Set<string>, options: RedundancyRuleOptions): {
|
|
69
|
+
comments: any[];
|
|
70
|
+
} | null;
|
|
71
|
+
/**
|
|
72
|
+
* Compute unique removal ranges for the given annotation comments.
|
|
73
|
+
*
|
|
74
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SAFE-REMOVAL
|
|
75
|
+
*/
|
|
76
|
+
export declare function getRemovalRangesForAnnotationComments(comments: any[], sourceCode: ReturnType<Rule.RuleContext["getSourceCode"]>): [number, number][];
|
|
77
|
+
/**
|
|
78
|
+
* Analyze a block's statements and report redundant traceability annotations.
|
|
79
|
+
*
|
|
80
|
+
* This helper encapsulates the iteration and reporting logic so that the
|
|
81
|
+
* BlockStatement visitor remains small and focused on scope setup.
|
|
82
|
+
*
|
|
83
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-SAFE-REMOVAL REQ-STATEMENT-SIGNIFICANCE
|
|
84
|
+
*/
|
|
85
|
+
export declare function reportRedundantAnnotationsInBlock(context: Rule.RuleContext, blockNode: any, scopePairs: Set<string>, options: RedundancyRuleOptions): void;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getScopeCommentsFromJSDocAndLeading = getScopeCommentsFromJSDocAndLeading;
|
|
4
|
+
exports.getScopePairs = getScopePairs;
|
|
5
|
+
exports.getStatementComments = getStatementComments;
|
|
6
|
+
exports.debugScopePairs = debugScopePairs;
|
|
7
|
+
exports.collectScopePairs = collectScopePairs;
|
|
8
|
+
exports.getStatementPairsForRedundancy = getStatementPairsForRedundancy;
|
|
9
|
+
exports.isStatementRedundantWithinScope = isStatementRedundantWithinScope;
|
|
10
|
+
exports.getAnnotationCommentsFromStatement = getAnnotationCommentsFromStatement;
|
|
11
|
+
exports.getRedundantStatementContext = getRedundantStatementContext;
|
|
12
|
+
exports.getRemovalRangesForAnnotationComments = getRemovalRangesForAnnotationComments;
|
|
13
|
+
exports.reportRedundantAnnotationsInBlock = reportRedundantAnnotationsInBlock;
|
|
14
|
+
const annotation_scope_analyzer_1 = require("./annotation-scope-analyzer");
|
|
15
|
+
const branch_annotation_helpers_1 = require("./branch-annotation-helpers");
|
|
16
|
+
/**
|
|
17
|
+
* Collect comments around a scope node using JSDoc, leading comments,
|
|
18
|
+
* and any comments that appear immediately before the node.
|
|
19
|
+
*
|
|
20
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-ANALYSIS REQ-SCOPE-INHERITANCE
|
|
21
|
+
*/
|
|
22
|
+
function getScopeCommentsFromJSDocAndLeading(sourceCode, scopeNode) {
|
|
23
|
+
const comments = [];
|
|
24
|
+
const jsdoc = sourceCode.getJSDocComment
|
|
25
|
+
? sourceCode.getJSDocComment(scopeNode)
|
|
26
|
+
: null;
|
|
27
|
+
const before = sourceCode.getCommentsBefore
|
|
28
|
+
? sourceCode.getCommentsBefore(scopeNode) || []
|
|
29
|
+
: [];
|
|
30
|
+
if (jsdoc) {
|
|
31
|
+
comments.push(jsdoc);
|
|
32
|
+
}
|
|
33
|
+
if (Array.isArray(scopeNode.leadingComments)) {
|
|
34
|
+
comments.push(...scopeNode.leadingComments);
|
|
35
|
+
}
|
|
36
|
+
comments.push(...before);
|
|
37
|
+
return comments;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Extract traceability pairs (story-req combinations) from comments
|
|
41
|
+
* directly attached to the provided scope node.
|
|
42
|
+
*
|
|
43
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-ANALYSIS REQ-SCOPE-INHERITANCE
|
|
44
|
+
*/
|
|
45
|
+
function getScopePairs(sourceCode, scopeNode, parent) {
|
|
46
|
+
if (branch_annotation_helpers_1.DEFAULT_BRANCH_TYPES.includes(scopeNode.type)) {
|
|
47
|
+
const commentText = (0, branch_annotation_helpers_1.gatherBranchCommentText)(sourceCode, scopeNode, parent, "before");
|
|
48
|
+
return (0, annotation_scope_analyzer_1.extractStoryReqPairsFromText)(commentText);
|
|
49
|
+
}
|
|
50
|
+
const directComments = getScopeCommentsFromJSDocAndLeading(sourceCode, scopeNode);
|
|
51
|
+
if (directComments.length === 0) {
|
|
52
|
+
return new Set();
|
|
53
|
+
}
|
|
54
|
+
return (0, annotation_scope_analyzer_1.extractStoryReqPairsFromComments)(directComments);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Collect statement's immediate comments (leading, trailing, JSDoc).
|
|
58
|
+
*
|
|
59
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-STATEMENT-SIGNIFICANCE REQ-SAFE-REMOVAL
|
|
60
|
+
*/
|
|
61
|
+
function getStatementComments(context, node) {
|
|
62
|
+
const sourceCode = context.getSourceCode();
|
|
63
|
+
const comments = [];
|
|
64
|
+
if (sourceCode.getCommentsBefore) {
|
|
65
|
+
comments.push(...(sourceCode.getCommentsBefore(node) || []));
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(node.leadingComments)) {
|
|
68
|
+
comments.push(...node.leadingComments);
|
|
69
|
+
}
|
|
70
|
+
const jsdoc = sourceCode.getJSDocComment
|
|
71
|
+
? sourceCode.getJSDocComment(node)
|
|
72
|
+
: null;
|
|
73
|
+
if (jsdoc) {
|
|
74
|
+
comments.push(jsdoc);
|
|
75
|
+
}
|
|
76
|
+
return comments;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Debug log scope pairs when TRACEABILITY_DEBUG is enabled.
|
|
80
|
+
*
|
|
81
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-ANALYSIS
|
|
82
|
+
*/
|
|
83
|
+
function debugScopePairs(scopeNode, scopePairs) {
|
|
84
|
+
if (process.env.TRACEABILITY_DEBUG === "1") {
|
|
85
|
+
console.log("[no-redundant-annotation] Scope %s has %d pairs: %s", scopeNode && scopeNode.type, scopePairs.size, Array.from(scopePairs)
|
|
86
|
+
.map((p) => `"${p}"`)
|
|
87
|
+
.join(", "));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Recursively collect traceability pairs from the given scope node and
|
|
92
|
+
* its ancestors up to maxDepth levels.
|
|
93
|
+
*
|
|
94
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SCOPE-INHERITANCE REQ-CONFIGURABLE-STRICTNESS
|
|
95
|
+
*/
|
|
96
|
+
function collectScopePairs(context, scopeNode, maxDepth) {
|
|
97
|
+
const allPairs = new Set();
|
|
98
|
+
let currentNode = scopeNode;
|
|
99
|
+
let depth = 0;
|
|
100
|
+
while (currentNode && depth < maxDepth) {
|
|
101
|
+
const parent = currentNode.parent;
|
|
102
|
+
const nodePairs = getScopePairs(context.getSourceCode(), currentNode, parent);
|
|
103
|
+
nodePairs.forEach((pair) => allPairs.add(pair));
|
|
104
|
+
if (allPairs.size > 0 && depth > 0) {
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
currentNode = parent;
|
|
108
|
+
depth++;
|
|
109
|
+
}
|
|
110
|
+
return allPairs;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Gather pairs and comments from a statement for redundancy checking.
|
|
114
|
+
*
|
|
115
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-STATEMENT-SIGNIFICANCE REQ-CONFIGURABLE-STRICTNESS REQ-DIFFERENT-REQUIREMENTS
|
|
116
|
+
*/
|
|
117
|
+
function getStatementPairsForRedundancy(context, stmt, scopePairs, options) {
|
|
118
|
+
if (!(0, annotation_scope_analyzer_1.isStatementEligibleForRedundancy)(stmt, options, branch_annotation_helpers_1.DEFAULT_BRANCH_TYPES)) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const comments = getStatementComments(context, stmt);
|
|
122
|
+
if (comments.length === 0) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const pairs = (0, annotation_scope_analyzer_1.extractStoryReqPairsFromComments)(comments);
|
|
126
|
+
if (process.env.TRACEABILITY_DEBUG === "1") {
|
|
127
|
+
console.log("[no-redundant-annotation] Statement %s has %d pairs: %s (scope has %d)", stmt.type, pairs.size, Array.from(pairs)
|
|
128
|
+
.map((p) => `"${p}"`)
|
|
129
|
+
.join(", "), scopePairs.size);
|
|
130
|
+
}
|
|
131
|
+
if (pairs.size === 0) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
return { comments, pairs };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Determine whether statement pairs are redundant within scope pairs
|
|
138
|
+
* according to configured options.
|
|
139
|
+
*
|
|
140
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-CONFIGURABLE-STRICTNESS
|
|
141
|
+
*/
|
|
142
|
+
function isStatementRedundantWithinScope(stmtPairs, scopePairs, options) {
|
|
143
|
+
if (options.allowEmphasisDuplication &&
|
|
144
|
+
stmtPairs.size === 1 &&
|
|
145
|
+
(0, annotation_scope_analyzer_1.arePairsFullyCovered)(stmtPairs, scopePairs)) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
if (!(0, annotation_scope_analyzer_1.arePairsFullyCovered)(stmtPairs, scopePairs)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Filter a list of comments down to those that contain traceability
|
|
155
|
+
* annotations relevant for redundancy detection.
|
|
156
|
+
*
|
|
157
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SAFE-REMOVAL REQ-REDUNDANCY-PATTERNS
|
|
158
|
+
*/
|
|
159
|
+
function getAnnotationCommentsFromStatement(comments) {
|
|
160
|
+
return comments.filter((comment) => {
|
|
161
|
+
const commentText = typeof comment.value === "string" ? comment.value : "";
|
|
162
|
+
return /@story\b|@req\b|@supports\b/.test(commentText);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Determine whether a statement is redundant relative to the provided
|
|
167
|
+
* scopePairs and options, using helper functions to gather statement
|
|
168
|
+
* pairs, apply redundancy rules, and collect the associated annotation
|
|
169
|
+
* comments. Returns null when the statement should not be treated as
|
|
170
|
+
* redundant.
|
|
171
|
+
*
|
|
172
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-SAFE-REMOVAL REQ-STATEMENT-SIGNIFICANCE REQ-CONFIGURABLE-STRICTNESS
|
|
173
|
+
*/
|
|
174
|
+
function getRedundantStatementContext(context, stmt, scopePairs, options) {
|
|
175
|
+
const stmtInfo = getStatementPairsForRedundancy(context, stmt, scopePairs, options);
|
|
176
|
+
if (!stmtInfo) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
const { comments, pairs } = stmtInfo;
|
|
180
|
+
if (!isStatementRedundantWithinScope(pairs, scopePairs, options)) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
const annotationComments = getAnnotationCommentsFromStatement(comments);
|
|
184
|
+
if (annotationComments.length === 0) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
return { comments: annotationComments };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Compute unique removal ranges for the given annotation comments.
|
|
191
|
+
*
|
|
192
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-SAFE-REMOVAL
|
|
193
|
+
*/
|
|
194
|
+
function getRemovalRangesForAnnotationComments(comments, sourceCode) {
|
|
195
|
+
const rangeMap = new Map();
|
|
196
|
+
for (const comment of comments) {
|
|
197
|
+
const [removalStart, removalEnd] = (0, annotation_scope_analyzer_1.getCommentRemovalRange)(comment, sourceCode);
|
|
198
|
+
const key = `${removalStart}:${removalEnd}`;
|
|
199
|
+
if (!rangeMap.has(key)) {
|
|
200
|
+
rangeMap.set(key, [removalStart, removalEnd]);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return Array.from(rangeMap.values()).sort((a, b) => b[0] - a[0]);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Analyze a block's statements and report redundant traceability annotations.
|
|
207
|
+
*
|
|
208
|
+
* This helper encapsulates the iteration and reporting logic so that the
|
|
209
|
+
* BlockStatement visitor remains small and focused on scope setup.
|
|
210
|
+
*
|
|
211
|
+
* @supports docs/stories/027.0-DEV-REDUNDANT-ANNOTATION-DETECTION.story.md REQ-REDUNDANCY-PATTERNS REQ-SAFE-REMOVAL REQ-STATEMENT-SIGNIFICANCE
|
|
212
|
+
*/
|
|
213
|
+
function reportRedundantAnnotationsInBlock(context, blockNode, scopePairs, options) {
|
|
214
|
+
const statements = Array.isArray(blockNode.body) ? blockNode.body : [];
|
|
215
|
+
if (statements.length === 0 || scopePairs.size === 0)
|
|
216
|
+
return;
|
|
217
|
+
const sourceCode = context.getSourceCode();
|
|
218
|
+
for (const stmt of statements) {
|
|
219
|
+
const info = getRedundantStatementContext(context, stmt, scopePairs, options);
|
|
220
|
+
if (!info) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const ranges = getRemovalRangesForAnnotationComments(info.comments, sourceCode);
|
|
224
|
+
if (ranges.length === 0) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
context.report({
|
|
228
|
+
node: stmt,
|
|
229
|
+
messageId: "redundantAnnotation",
|
|
230
|
+
fix(fixer) {
|
|
231
|
+
return ranges.map(([start, end]) => fixer.removeRange([start, end]));
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for storyParser module
|
|
3
|
+
* @story docs/stories/010.0-DEV-DEEP-VALIDATION.story.md
|
|
4
|
+
* @req REQ-DEEP-PARSE - Test parsing story file content to identify available requirements
|
|
5
|
+
* @req REQ-DEEP-FORMAT - Test finding requirement IDs in multiple markdown contexts
|
|
6
|
+
* @req REQ-DEEP-SECTION - Test handling requirements in different story file sections
|
|
7
|
+
*/
|
|
8
|
+
export {};
|