Skip to content

Commit 0e6b80e

Browse files
bary12gajus
authored andcommitted
feat: add fixers to some rules (#54)
* hyphen POC * added fixers to requireDescriptionCompleteSentence * Added check that all sentences start with an uppercase letter, a fixer for it, and a test * Added fixer for checktypes * Added fixer to newlineAfterDescription * removed unused function * Fix eslint errors * More ESlint fixes
1 parent 60eb36b commit 0e6b80e

9 files changed

+247
-37
lines changed

src/iterateJsdoc.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,28 @@ export default (iterator) => {
6363
]
6464
})[0] || {};
6565

66-
const report = (message) => {
67-
context.report(jsdocNode, message);
66+
const report = (message, fixer = null) => {
67+
if (fixer === null) {
68+
context.report(jsdocNode, message);
69+
} else {
70+
context.report({
71+
fix: fixer,
72+
message,
73+
node: jsdocNode
74+
});
75+
}
6876
};
6977

7078
const utils = curryUtils(functionNode, jsdoc, tagNamePreference, additionalTagNames);
7179

7280
iterator({
7381
context,
7482
functionNode,
83+
indent,
7584
jsdoc,
7685
jsdocNode,
7786
report,
87+
sourceCode,
7888
utils
7989
});
8090
};

src/rules/checkTypes.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ const strictNativeTypes = [
4242

4343
export default iterateJsdoc(({
4444
jsdoc,
45+
jsdocNode,
46+
sourceCode,
4547
report
4648
}) => {
4749
const jsdocTags = _.filter(jsdoc.tags, (tag) => {
@@ -51,7 +53,11 @@ export default iterateJsdoc(({
5153
_.forEach(jsdocTags, (jsdocTag) => {
5254
_.some(strictNativeTypes, (strictNativeType) => {
5355
if (strictNativeType.toLowerCase() === jsdocTag.type.toLowerCase() && strictNativeType !== jsdocTag.type) {
54-
report('Invalid JSDoc @' + jsdocTag.tag + ' "' + jsdocTag.name + '" type "' + jsdocTag.type + '".');
56+
const fix = (fixer) => {
57+
return fixer.replaceText(jsdocNode, sourceCode.getText(jsdocNode).replace('{' + jsdocTag.type + '}', '{' + strictNativeType + '}'));
58+
};
59+
60+
report('Invalid JSDoc @' + jsdocTag.tag + ' "' + jsdocTag.name + '" type "' + jsdocTag.type + '".', fix);
5561

5662
return true;
5763
}

src/rules/newlineAfterDescription.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import iterateJsdoc from '../iterateJsdoc';
44
export default iterateJsdoc(({
55
jsdoc,
66
report,
7-
context
7+
context,
8+
jsdocNode,
9+
sourceCode,
10+
indent
811
}) => {
912
let always;
1013

@@ -25,9 +28,29 @@ export default iterateJsdoc(({
2528

2629
if (always) {
2730
if (!descriptionEndsWithANewline) {
28-
report('There must be a newline after the description of the JSDoc block.');
31+
report('There must be a newline after the description of the JSDoc block.', (fixer) => {
32+
const sourceLines = sourceCode.getText(jsdocNode).split('\n');
33+
const lastDescriptionLine = _.findLastIndex(sourceLines, (line) => {
34+
return _.includes(line, _.last(jsdoc.description.split('\n')));
35+
});
36+
37+
// Add the new line
38+
sourceLines.splice(lastDescriptionLine + 1, 0, indent + ' * ');
39+
40+
return fixer.replaceText(jsdocNode, sourceLines.join('\n'));
41+
});
2942
}
3043
} else if (descriptionEndsWithANewline) {
31-
report('There must be no newline after the description of the JSDoc block.');
44+
report('There must be no newline after the description of the JSDoc block.', (fixer) => {
45+
const sourceLines = sourceCode.getText(jsdocNode).split('\n');
46+
const lastDescriptionLine = _.findLastIndex(sourceLines, (line) => {
47+
return _.includes(line, _.last(jsdoc.description.split('\n')));
48+
});
49+
50+
// Remove the extra line
51+
sourceLines.splice(lastDescriptionLine + 1, 1);
52+
53+
return fixer.replaceText(jsdocNode, sourceLines.join('\n'));
54+
});
3255
}
3356
});

src/rules/requireDescriptionCompleteSentence.js

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ const extractParagraphs = (text) => {
55
return text.split(/\n\n/);
66
};
77

8+
const extractSentences = (text) => {
9+
return text.split(/\.\s*/).filter((sentence) => {
10+
// Ignore sentences with only whitespaces.
11+
return !/^\s*$/.test(sentence);
12+
}).map((sentence) => {
13+
// Re-add the dot.
14+
return sentence + '.';
15+
});
16+
};
17+
818
const isNewLinePrecededByAPeriod = (text) => {
919
let lastLineEndsSentence;
1020

@@ -25,26 +35,45 @@ const isCapitalized = (str) => {
2535
return str[0] === str[0].toUpperCase();
2636
};
2737

28-
const validateDescription = (description, report) => {
38+
const capitalize = (str) => {
39+
return str.charAt(0).toUpperCase() + str.slice(1);
40+
};
41+
42+
const validateDescription = (description, report, jsdocNode, sourceCode) => {
2943
if (!description) {
3044
return false;
3145
}
3246

3347
const paragraphs = extractParagraphs(description);
3448

35-
return _.some(paragraphs, (paragraph, index) => {
36-
if (!isCapitalized(paragraph)) {
37-
if (index === 0) {
38-
report('Description must start with an uppercase character.');
39-
} else {
40-
report('Paragraph must start with an uppercase character.');
41-
}
49+
return _.some(paragraphs, (paragraph) => {
50+
const sentences = extractSentences(paragraph);
4251

43-
return true;
52+
if (_.some(sentences, (sentence) => {
53+
return !isCapitalized(sentence);
54+
})) {
55+
report('Sentence should start with an uppercase character.', (fixer) => {
56+
let text = sourceCode.getText(jsdocNode);
57+
58+
for (const sentence of sentences.filter((sentence_) => {
59+
return !isCapitalized(sentence_);
60+
})) {
61+
const beginning = sentence.split(/\n/)[0];
62+
63+
text = text.replace(beginning, capitalize(beginning));
64+
}
65+
66+
return fixer.replaceText(jsdocNode, text);
67+
});
4468
}
4569

4670
if (!/\.$/.test(paragraph)) {
47-
report('Sentence must end with a period.');
71+
report('Sentence must end with a period.', (fixer) => {
72+
const line = _.last(paragraph.split('\n'));
73+
const replacement = sourceCode.getText(jsdocNode).replace(line, line + '.');
74+
75+
return fixer.replaceText(jsdocNode, replacement);
76+
});
4877

4978
return true;
5079
}
@@ -60,10 +89,12 @@ const validateDescription = (description, report) => {
6089
};
6190

6291
export default iterateJsdoc(({
92+
sourceCode,
6393
jsdoc,
64-
report
94+
report,
95+
jsdocNode
6596
}) => {
66-
if (validateDescription(jsdoc.description, report)) {
97+
if (validateDescription(jsdoc.description, report, jsdocNode, sourceCode)) {
6798
return;
6899
}
69100

@@ -74,6 +105,6 @@ export default iterateJsdoc(({
74105
_.some(tags, (tag) => {
75106
const description = _.trimStart(tag.description, '- ');
76107

77-
return validateDescription(description, report);
108+
return validateDescription(description, report, jsdocNode, sourceCode);
78109
});
79110
});

src/rules/requireHyphenBeforeParamDescription.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@ import _ from 'lodash';
22
import iterateJsdoc from '../iterateJsdoc';
33

44
export default iterateJsdoc(({
5+
sourceCode,
56
jsdoc,
6-
report
7+
report,
8+
jsdocNode
79
}) => {
810
const jsdocTags = _.filter(jsdoc.tags, {
911
tag: 'param'
1012
});
1113

1214
_.forEach(jsdocTags, (jsdocTag) => {
1315
if (jsdocTag.description && !_.startsWith(jsdocTag.description, '-')) {
14-
report('There must be a hyphen before @param description.');
16+
report('There must be a hyphen before @param description.', (fixer) => {
17+
const replacement = sourceCode.getText(jsdocNode).replace(jsdocTag.description, '- ' + jsdocTag.description);
18+
19+
return fixer.replaceText(jsdocNode, replacement);
20+
});
1521
}
1622
});
1723
});

test/rules/assertions/checkTypes.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@ export default {
1515
{
1616
message: 'Invalid JSDoc @param "foo" type "Number".'
1717
}
18-
]
18+
],
19+
output: `
20+
/**
21+
* @param {number} foo
22+
*/
23+
function quux (foo) {
24+
25+
}
26+
`
1927
},
2028
{
2129
code: `
@@ -30,7 +38,15 @@ export default {
3038
{
3139
message: 'Invalid JSDoc @arg "foo" type "Number".'
3240
}
33-
]
41+
],
42+
output: `
43+
/**
44+
* @arg {number} foo
45+
*/
46+
function quux (foo) {
47+
48+
}
49+
`
3450
}
3551
],
3652
valid: [

test/rules/assertions/newlineAfterDescription.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,19 @@ export default {
2121
],
2222
options: [
2323
'always'
24-
]
24+
],
25+
output: `
26+
/**
27+
* Foo.
28+
*
29+
* Foo.
30+
*
31+
* @foo
32+
*/
33+
function quux () {
34+
35+
}
36+
`
2537
},
2638
{
2739
code: `
@@ -43,7 +55,18 @@ export default {
4355
],
4456
options: [
4557
'never'
46-
]
58+
],
59+
output: `
60+
/**
61+
* Bar.
62+
*
63+
* Bar.
64+
* @bar
65+
*/
66+
function quux () {
67+
68+
}
69+
`
4770
}
4871
],
4972
valid: [

0 commit comments

Comments
 (0)