From 86c8b2bdcac364815ba8d3cc6ceb1cbf8e641248 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Fri, 12 Jul 2019 17:41:03 -0700 Subject: [PATCH] fix(check-examples): preserve whitespace so as to report issues with whitespace-related rules such as `indent` (fixes #211) --- package.json | 1 - src/iterateJsdoc.js | 52 +++++++++++++++++++++++--- src/rules/checkExamples.js | 20 ++++------ test/rules/assertions/checkExamples.js | 29 ++++++++++++-- 4 files changed, 78 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 730de6c3d..7edea057a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js index f6e8a0f9d..42de5b5b7 100644 --- a/src/iterateJsdoc.js +++ b/src/iterateJsdoc.js @@ -3,7 +3,13 @@ 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 @@ -11,14 +17,45 @@ const parseComment = (commentNode, indent) => { 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] || {}; }; @@ -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; @@ -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 { diff --git a/src/rules/checkExamples.js b/src/rules/checkExamples.js index 483cabb44..be8d6396a 100644 --- a/src/rules/checkExamples.js +++ b/src/rules/checkExamples.js @@ -1,5 +1,4 @@ import {CLIEngine, Linter} from 'eslint'; -import escapeRegexString from 'escape-regex-string'; import iterateJsdoc from '../iterateJsdoc'; import warnRemovedSettings from '../warnRemovedSettings'; @@ -72,13 +71,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())) { @@ -101,16 +96,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 @@ -135,7 +128,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; } @@ -277,5 +270,6 @@ export default iterateJsdoc(({ } ], type: 'suggestion' - } + }, + noTrim: true }); diff --git a/test/rules/assertions/checkExamples.js b/test/rules/assertions/checkExamples.js index fe25703ab..3b498e2ed 100644 --- a/test/rules/assertions/checkExamples.js +++ b/test/rules/assertions/checkExamples.js @@ -86,6 +86,7 @@ export default { code: ` /** * @example + * * \`\`\`js alert('hello'); \`\`\` */ function quux () { @@ -184,7 +185,7 @@ export default { } }, eslintrcForExamples: false, - rejectExampleCodeRegex: '^\\s*<.*>$' + rejectExampleCodeRegex: '^\\s*<.*>\\s*$' }] }, { @@ -305,7 +306,7 @@ export default { code: ` /** * @example const i = 5; - * quux2() + * quux2() */ function quux2 () { @@ -327,7 +328,7 @@ export default { code: ` /** * @example const i = 5; - * quux2() + * quux2() */ function quux2 () { @@ -346,7 +347,7 @@ export default { code: ` /** * @example const i = 5; - * quux2() + * quux2() */ function quux2 () { @@ -608,6 +609,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 + }] } ] };