Skip to content

Commit

Permalink
Merge pull request #335 from brettz9/indent
Browse files Browse the repository at this point in the history
Indent
  • Loading branch information
brettz9 committed Jul 13, 2019
2 parents 44a353e + 2bf4caa commit 58518f5
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 25 deletions.
20 changes: 19 additions & 1 deletion .README/rules/check-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ syntax highlighting). The following options determine whether a given
so you may wish to use `(?:...)` groups where you do not wish the
first such group treated as one to include. If no parenthetical group
exists or matches, the whole matching expression will be used.
An example might be ````"^```(?:js|javascript)([\\s\\S]*)```$"````
An example might be ````"^```(?:js|javascript)([\\s\\S]*)```\s*$"````
to only match explicitly fenced JavaScript blocks.
* `rejectExampleCodeRegex` - Regex blacklist which rejects
non-lintable examples (has priority over `exampleCodeRegex`). An example
Expand All @@ -37,6 +37,24 @@ If neither is in use, all examples will be matched. Note also that even if
`captionRequired` is not set, any initial `<caption>` will be stripped out
before doing the regex matching.

#### `paddedIndent`

This integer property allows one to add a fixed amount of whitespace at the
beginning of the second or later lines of the example to be stripped so as
to avoid linting issues with the decorative whitespace. For example, if set
to a value of `4`, the initial whitespace below will not trigger `indent`
rule errors as the extra 4 spaces on each subsequent line will be stripped
out before evaluation.

```js
/**
* @example
* anArray.filter((a) => {
* return a.b;
* });
*/
```

#### `reportUnusedDisableDirectives`

