eslint-plugin-traceability 1.4.11 → 1.5.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.
@@ -71,7 +71,6 @@ exports.DEFAULT_SCOPE = [
71
71
  "MethodDefinition",
72
72
  "TSMethodSignature",
73
73
  "TSDeclareFunction",
74
- "VariableDeclarator",
75
74
  ];
76
75
  /**
77
76
  * Allowed values for export priority option.
@@ -103,8 +102,13 @@ function reportMissing(context, sourceCode, node, target) {
103
102
  // @req REQ-ANNOTATION-REQUIRED - Resolve function name for reporting, default to <unknown>
104
103
  const name = node && node.id && node.id.name ? node.id.name : "<unknown>";
105
104
  const resolvedTarget = target ?? node;
105
+ const nameNode = node && node.id && node.id.type === "Identifier"
106
+ ? node.id
107
+ : node && node.key && node.key.type === "Identifier"
108
+ ? node.key
109
+ : node;
106
110
  context.report({
107
- node,
111
+ node: nameNode,
108
112
  messageId: "missingStory",
109
113
  data: { name },
110
114
  suggest: [
@@ -1,10 +1,2 @@
1
- /**
2
- * Rule to validate @story and @req annotation format and syntax
3
- * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
4
- * @req REQ-FORMAT-SPECIFICATION - Define clear format rules for @story and @req annotations
5
- * @req REQ-SYNTAX-VALIDATION - Validate annotation syntax matches specification
6
- * @req REQ-PATH-FORMAT - Validate @story paths follow expected patterns
7
- * @req REQ-REQ-FORMAT - Validate @req identifiers follow expected patterns
8
- */
9
1
  declare const _default: any;
10
2
  export default _default;
@@ -1,13 +1,187 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const STORY_EXAMPLE_PATH = "docs/stories/005.0-DEV-EXAMPLE.story.md";
3
4
  /**
4
- * Rule to validate @story and @req annotation format and syntax
5
+ * Normalize a raw comment line to make annotation parsing more robust.
6
+ *
7
+ * This function trims whitespace, keeps any annotation tags that appear
8
+ * later in the line, and supports common JSDoc styles such as leading "*".
9
+ *
10
+ * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
11
+ * @req REQ-FLEXIBLE-PARSING - Support reasonable variations in whitespace and formatting
12
+ */
13
+ function normalizeCommentLine(rawLine) {
14
+ const trimmed = rawLine.trim();
15
+ if (!trimmed) {
16
+ return "";
17
+ }
18
+ const annotationMatch = trimmed.match(/@story\b|@req\b/);
19
+ if (!annotationMatch || annotationMatch.index === undefined) {
20
+ const withoutLeadingStar = trimmed.replace(/^\*\s?/, "");
21
+ return withoutLeadingStar;
22
+ }
23
+ return trimmed.slice(annotationMatch.index);
24
+ }
25
+ /**
26
+ * Collapse internal whitespace in an annotation value so that multi-line
27
+ * annotations are treated as a single logical value.
28
+ *
29
+ * Example:
30
+ * "docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md" across
31
+ * multiple lines will be collapsed before validation.
32
+ *
33
+ * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
34
+ * @req REQ-MULTILINE-SUPPORT - Handle annotations split across multiple lines
35
+ */
36
+ function collapseAnnotationValue(value) {
37
+ return value.replace(/\s+/g, "");
38
+ }
39
+ /**
40
+ * Build a detailed error message for invalid @story annotations.
41
+ *
42
+ * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
43
+ * @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
44
+ */
45
+ function buildStoryErrorMessage(kind, value) {
46
+ if (kind === "missing") {
47
+ return `Missing story path for @story annotation. Expected a path like "${STORY_EXAMPLE_PATH}".`;
48
+ }
49
+ return `Invalid story path "${value ?? ""}" for @story annotation. Expected a path like "${STORY_EXAMPLE_PATH}".`;
50
+ }
51
+ /**
52
+ * Build a detailed error message for invalid @req annotations.
53
+ *
54
+ * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
55
+ * @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
56
+ */
57
+ function buildReqErrorMessage(kind, value) {
58
+ if (kind === "missing") {
59
+ return 'Missing requirement ID for @req annotation. Expected an identifier like "REQ-EXAMPLE".';
60
+ }
61
+ return `Invalid requirement ID "${value ?? ""}" for @req annotation. Expected an identifier like "REQ-EXAMPLE" (uppercase letters, numbers, and dashes only).`;
62
+ }
63
+ /**
64
+ * Validate a @story annotation value and report detailed errors when needed.
65
+ *
5
66
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
6
- * @req REQ-FORMAT-SPECIFICATION - Define clear format rules for @story and @req annotations
7
- * @req REQ-SYNTAX-VALIDATION - Validate annotation syntax matches specification
8
67
  * @req REQ-PATH-FORMAT - Validate @story paths follow expected patterns
68
+ * @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
69
+ */
70
+ function validateStoryAnnotation(context, comment, rawValue) {
71
+ const trimmed = rawValue.trim();
72
+ if (!trimmed) {
73
+ context.report({
74
+ node: comment,
75
+ messageId: "invalidStoryFormat",
76
+ data: { details: buildStoryErrorMessage("missing", null) },
77
+ });
78
+ return;
79
+ }
80
+ const collapsed = collapseAnnotationValue(trimmed);
81
+ const pathPattern = /^docs\/stories\/[0-9]+\.[0-9]+-DEV-[\w-]+\.story\.md$/;
82
+ if (!pathPattern.test(collapsed)) {
83
+ context.report({
84
+ node: comment,
85
+ messageId: "invalidStoryFormat",
86
+ data: { details: buildStoryErrorMessage("invalid", collapsed) },
87
+ });
88
+ }
89
+ }
90
+ /**
91
+ * Validate a @req annotation value and report detailed errors when needed.
92
+ *
93
+ * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
9
94
  * @req REQ-REQ-FORMAT - Validate @req identifiers follow expected patterns
95
+ * @req REQ-ERROR-SPECIFICITY - Provide specific error messages for different format violations
96
+ */
97
+ function validateReqAnnotation(context, comment, rawValue) {
98
+ const trimmed = rawValue.trim();
99
+ if (!trimmed) {
100
+ context.report({
101
+ node: comment,
102
+ messageId: "invalidReqFormat",
103
+ data: { details: buildReqErrorMessage("missing", null) },
104
+ });
105
+ return;
106
+ }
107
+ const collapsed = collapseAnnotationValue(trimmed);
108
+ const reqPattern = /^REQ-[A-Z0-9-]+$/;
109
+ if (!reqPattern.test(collapsed)) {
110
+ context.report({
111
+ node: comment,
112
+ messageId: "invalidReqFormat",
113
+ data: { details: buildReqErrorMessage("invalid", collapsed) },
114
+ });
115
+ }
116
+ }
117
+ /**
118
+ * Process a single comment node and validate any @story/@req annotations it contains.
119
+ *
120
+ * Supports annotations whose values span multiple lines within the same
121
+ * comment block, collapsing whitespace so that the logical value can be
122
+ * validated against the configured patterns.
123
+ *
124
+ * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
125
+ * @req REQ-MULTILINE-SUPPORT - Handle annotations split across multiple lines
126
+ * @req REQ-FLEXIBLE-PARSING - Support reasonable variations in whitespace and formatting
10
127
  */
