Skip to content

Commit 85572c8

Browse files
authored
improvement(eslint-plugin-fluid): Implement auto-fix for no-hyphen-after-jsdoc-tag rule and give better range info (#25935)
Now reports the text range containing the hyphen character and surrounding whitespace, rather than the range containing the TSDoc tag. Also implements auto-fix behavior (replacing hyphen character and surrounding whitespace with a single space).
1 parent 58e34d9 commit 85572c8

File tree

3 files changed

+53
-24
lines changed

3 files changed

+53
-24
lines changed

common/build/eslint-plugin-fluid/src/rules/no-hyphen-after-jsdoc-tag.js

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,47 @@
88
* @typedef {import("eslint").Rule.RuleModule} RuleModule
99
* @typedef {import('@microsoft/tsdoc').DocNode} DocNode
1010
* @typedef {import('@microsoft/tsdoc').DocPlainText} DocPlainText
11+
*
12+
* @typedef {{
13+
* startIndex: number; // Starting character index of the hyphen pattern (inclusive).
14+
* endIndex: number; // Ending character index of the hyphen pattern (exclusive).
15+
* }} HyphenPatternMatch
1116
*/
1217

18+
const { fail } = require("node:assert");
1319
const { DocNodeKind, TSDocParser } = require("@microsoft/tsdoc");
1420

1521
const parser = new TSDocParser();
1622

1723
/**
1824
* Checks if a comment text starts with a hyphen.
1925
* @param {DocPlainText} plainTextNode - The plain text node to check.
26+
* @return {HyphenPatternMatch | undefined} The hyphen pattern match info if found; otherwise, undefined.
2027
*/
2128
function doesTextNodeStartWithHyphen(plainTextNode) {
22-
return plainTextNode.text.trimStart().startsWith("-");
29+
// RegEx explanation:
30+
// ^\s* - Match the start of the string, followed by zero or more whitespace characters
31+
// - - Match the `-` character literal
32+
// \s* - Match zero or more whitespace characters
33+
const match = plainTextNode.text.match(/^\s*-\s*/);
34+
35+
if (!match) {
36+
return undefined;
37+
}
38+
39+
const textRange =
40+
plainTextNode.textExcerpt?.getContainingTextRange() ??
41+
fail("Expected textExcerpt to be defined.");
42+
return {
43+
startIndex: textRange.pos,
44+
endIndex: textRange.pos + match[0].length,
45+
};
2346
}
2447

2548
/**
2649
* Checks if a comment body starts with a hyphen.
2750
* @param { DocNode } commentBodyNode - The doc node representing the body of the comment.
51+
* @return {HyphenPatternMatch | undefined} The hyphen pattern match info if found; otherwise, undefined.
2852
*/
2953
function doesCommentBodyStartWithHyphen(commentBodyNode) {
3054
// Walk down first node of the tree until we find a leaf.
@@ -36,7 +60,7 @@ function doesCommentBodyStartWithHyphen(commentBodyNode) {
3660

3761
const childNodes = commentBodyNode.getChildNodes();
3862
if (childNodes.length === 0) {
39-
return false;
63+
return undefined;
4064
}
4165

4266
return doesCommentBodyStartWithHyphen(childNodes[0]);
@@ -58,6 +82,7 @@ const rule = {
5882
hyphenAfterTag:
5983
"JSDoc/TSDoc block tags must not be followed by a hyphen character (`-`).",
6084
},
85+
fixable: "code",
6186
schema: [],
6287
},
6388

@@ -101,25 +126,20 @@ const rule = {
101126
// Note: the TSDoc format makes it difficult to extract the range information for the block content specifically.
102127
// Instead, we just report the range for the tag itself.
103128
for (const block of blocksToCheck) {
104-
if (doesCommentBodyStartWithHyphen(block.content)) {
105-
const tagTextRange = block.blockTag
106-
.getTokenSequence()
107-
.getContainingTextRange();
108-
const tagTextRangeStart = tagTextRange.pos - 1; // Include the `@`
109-
const tagTextRangeEnd = tagTextRange.end;
110-
const startIndex = sourceCode.getLocFromIndex(
111-
commentStartIndex + tagTextRangeStart,
112-
);
113-
const endIndex = sourceCode.getLocFromIndex(
114-
commentStartIndex + tagTextRangeEnd,
115-
);
129+
const hyphenMatch = doesCommentBodyStartWithHyphen(block.content);
130+
if (hyphenMatch) {
131+
const startIndex = commentStartIndex + hyphenMatch.startIndex - 1;
132+
const endIndex = commentStartIndex + hyphenMatch.endIndex - 1;
116133

117134
context.report({
118135
loc: {
119-
start: startIndex,
120-
end: endIndex,
136+
start: sourceCode.getLocFromIndex(startIndex),
137+
end: sourceCode.getLocFromIndex(endIndex),
121138
},
122139
messageId: "hyphenAfterTag",
140+
fix(fixer) {
141+
return fixer.replaceTextRange([startIndex, endIndex], " ");
142+
},
123143
});
124144
}
125145
}

common/build/eslint-plugin-fluid/src/test/no-hyphen-after-jsdoc-tag.test.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,30 @@ describe(`Do not allow \`-\` following JSDoc/TSDoc tags (eslint ${eslintVersion}
3838
const error1 = result.messages[0];
3939
assert.strictEqual(error1.message, expectedErrorMessage);
4040
assert.strictEqual(error1.line, 8);
41-
assert.strictEqual(error1.column, 4); // 1-based, inclusive
42-
assert.strictEqual(error1.endColumn, 13); // 1-based, exclusive
41+
assert.strictEqual(error1.column, 12); // 1-based, inclusive
42+
assert.strictEqual(error1.endColumn, 15); // 1-based, exclusive
43+
assert.notEqual(error1.fix, undefined);
44+
assert.deepEqual(error1.fix.range, [234, 237]); // 0-based global character index in the file. The start is inclusive, and the end is exclusive.
45+
assert.deepEqual(error1.fix.text, " "); // Replace hyphen and surrounding whitespace with a single space.
4346

4447
// Error 2
4548
const error2 = result.messages[1];
4649
assert.strictEqual(error2.message, expectedErrorMessage);
4750
assert.strictEqual(error2.line, 9);
48-
assert.strictEqual(error2.column, 4); // 1-based, inclusive
49-
assert.strictEqual(error2.endColumn, 16); // 1-based, exclusive
51+
assert.strictEqual(error2.column, 15); // 1-based, inclusive
52+
assert.strictEqual(error2.endColumn, 19); // 1-based, exclusive
53+
assert.notEqual(error2.fix, undefined);
54+
assert.deepEqual(error2.fix.range, [274, 278]); // 0-based global character index in the file. The start is inclusive, and the end is exclusive.
55+
assert.deepEqual(error2.fix.text, " "); // Replace hyphen and surrounding whitespace with a single space.
5056

5157
// Error 3
5258
const error3 = result.messages[2];
5359
assert.strictEqual(error3.message, expectedErrorMessage);
5460
assert.strictEqual(error3.line, 10);
55-
assert.strictEqual(error3.column, 4); // 1-based, inclusive
56-
assert.strictEqual(error3.endColumn, 13); // 1-based, exclusive
61+
assert.strictEqual(error3.column, 12); // 1-based, inclusive
62+
assert.strictEqual(error3.endColumn, 16); // 1-based, exclusive
63+
assert.notEqual(error3.fix, undefined);
64+
assert.deepEqual(error3.fix.range, [338, 342]); // 0-based global character index in the file. The start is inclusive, and the end is exclusive.
65+
assert.deepEqual(error3.fix.text, " "); // Replace hyphen and surrounding whitespace with a single space.
5766
});
5867
});

common/build/eslint-plugin-fluid/src/test/test-cases/no-hyphen-after-jsdoc-tag/test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
/**
77
* I am a test function with pretty standard docs, but all of my tags have hyphens after them :(
88
* @remarks - Here are some remarks.
9-
* @deprecated - This function is deprecated, use something else.
10-
* @returns - The concatenated string.
9+
* @deprecated - This function is deprecated, use something else.
10+
* @returns - The concatenated string.
1111
*/
1212
function invalid<T>(param1: string, param2: T): string {
1313
return `${param1} - ${param2}`;

0 commit comments

Comments
 (0)