If not set to `false`, `reportUnusedDisableDirectives` will report disabled
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"dependencies": {
"comment-parser": "^0.5.5",
"debug": "^4.1.1",
"escape-regex-string": "^1.0.6",
"flat-map-polyfill": "^0.3.8",
"jsdoctypeparser": "5.0.1",
"lodash": "^4.17.14",
Expand Down
52 changes: 46 additions & 6 deletions src/iterateJsdoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,59 @@ import commentParser from 'comment-parser';
import jsdocUtils from './jsdocUtils';
import getJSDocComment from './eslint/getJSDocComment';

const parseComment = (commentNode, indent) => {
/**
*
* @param {object} commentNode
* @param {string} indent Whitespace
* @returns {object}
*/
const parseComment = (commentNode, indent, trim = true) => {
// Preserve JSDoc block start/end indentation.
return commentParser(`${indent}/*${commentNode.value}${indent}*/`, {
// @see https://github.com/yavorskiy/comment-parser/issues/21
parsers: [
commentParser.PARSERS.parse_tag,
commentParser.PARSERS.parse_type,
(str, data) => {
if (['return', 'returns', 'throws', 'exception'].includes(data.tag)) {
if (['example', 'return', 'returns', 'throws', 'exception'].includes(data.tag)) {
return null;
}

return commentParser.PARSERS.parse_name(str, data);
},
commentParser.PARSERS.parse_description
]
trim ?
commentParser.PARSERS.parse_description :

// parse_description
(str, data) => {
// Only expected throw in previous step is if bad name (i.e.,
// missing end bracket on optional name), but `@example`
// skips name parsing
/* istanbul ignore next */
if (data.errors && data.errors.length) {
return null;
}

// Tweak original regex to capture only single optional space
const result = str.match(/^\s?((.|\s)+)?/);

// Always has at least whitespace due to `indent` we've added
/* istanbul ignore next */
if (result) {
return {
data: {
description: result[1] === undefined ? '' : result[1]
},
source: result[0]
};
}

// Always has at least whitespace due to `indent` we've added
/* istanbul ignore next */
return null;
}
],
trim
})[0] || {};
};

Expand Down Expand Up @@ -322,7 +359,7 @@ const iterateAllJsdocs = (iterator, ruleConfig) => {
}

const indent = ' '.repeat(comment.loc.start.column);
const jsdoc = parseComment(comment, indent);
const jsdoc = parseComment(comment, indent, !ruleConfig.noTrim);
const settings = getSettings(context);
const report = makeReport(context, comment);
const jsdocNode = comment;
Expand Down Expand Up @@ -368,7 +405,10 @@ export default function iterateJsdoc (iterator, ruleConfig) {
}

if (ruleConfig.iterateAllJsdocs) {
return iterateAllJsdocs(iterator, {meta: ruleConfig.meta});
return iterateAllJsdocs(iterator, {
meta: ruleConfig.meta,
noTrim: ruleConfig.noTrim
});
}

return {
Expand Down
29 changes: 16 additions & 13 deletions src/rules/checkExamples.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {CLIEngine, Linter} from 'eslint';
import escapeRegexString from 'escape-regex-string';
import iterateJsdoc from '../iterateJsdoc';
import warnRemovedSettings from '../warnRemovedSettings';

Expand Down Expand Up @@ -30,6 +29,7 @@ export default iterateJsdoc(({
noDefaultExampleRules = false,
eslintrcForExamples = true,
matchingFileName: filename = null,
paddedIndent = 0,
baseConfig = {},
configFile,
allowInlineConfig = true,
Expand Down Expand Up @@ -72,13 +72,9 @@ export default iterateJsdoc(({

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

let source = tag.source.slice(initialTagLength);
let source = tag.description;
const match = source.match(hasCaptionRegex);

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

// Strip out anything preceding user regex match (can affect line numbering)
let preMatchLines = 0;

const preMatch = source.slice(0, idx);

preMatchLines = countChars(preMatch, '\n');
const preMatchLines = countChars(preMatch, '\n');

nonJSPrefacingLines = preMatchLines;

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

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

nonJSPrefacingCols += charsInLastLine - initialTagLength;
nonJSPrefacingCols += charsInLastLine;
} else {
nonJSPrefacingCols += colDelta + nonJSPreface.length;
}
Expand All @@ -157,6 +151,10 @@ export default iterateJsdoc(({

let messages;

if (paddedIndent) {
source = source.replace(new RegExp(`(^|\n) {${paddedIndent}}(?!$)`, 'g'), '\n');
}

if (filename) {
const config = cli.getConfigForFile(filename);

Expand Down Expand Up @@ -265,6 +263,10 @@ export default iterateJsdoc(({
default: false,
type: 'boolean'
},
paddedIndent: {
default: 0,
type: 'integer'
},
rejectExampleCodeRegex: {
type: 'string'
},
Expand All @@ -277,5 +279,6 @@ export default iterateJsdoc(({
}
],
type: 'suggestion'
}
},
noTrim: true
});
54 changes: 50 additions & 4 deletions test/rules/assertions/checkExamples.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export default {
code: `
/**
* @example
*
* \`\`\`js alert('hello'); \`\`\`
*/
function quux () {
Expand Down Expand Up @@ -184,7 +185,7 @@ export default {
}
},
eslintrcForExamples: false,
rejectExampleCodeRegex: '^\\s*<.*>$'
rejectExampleCodeRegex: '^\\s*<.*>\\s*$'
}]
},
{
Expand Down Expand Up @@ -305,7 +306,7 @@ export default {
code: `
/**
* @example const i = 5;
* quux2()
* quux2()
*/
function quux2 () {
Expand All @@ -327,7 +328,32 @@ export default {
code: `
/**
* @example const i = 5;
* quux2()
* quux2()
*/
function quux2 () {
}
`,
errors: [
{
message: '@example warning (id-length): Identifier name \'i\' is too short (< 2).'
},
{
message: '@example error (semi): Missing semicolon.'
}
],
options: [
{
paddedIndent: 2
}
]
},
{
code: `
/**
* @example
* const i = 5;
* quux2()
*/
function quux2 () {
Expand All @@ -346,7 +372,7 @@ export default {
code: `
/**
* @example const i = 5;
* quux2()
* quux2()
*/
function quux2 () {
Expand Down Expand Up @@ -608,6 +634,26 @@ export default {
eslintrcForExamples: false,
exampleCodeRegex: '```js([\\s\\S]*)```'
}]
},
{
code: `
/**
* @example
* foo(function (err) {
* throw err;
* });
*/
function quux () {}
`,
options: [{
baseConfig: {
rules: {
indent: ['error']
}
},
eslintrcForExamples: false,
noDefaultExampleRules: false
}]
}
]
};

0 comments on commit 58518f5

Please sign in to comment.