128
+ function processComment(context, comment) {
129
+ const rawLines = (comment.value || "").split(/\r?\n/);
130
+ let pending = null;
131
+ /**
132
+ * Finalize and validate the currently pending annotation, if any.
133
+ *
134
+ * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
135
+ * @req REQ-SYNTAX-VALIDATION - Validate annotation syntax matches specification
136
+ */
137
+ function finalizePending() {
138
+ if (!pending) {
139
+ return;
140
+ }
141
+ // @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
142
+ // @req REQ-SYNTAX-VALIDATION - Dispatch validation based on annotation type
143
+ if (pending.type === "story") {
144
+ validateStoryAnnotation(context, comment, pending.value);
145
+ }
146
+ else {
147
+ validateReqAnnotation(context, comment, pending.value);
148
+ }
149
+ pending = null;
150
+ }
151
+ rawLines.forEach((rawLine) => {
152
+ const normalized = normalizeCommentLine(rawLine);
153
+ if (!normalized) {
154
+ return;
155
+ }
156
+ const isStory = /@story\b/.test(normalized);
157
+ const isReq = /@req\b/.test(normalized);
158
+ // @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
159
+ // @req REQ-SYNTAX-VALIDATION - Start new pending annotation when a tag is found
160
+ if (isStory || isReq) {
161
+ finalizePending();
162
+ const value = normalized.replace(/^@story\b|^@req\b/, "").trim();
163
+ pending = {
164
+ type: isStory ? "story" : "req",
165
+ value,
166
+ hasValue: value.trim().length > 0,
167
+ };
168
+ return;
169
+ }
170
+ // @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
171
+ // @req REQ-MULTILINE-SUPPORT - Treat subsequent lines as continuation for pending annotation
172
+ if (pending) {
173
+ const continuation = normalized.trim();
174
+ if (!continuation) {
175
+ return;
176
+ }
177
+ pending.value = pending.value
178
+ ? `${pending.value} ${continuation}`
179
+ : continuation;
180
+ pending.hasValue = pending.hasValue || continuation.length > 0;
181
+ }
182
+ });
183
+ finalizePending();
184
+ }
11
185
  exports.default = {
12
186
  meta: {
13
187
  type: "problem",
@@ -16,8 +190,8 @@ exports.default = {
16
190
  recommended: "error",
17
191
  },
18
192
  messages: {
19
- invalidStoryFormat: "Invalid @story annotation format",
20
- invalidReqFormat: "Invalid @req annotation format",
193
+ invalidStoryFormat: "{{details}}",
194
+ invalidReqFormat: "{{details}}",
21
195
  },
22
196
  schema: [],
23
197
  },
@@ -31,6 +205,7 @@ exports.default = {
31
205
  return {
32
206
  /**
33
207
  * Program-level handler that inspects all comments for @story and @req tags
208
+ *
34
209
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
35
210
  * @req REQ-PATH-FORMAT - Validate @story paths follow expected patterns
36
211
  * @req REQ-REQ-FORMAT - Validate @req identifiers follow expected patterns
@@ -38,32 +213,7 @@ exports.default = {
38
213
  Program() {
39
214
  const comments = sourceCode.getAllComments() || [];
40
215
  comments.forEach((comment) => {
41
- const lines = comment.value
42
- .split(/\r?\n/)
43
- .map((l) => l.replace(/^[^@]*/, "").trim());
44
- lines.forEach((line) => {
45
- if (line.startsWith("@story")) {
46
- const parts = line.split(/\s+/);
47
- const storyPath = parts[1];
48
- if (!storyPath ||
49
- !/^docs\/stories\/[0-9]+\.[0-9]+-DEV-[\w-]+\.story\.md$/.test(storyPath)) {
50
- context.report({
51
- node: comment,
52
- messageId: "invalidStoryFormat",
53
- });
54
- }
55
- }
56
- if (line.startsWith("@req")) {
57
- const parts = line.split(/\s+/);
58
- const reqId = parts[1];
59
- if (!reqId || !/^REQ-[A-Z0-9-]+$/.test(reqId)) {
60
- context.report({
61
- node: comment,
62
- messageId: "invalidReqFormat",
63
- });
64
- }
65
- }
66
- });
216
+ processComment(context, comment);
67
217
  });
