Skip to content

Commit e041b65

Browse files
committed
feat(check-tag-names): allow reporting of normally valid tag names (fixes gajus#108)
feat(check-tag-names): allow custom error messages when suggesting tags to replace (as with types in `check-types`) Reporting normally valid tags may be of interest for tags like `@todo`. This is implemented by allowing the user to set targeted `tagNamePreference` tags to `false` or to an object with only a `message` and no `replacement` Custom messages for `check-tag-names` are implemented by allowing the user to set targeted `tagNamePreference` tags to an object with `message` and `replacement` (mirroring `preferredTypes` behavior). Note that for other rules leveraging `tagNamePreference` (via `utils.getPreferredTagName`) to find the user's preferred tag name, the `replacement` will be used in the likes of error messages but not the `message`. Also, for various (param, return, and description-related) rules which have used `tagNamePreference` (via the `utils.getPreferredTagName` utility), report to user if they have blocked (probably inadvertently) and not indicated a replacement for the relevant tag needed by the rule in the `tagNamePreference` setting.
1 parent 6baebd6 commit e041b65

26 files changed

+517
-14
lines changed

.README/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,60 @@ Use `settings.jsdoc.tagNamePreference` to configure a preferred alias name for a
147147
}
148148
```
149149

150+
One may also use an object with a `message` and `replacement`, with `{{tagName}}` and `{{preferredType}}` (or its alias `{{replacement}}`) as template variables to be
151+
substituted within `message`.
152+
153+
The following will report the message `@extends is to be used over @augments as it is more evocative of classes than @augments` upon encountering `@augments`.
154+
155+
```json
156+
{
157+
"rules": {},
158+
"settings": {
159+
"jsdoc": {
160+
"tagNamePreference": {
161+
"augments": {
162+
"message": "@{{replacement}} is to be used over @{{tagName}} as it is more evocative of classes than @augments",
163+
"replacement": "extends"
164+
}
165+
}
166+
}
167+
}
168+
}
169+
```
170+
171+
If one wishes to reject a normally valid tag, e.g., `@todo`, one may set the tag to `false`:
172+
173+
```json
174+
{
175+
"rules": {},
176+
"settings": {
177+
"jsdoc": {
178+
"tagNamePreference": {
179+
"todo": false
180+
}
181+
}
182+
}
183+
}
184+
```
185+
186+
Or one may set the targeted tag to an object with a custom `message`, but without a `replacement` property:
187+
188+
```json
189+
{
190+
"rules": {},
191+
"settings": {
192+
"jsdoc": {
193+
"tagNamePreference": {
194+
"todo": {
195+
"message": "We expect immediate perfection, so don't leave to-dos in your code."
196+
}
197+
}
198+
}
199+
}
200+
}
201+
```
202+
203+
150204
The defaults in `eslint-plugin-jsdoc` (for tags which offer
151205
aliases) are as follows:
152206

src/iterateJsdoc.js

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const parseComment = (commentNode, indent) => {
2525
const getUtils = (
2626
node,
2727
jsdoc,
28+
jsdocNode,
2829
{
2930
tagNamePreference,
3031
additionalTagNames,
@@ -37,6 +38,7 @@ const getUtils = (
3738
allowAugmentsExtendsWithoutParam,
3839
checkSeesForNamepaths
3940
},
41+
report,
4042
context
4143
) => {
4244
const ancestors = context.getAncestors();
@@ -57,15 +59,36 @@ const getUtils = (
5759
};
5860

5961
utils.getJsdocParameterNamesDeep = () => {
60-
return jsdocUtils.getJsdocParameterNamesDeep(jsdoc, utils.getPreferredTagName('param'));
62+
const param = utils.getPreferredTagName('param');
63+
if (!param) {
64+
return false;
65+
}
66+
67+
return jsdocUtils.getJsdocParameterNamesDeep(jsdoc, param);
6168
};
6269

6370
utils.getJsdocParameterNames = () => {
64-
return jsdocUtils.getJsdocParameterNames(jsdoc, utils.getPreferredTagName('param'));
71+
const param = utils.getPreferredTagName('param');
72+
if (!param) {
73+
return false;
74+
}
75+
76+
return jsdocUtils.getJsdocParameterNames(jsdoc, param);
6577
};
6678

67-
utils.getPreferredTagName = (name) => {
68-
return jsdocUtils.getPreferredTagName(name, tagNamePreference);
79+
utils.getPreferredTagName = (name, allowObjectReturn = false, defaultMessage = 'Unexpected tag `@{{tagName}}`') => {
80+
const ret = jsdocUtils.getPreferredTagName(name, tagNamePreference);
81+
const isObject = ret && typeof ret === 'object';
82+
if (ret === false || isObject && !ret.replacement) {
83+
const message = isObject && ret.message || defaultMessage;
84+
report(message, null, utils.getTags(name)[0], {
85+
tagName: name
86+
});
87+
88+
return false;
89+
}
90+
91+
return isObject && !allowObjectReturn ? ret.replacement : ret;
6992
};
7093

7194
utils.isValidTag = (name) => {
@@ -346,17 +369,19 @@ const iterateAllJsdocs = (iterator, ruleConfig) => {
346369
const indent = _.repeat(' ', comment.loc.start.column);
347370
const jsdoc = parseComment(comment, indent);
348371
const settings = getSettings(context);
372+
const report = makeReport(context, comment);
373+
const jsdocNode = comment;
349374

350375
iterator({
351376
context,
352377
indent,
353378
jsdoc,
354-
jsdocNode: comment,
379+
jsdocNode,
355380
node: null,
356-
report: makeReport(context, comment),
381+
report,
357382
settings: getSettings(context),
358383
sourceCode: context.getSourceCode(),
359-
utils: getUtils(null, jsdoc, settings, context)
384+
utils: getUtils(null, jsdoc, jsdocNode, settings, report, context)
360385
});
361386
});
362387
}
@@ -433,7 +458,9 @@ export default function iterateJsdoc (iterator, ruleConfig) {
433458
const utils = getUtils(
434459
node,
435460
jsdoc,
461+
jsdocNode,
436462
settings,
463+
report,
437464
context
438465
);
439466

src/rules/checkParamNames.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ export default iterateJsdoc(({
9292
}) => {
9393
const functionParameterNames = utils.getFunctionParameterNames();
9494
const jsdocParameterNamesDeep = utils.getJsdocParameterNamesDeep();
95+
if (!jsdocParameterNamesDeep) {
96+
return;
97+
}
9598
const targetTagName = utils.getPreferredTagName('param');
9699
const isError = validateParameterNames(targetTagName, functionParameterNames, jsdoc, report);
97100

src/rules/checkTagNames.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,34 @@ export default iterateJsdoc(({
1111
return;
1212
}
1313
jsdoc.tags.forEach((jsdocTag) => {
14-
if (utils.isValidTag(jsdocTag.tag)) {
15-
const preferredTagName = utils.getPreferredTagName(jsdocTag.tag);
14+
const tagName = jsdocTag.tag;
15+
if (utils.isValidTag(tagName)) {
16+
let message = 'Invalid JSDoc tag (preference). Replace "{{tagName}}" JSDoc tag with "{{preferredTagName}}".';
17+
let preferredTagName = utils.getPreferredTagName(
18+
tagName,
19+
true,
20+
'Blacklisted tag found (`@{{tagName}}`)'
21+
);
22+
if (!preferredTagName) {
23+
return;
24+
}
25+
if (preferredTagName && typeof preferredTagName === 'object') {
26+
({message, replacement: preferredTagName} = preferredTagName);
27+
}
1628

17-
if (preferredTagName !== jsdocTag.tag) {
18-
report('Invalid JSDoc tag (preference). Replace "' + jsdocTag.tag + '" JSDoc tag with "' + preferredTagName + '".', (fixer) => {
19-
const replacement = sourceCode.getText(jsdocNode).replace('@' + jsdocTag.tag, '@' + preferredTagName);
29+
if (preferredTagName !== tagName) {
30+
report(message, (fixer) => {
31+
const replacement = sourceCode.getText(jsdocNode).replace('@' + tagName, '@' + preferredTagName);
2032

2133
return fixer.replaceText(jsdocNode, replacement);
22-
}, jsdocTag);
34+
}, jsdocTag, {
35+
preferredTagName,
36+
replacement: preferredTagName,
37+
tagName
38+
});
2339
}
2440
} else {
25-
report('Invalid JSDoc tag name "' + jsdocTag.tag + '".', null, jsdocTag);
41+
report('Invalid JSDoc tag name "' + tagName + '".', null, jsdocTag);
2642
}
2743
});
2844
}, {

src/rules/requireDescription.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export default iterateJsdoc(({
1111
}
1212

1313
const targetTagName = utils.getPreferredTagName('description');
14+
if (!targetTagName) {
15+
return;
16+
}
1417

1518
const functionExamples = _.filter(jsdoc.tags, {
1619
tag: targetTagName

src/rules/requireHyphenBeforeParamDescription.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export default iterateJsdoc(({
1111
let always;
1212

1313
const targetTagName = utils.getPreferredTagName('param');
14+
if (!targetTagName) {
15+
return;
16+
}
1417

1518
if (_.has(context.options, 0)) {
1619
always = context.options[0] === 'always';

src/rules/requireParam.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ export default iterateJsdoc(({
77
}) => {
88
const functionParameterNames = utils.getFunctionParameterNames();
99
const jsdocParameterNames = utils.getJsdocParameterNames();
10+
if (!jsdocParameterNames) {
11+
return;
12+
}
1013

1114
if (utils.avoidDocs('param')) {
1215
return;

src/rules/requireParamDescription.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ export default iterateJsdoc(({
55
utils
66
}) => {
77
const targetTagName = utils.getPreferredTagName('param');
8+
if (!targetTagName) {
9+
return;
10+
}
811

912
utils.forEachTag(targetTagName, (jsdocParameter) => {
1013
if (!jsdocParameter.description) {

src/rules/requireParamName.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ export default iterateJsdoc(({
55
utils
66
}) => {
77
const targetTagName = utils.getPreferredTagName('param');
8+
if (!targetTagName) {
9+
return;
10+
}
811

912
utils.forEachTag(targetTagName, (jsdocParameter) => {
1013
if (jsdocParameter.tag && jsdocParameter.name === '') {

src/rules/requireParamType.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ export default iterateJsdoc(({
55
utils
66
}) => {
77
const targetTagName = utils.getPreferredTagName('param');
8+
if (!targetTagName) {
9+
return;
10+
}
811

912
utils.forEachTag(targetTagName, (jsdocParameter) => {
1013
if (!jsdocParameter.type) {

src/rules/requireReturns.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ export default iterateJsdoc(({
5555
const options = context.options[0] || {};
5656

5757
const tagName = utils.getPreferredTagName('returns');
58+
if (!tagName) {
59+
return;
60+
}
5861
const tags = utils.getTags(tagName);
5962

6063
if (tags.length > 1) {

src/rules/requireReturnsCheck.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ export default iterateJsdoc(({
2727
}
2828

2929
const tagName = utils.getPreferredTagName('returns');
30+
if (!tagName) {
31+
return;
32+
}
3033
const tags = utils.getTags(tagName);
3134

3235
if (tags.length === 0) {

src/rules/requireReturnsDescription.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ export default iterateJsdoc(({
55
utils
66
}) => {
77
const targetTagName = utils.getPreferredTagName('returns');
8+
if (!targetTagName) {
9+
return;
10+
}
811

912
utils.forEachTag(targetTagName, (jsdocTag) => {
1013
const type = jsdocTag.type && jsdocTag.type.trim();

src/rules/requireReturnsType.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ export default iterateJsdoc(({
55
utils
66
}) => {
77
const targetTagName = utils.getPreferredTagName('returns');
8+
if (!targetTagName) {
9+
return;
10+
}
811

912
utils.forEachTag(targetTagName, (jsdocTag) => {
1013
if (!jsdocTag.type) {

test/rules/assertions/checkParamNames.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,28 @@ export default {
190190
parserOptions: {
191191
sourceType: 'module'
192192
}
193+
},
194+
{
195+
code: `
196+
/**
197+
* @param foo
198+
*/
199+
function quux (foo) {
200+
201+
}
202+
`,
203+
errors: [
204+
{
205+
message: 'Unexpected tag `@param`'
206+
}
207+
],
208+
settings: {
209+
jsdoc: {
210+
tagNamePreference: {
211+
param: false
212+
}
213+
}
214+
}
193215
}
194216
],
195217
valid: [

0 commit comments

Comments
 (0)