Skip to content

Commit cea3560

Browse files
committed
fix(check-examples): preserve whitespace so as to report issues with whitespace-related rules such as indent (fixes #211)
1 parent 44a353e commit cea3560

File tree

4 files changed

+78
-24
lines changed

4 files changed

+78
-24
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"dependencies": {
88
"comment-parser": "^0.5.5",
99
"debug": "^4.1.1",
10-
"escape-regex-string": "^1.0.6",
1110
"flat-map-polyfill": "^0.3.8",
1211
"jsdoctypeparser": "5.0.1",
1312
"lodash": "^4.17.14",

src/iterateJsdoc.js

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,59 @@ import commentParser from 'comment-parser';
33
import jsdocUtils from './jsdocUtils';
44
import getJSDocComment from './eslint/getJSDocComment';
55

6-
const parseComment = (commentNode, indent) => {
6+
/**
7+
*
8+
* @param {object} commentNode
9+
* @param {string} indent Whitespace
10+
* @returns {object}
11+
*/
12+
const parseComment = (commentNode, indent, trim = true) => {
713
// Preserve JSDoc block start/end indentation.
814
return commentParser(`${indent}/*${commentNode.value}${indent}*/`, {
915
// @see https://github.com/yavorskiy/comment-parser/issues/21
1016
parsers: [
1117
commentParser.PARSERS.parse_tag,
1218
commentParser.PARSERS.parse_type,
1319
(str, data) => {
14-
if (['return', 'returns', 'throws', 'exception'].includes(data.tag)) {
20+
if (['example', 'return', 'returns', 'throws', 'exception'].includes(data.tag)) {
1521
return null;
1622
}
1723

1824
return commentParser.PARSERS.parse_name(str, data);
1925
},
20-
commentParser.PARSERS.parse_description
21-
]
26+
trim ?
27+
commentParser.PARSERS.parse_description :
28+
29+
// parse_description
30+
(str, data) => {
31+
// Only expected throw in previous step is if bad name (i.e.,
32+
// missing end bracket on optional name), but `@example`
33+
// skips name parsing
34+
/* istanbul ignore next */
35+
if (data.errors && data.errors.length) {
36+
return null;
37+
}
38+
39+
// Tweak original regex to capture only single optional space
40+
const result = str.match(/^\s?((.|\s)+)?/);
41+
42+
// Always has at least whitespace due to `indent` we've added
43+
/* istanbul ignore next */
44+
if (result) {
45+
return {
46+
data: {
47+
description: result[1] === undefined ? '' : result[1]
48+
},
49+
source: result[0]
50+
};
51+
}
52+
53+
// Always has at least whitespace due to `indent` we've added
54+
/* istanbul ignore next */
55+
return null;
56+
}
57+
],
58+
trim
2259
})[0] || {};
2360
};
2461

@@ -322,7 +359,7 @@ const iterateAllJsdocs = (iterator, ruleConfig) => {
322359
}
323360

324361
const indent = ' '.repeat(comment.loc.start.column);
325-
const jsdoc = parseComment(comment, indent);
362+
const jsdoc = parseComment(comment, indent, !ruleConfig.noTrim);
326363
const settings = getSettings(context);
327364
const report = makeReport(context, comment);
328365
const jsdocNode = comment;
@@ -368,7 +405,10 @@ export default function iterateJsdoc (iterator, ruleConfig) {
368405
}
369406

370407
if (ruleConfig.iterateAllJsdocs) {
371-
return iterateAllJsdocs(iterator, {meta: ruleConfig.meta});
408+
return iterateAllJsdocs(iterator, {
409+
meta: ruleConfig.meta,
410+
noTrim: ruleConfig.noTrim
411+
});
372412
}
373413

374414
return {

src/rules/checkExamples.js

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {CLIEngine, Linter} from 'eslint';
2-
import escapeRegexString from 'escape-regex-string';
32
import iterateJsdoc from '../iterateJsdoc';
43
import warnRemovedSettings from '../warnRemovedSettings';
54

@@ -72,13 +71,9 @@ export default iterateJsdoc(({
7271

7372
utils.forEachPreferredTag('example', (tag, targetTagName) => {
7473
// If a space is present, we should ignore it
75-
const initialTag = tag.source.match(
76-
new RegExp(`^@${escapeRegexString(targetTagName)} ?`, 'u')
77-
);
78-
const initialTagLength = initialTag[0].length;
79-
const firstLinePrefixLength = preTagSpaceLength + initialTagLength;
74+
const firstLinePrefixLength = preTagSpaceLength;
8075

81-
let source = tag.source.slice(initialTagLength);
76+
let source = tag.description;
8277
const match = source.match(hasCaptionRegex);
8378

8479
if (captionRequired && (!match || !match[1].trim())) {
@@ -101,16 +96,14 @@ export default iterateJsdoc(({
10196
const idx = source.search(exampleCodeRegex);
10297

10398
// Strip out anything preceding user regex match (can affect line numbering)
104-
let preMatchLines = 0;
105-
10699
const preMatch = source.slice(0, idx);
107100

108-
preMatchLines = countChars(preMatch, '\n');
101+
const preMatchLines = countChars(preMatch, '\n');
109102

110103
nonJSPrefacingLines = preMatchLines;
111104

112105
const colDelta = preMatchLines ?
113-
preMatch.slice(preMatch.lastIndexOf('\n') + 1).length - initialTagLength :
106+
preMatch.slice(preMatch.lastIndexOf('\n') + 1).length :
114107
preMatch.length;
115108

116109
// Get rid of text preceding user regex match (even if it leaves valid JS, it
@@ -135,7 +128,7 @@ export default iterateJsdoc(({
135128
if (nonJSPrefaceLineCount) {
136129
const charsInLastLine = nonJSPreface.slice(nonJSPreface.lastIndexOf('\n') + 1).length;
137130

138-
nonJSPrefacingCols += charsInLastLine - initialTagLength;
131+
nonJSPrefacingCols += charsInLastLine;
139132
} else {
140133
nonJSPrefacingCols += colDelta + nonJSPreface.length;
141134
}
@@ -277,5 +270,6 @@ export default iterateJsdoc(({
277270
}
278271
],
279272
type: 'suggestion'
280-
}
273+
},
274+
noTrim: true
281275
});

test/rules/assertions/checkExamples.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export default {
8686
code: `
8787
/**
8888
* @example
89+
*
8990
* \`\`\`js alert('hello'); \`\`\`
9091
*/
9192
function quux () {
@@ -184,7 +185,7 @@ export default {
184185
}
185186
},
186187
eslintrcForExamples: false,
187-
rejectExampleCodeRegex: '^\\s*<.*>$'
188+
rejectExampleCodeRegex: '^\\s*<.*>\\s*$'
188189
}]
189190
},
190191
{
@@ -305,7 +306,7 @@ export default {
305306
code: `
306307
/**
307308
* @example const i = 5;
308-
* quux2()
309+
* quux2()
309310
*/
310311
function quux2 () {
311312
@@ -327,7 +328,7 @@ export default {
327328
code: `
328329
/**
329330
* @example const i = 5;
330-
* quux2()
331+
* quux2()
331332
*/
332333
function quux2 () {
333334
@@ -346,7 +347,7 @@ export default {
346347
code: `
347348
/**
348349
* @example const i = 5;
349-
* quux2()
350+
* quux2()
350351
*/
351352
function quux2 () {
352353
@@ -608,6 +609,26 @@ export default {
608609
eslintrcForExamples: false,
609610
exampleCodeRegex: '```js([\\s\\S]*)```'
610611
}]
612+
},
613+
{
614+
code: `
615+
/**
616+
* @example
617+
* foo(function (err) {
618+
* throw err;
619+
* });
620+
*/
621+
function quux () {}
622+
`,
623+
options: [{
624+
baseConfig: {
625+
rules: {
626+
indent: ['error']
627+
}
628+
},
629+
eslintrcForExamples: false,
630+
noDefaultExampleRules: false
631+
}]
611632
}
612633
]
613634
};

0 commit comments

Comments
 (0)