68
218
  },
69
219
  };
@@ -17,41 +17,148 @@ describe("Valid Annotation Format Rule (Story 005.0-DEV-ANNOTATION-VALIDATION)",
17
17
  ruleTester.run("valid-annotation-format", valid_annotation_format_1.default, {
18
18
  valid: [
19
19
  {
20
- name: "[REQ-PATH-FORMAT] valid story annotation format",
20
+ name: "[REQ-PATH-FORMAT] valid story annotation format (single-line)",
21
21
  code: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md`,
22
22
  },
23
23
  {
24
- name: "[REQ-REQ-FORMAT] valid req annotation format",
24
+ name: "[REQ-REQ-FORMAT] valid req annotation format (single-line)",
25
25
  code: `// @req REQ-EXAMPLE`,
26
26
  },
27
27
  {
28
- name: "[REQ-FORMAT-SPECIFICATION] valid block annotations",
28
+ name: "[REQ-FORMAT-SPECIFICATION] valid block annotations (single-line values)",
29
29
  code: `/**
30
30
  * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
31
31
  * @req REQ-VALID-EXAMPLE
32
+ */`,
33
+ },
34
+ {
35
+ name: "[REQ-MULTILINE-SUPPORT] valid multi-line @story annotation value in block comment",
36
+ code: `/**
37
+ * @story docs/stories/005.0-
38
+ * DEV-ANNOTATION-VALIDATION.story.md
39
+ */`,
40
+ },
41
+ {
42
+ name: "[REQ-MULTILINE-SUPPORT] valid multi-line @req annotation value in block comment",
43
+ code: `/**
44
+ * @req REQ-
45
+ * EXAMPLE
46
+ */`,
47
+ },
48
+ {
49
+ name: "[REQ-FLEXIBLE-PARSING] valid JSDoc-style comment with leading stars and spacing",
50
+ code: `/**
51
+ * @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story.md
52
+ * @req REQ-FLEXIBLE-PARSING
32
53
  */`,
33
54
  },
34
55
  ],
35
56
  invalid: [
36
57
  {
37
- name: "[REQ-PATH-FORMAT] missing story path",
58
+ name: "[REQ-PATH-FORMAT] missing story path (single line)",
38
59
  code: `// @story`,
39
- errors: [{ messageId: "invalidStoryFormat" }],
60
+ errors: [
61
+ {
62
+ messageId: "invalidStoryFormat",
63
+ data: {
64
+ details: 'Missing story path for @story annotation. Expected a path like "docs/stories/005.0-DEV-EXAMPLE.story.md".',
65
+ },
66
+ },
67
+ ],
40
68
  },
41
69
  {
42
70
  name: "[REQ-PATH-FORMAT] invalid story file extension",
43
71
  code: `// @story docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story`,
44
- errors: [{ messageId: "invalidStoryFormat" }],
72
+ errors: [
73
+ {
74
+ messageId: "invalidStoryFormat",
75
+ data: {
76
+ details: 'Invalid story path "docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story" for @story annotation. Expected a path like "docs/stories/005.0-DEV-EXAMPLE.story.md".',
77
+ },
78
+ },
79
+ ],
45
80
  },
46
81
  {
47
- name: "[REQ-REQ-FORMAT] missing req id",
82
+ name: "[REQ-REQ-FORMAT] missing req id (single line)",
48
83
  code: `// @req`,
49
- errors: [{ messageId: "invalidReqFormat" }],
84
+ errors: [
85
+ {
86
+ messageId: "invalidReqFormat",
87
+ data: {
88
+ details: 'Missing requirement ID for @req annotation. Expected an identifier like "REQ-EXAMPLE".',
89
+ },
90
+ },
91
+ ],
50
92
  },
51
93
  {
52
- name: "[REQ-REQ-FORMAT] invalid req id format",
94
+ name: "[REQ-REQ-FORMAT] invalid req id format (single line)",
53
95
  code: `// @req invalid-format`,
54
- errors: [{ messageId: "invalidReqFormat" }],
96
+ errors: [
97
+ {
98
+ messageId: "invalidReqFormat",
99
+ data: {
100
+ details: 'Invalid requirement ID "invalid-format" for @req annotation. Expected an identifier like "REQ-EXAMPLE" (uppercase letters, numbers, and dashes only).',
101
+ },
102
+ },
103
+ ],
104
+ },
105
+ {
106
+ name: "[REQ-MULTILINE-SUPPORT] missing story path with multi-line block comment",
107
+ code: `/**
108
+ * @story
109
+ */`,
110
+ errors: [
111
+ {
112
+ messageId: "invalidStoryFormat",
113
+ data: {
114
+ details: 'Missing story path for @story annotation. Expected a path like "docs/stories/005.0-DEV-EXAMPLE.story.md".',
115
+ },
116
+ },
117
+ ],
118
+ },
119
+ {
120
+ name: "[REQ-MULTILINE-SUPPORT] invalid multi-line story path after collapsing whitespace",
121
+ code: `/**
122
+ * @story docs/stories/005.0-
123
+ * DEV-ANNOTATION-VALIDATION.story
124
+ */`,
125
+ errors: [
126
+ {
127
+ messageId: "invalidStoryFormat",
128
+ data: {
129
+ details: 'Invalid story path "docs/stories/005.0-DEV-ANNOTATION-VALIDATION.story" for @story annotation. Expected a path like "docs/stories/005.0-DEV-EXAMPLE.story.md".',
130
+ },
131
+ },
132
+ ],
133
+ },
134
+ {
135
+ name: "[REQ-MULTILINE-SUPPORT] missing req id with multi-line block comment",
136
+ code: `/**
137
+ * @req
138
+ */`,
139
+ errors: [
140
+ {
141
+ messageId: "invalidReqFormat",
142
+ data: {
143
+ details: 'Missing requirement ID for @req annotation. Expected an identifier like "REQ-EXAMPLE".',
144
+ },
145
+ },
146
+ ],
147
+ },
148
+ {
149
+ name: "[REQ-MULTILINE-SUPPORT] invalid multi-line req id after collapsing whitespace",
150
+ code: `/**
151
+ * @req invalid-
152
+ * format
153
+ */`,
154
+ errors: [
155
+ {
156
+ messageId: "invalidReqFormat",
157
+ data: {
158
+ details: 'Invalid requirement ID "invalid-format" for @req annotation. Expected an identifier like "REQ-EXAMPLE" (uppercase letters, numbers, and dashes only).',
159
+ },
160
+ },
161
+ ],
55
162
  },
56
163
  ],
57
164
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-traceability",
3
- "version": "1.4.11",
3
+ "version": "1.5.0",
4
4
  "description": "A customizable ESLint plugin that enforces traceability annotations in your code, ensuring each implementation is linked to its requirement or test case.",
5
5
  "main": "lib/src/index.js",
6
6
  "types": "lib/src/index.d.ts",