Skip to content

Commit 58518f5

Browse files
authored
Merge pull request #335 from brettz9/indent
Indent
2 parents 44a353e + 2bf4caa commit 58518f5

File tree

5 files changed

+131
-25
lines changed

5 files changed

+131
-25
lines changed

.README/rules/check-examples.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ syntax highlighting). The following options determine whether a given
2626
so you may wish to use `(?:...)` groups where you do not wish the
2727
first such group treated as one to include. If no parenthetical group
2828
exists or matches, the whole matching expression will be used.
29-
An example might be ````"^```(?:js|javascript)([\\s\\S]*)```$"````
29+
An example might be ````"^```(?:js|javascript)([\\s\\S]*)```\s*$"````
3030
to only match explicitly fenced JavaScript blocks.
3131
* `rejectExampleCodeRegex` - Regex blacklist which rejects
3232
non-lintable examples (has priority over `exampleCodeRegex`). An example
@@ -37,6 +37,24 @@ If neither is in use, all examples will be matched. Note also that even if
3737
`captionRequired` is not set, any initial `<caption>` will be stripped out
3838
before doing the regex matching.
3939

40+
#### `paddedIndent`
41+
42+
This integer property allows one to add a fixed amount of whitespace at the
43+
beginning of the second or later lines of the example to be stripped so as
44+
to avoid linting issues with the decorative whitespace. For example, if set
45+
to a value of `4`, the initial whitespace below will not trigger `indent`
46+
rule errors as the extra 4 spaces on each subsequent line will be stripped
47+
out before evaluation.
48+
49+
```js
50+
/**
51+
* @example
52+
* anArray.filter((a) => {
53+
* return a.b;
54+
* });
55+
*/
56+
```
57+
4058
#### `reportUnusedDisableDirectives`
4159

4260
If not set to `false`, `reportUnusedDisableDirectives` will report disabled

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: 16 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

@@ -30,6 +29,7 @@ export default iterateJsdoc(({
3029
noDefaultExampleRules = false,
3130
eslintrcForExamples = true,
3231
matchingFileName: filename = null,
32+
paddedIndent = 0,
3333
baseConfig = {},
3434
configFile,
3535
allowInlineConfig = true,
@@ -72,13 +72,9 @@ export default iterateJsdoc(({
7272

7373
utils.forEachPreferredTag('example', (tag, targetTagName) => {
7474
// 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;
75+
const firstLinePrefixLength = preTagSpaceLength;
8076

81-
let source = tag.source.slice(initialTagLength);
77+
let source = tag.description;
8278
const match = source.match(hasCaptionRegex);
8379

8480
if (captionRequired && (!match || !match[1].trim())) {
@@ -101,16 +97,14 @@ export default iterateJsdoc(({
10197
const idx = source.search(exampleCodeRegex);
10298

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

108-
preMatchLines = countChars(preMatch, '\n');
102+
const preMatchLines = countChars(preMatch, '\n');
109103

110104
nonJSPrefacingLines = preMatchLines;
111105

112106
const colDelta = preMatchLines ?
113-
preMatch.slice(preMatch.lastIndexOf('\n') + 1).length - initialTagLength :
107+
preMatch.slice(preMatch.lastIndexOf('\n') + 1).length :
114108
preMatch.length;
115109

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

138-
nonJSPrefacingCols += charsInLastLine - initialTagLength;
132+
nonJSPrefacingCols += charsInLastLine;
139133
} else {
140134
nonJSPrefacingCols += colDelta + nonJSPreface.length;
141135
}
@@ -157,6 +151,10 @@ export default iterateJsdoc(({
157151

158152
let messages;
159153

154+
if (paddedIndent) {
155+
source = source.replace(new RegExp(`(^|\n) {${paddedIndent}}(?!$)`, 'g'), '\n');
156+
}
157+
160158
if (filename) {
161159
const config = cli.getConfigForFile(filename);
162160

@@ -265,6 +263,10 @@ export default iterateJsdoc(({
265263
default: false,
266264
type: 'boolean'
267265
},
266+
paddedIndent: {
267+
default: 0,
268+
type: 'integer'
269+
},
268270
rejectExampleCodeRegex: {
269271
type: 'string'
270272
},
@@ -277,5 +279,6 @@ export default iterateJsdoc(({
277279
}
278280
],
279281
type: 'suggestion'
280-
}
282+
},
283+
noTrim: true
281284
});

test/rules/assertions/checkExamples.js

Lines changed: 50 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,32 @@ export default {
327328
code: `
328329
/**
329330
* @example const i = 5;
330-
* quux2()
331+
* quux2()
332+
*/
333+
function quux2 () {
334+
335+
}
336+
`,
337+
errors: [
338+
{
339+
message: '@example warning (id-length): Identifier name \'i\' is too short (< 2).'
340+
},
341+
{
342+
message: '@example error (semi): Missing semicolon.'
343+
}
344+
],
345+
options: [
346+
{
347+
paddedIndent: 2
348+
}
349+
]
350+
},
351+
{
352+
code: `
353+
/**
354+
* @example
355+
* const i = 5;
356+
* quux2()
331357
*/
332358
function quux2 () {
333359
@@ -346,7 +372,7 @@ export default {
346372
code: `
347373
/**
348374
* @example const i = 5;
349-
* quux2()
375+
* quux2()
350376
*/
351377
function quux2 () {
352378
@@ -608,6 +634,26 @@ export default {
608634
eslintrcForExamples: false,
609635
exampleCodeRegex: '```js([\\s\\S]*)```'
610636
}]
637+
},
638+
{
639+
code: `
640+
/**
641+
* @example
642+
* foo(function (err) {
643+
* throw err;
644+
* });
645+
*/
646+
function quux () {}
647+
`,
648+
options: [{
649+
baseConfig: {
650+
rules: {
651+
indent: ['error']
652+
}
653+
},
654+
eslintrcForExamples: false,
655+
noDefaultExampleRules: false
656+
}]
611657
}
612658
]
613659
};

0 commit comments

Comments
 (0)