From 14d7832c6e4dd3608a80e22eb333ffcb15bf9e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Sat, 23 Oct 2021 05:12:37 +0200 Subject: [PATCH] feat: support ESLint 8.x (#792) * feat: support ESLint 8.x and Node 17 BREAKING CHANGE: - This update requires the disabling of the `jsdoc/check-examples` rule! We can hopefully restore this rule after https://github.com/eslint/eslint/issues/14745 - Requires ESLint@^7.0.0 || ^8.0.0 - Updates `jsdoc-type-pratt-parser` and `jsdoccomment` Co-authored-by: Brett Zamir --- .README/rules/check-examples.md | 3 ++ .travis.yml | 29 ++++++------ README.md | 17 +++++-- package.json | 33 +++++++------ src/iterateJsdoc.js | 3 ++ src/jsdocUtils.js | 6 ++- src/rules/checkExamples.js | 20 ++++++-- .../requireDescriptionCompleteSentence.js | 2 + src/rules/requireJsdoc.js | 4 +- test/rules/assertions/matchDescription.js | 4 +- test/rules/assertions/requireJsdoc.js | 47 +++++++++++++------ test/rules/index.js | 9 +++- 12 files changed, 118 insertions(+), 59 deletions(-) diff --git a/.README/rules/check-examples.md b/.README/rules/check-examples.md index 2b3cf7d68..d3f12321c 100644 --- a/.README/rules/check-examples.md +++ b/.README/rules/check-examples.md @@ -1,5 +1,8 @@ ### `check-examples` +> **NOTE**: This rule currently does not work in ESLint 8 (we are waiting for +> [issue 14745](https://github.com/eslint/eslint/issues/14745)). + Ensures that (JavaScript) examples within JSDoc adhere to ESLint rules. Also has options to lint the default values of optional `@param`/`@arg`/`@argument` and `@property`/`@prop` tags or the values of `@default`/`@defaultvalue` tags. diff --git a/.travis.yml b/.travis.yml index 8cf348fcd..161316f20 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,25 +3,27 @@ dist: xenial language: node_js node_js: + - "17" - "16" - "14.14.0" - "12.20.0" before_install: - npm config set depth 0 +install: + - echo "Avoid Travis's npm auto-install" before_script: > node_version=$(node -v); - if [ ${node_version:3:1} = "." ]; then - echo "Node 10+" - if [ ${ESLINT} = "6" ]; then - npm install --legacy-peer-deps --no-save "eslint@${ESLINT}" eslint-config-canonical@24.4.4 - else - npm install --legacy-peer-deps --no-save "eslint@${ESLINT}" - fi - else - echo "Node 8+" - npm install --legacy-peer-deps --no-save "eslint@${ESLINT}" husky@3.1.0 semantic-release@15.14.0 eslint-config-canonical@18.1.1 - fi + if [ ${node_version:1:2} = "17" ]; then + echo "Deadsnakes" + sudo add-apt-repository --yes ppa:deadsnakes/ppa + echo "Update apt-get" + sudo apt-get update + echo "Install Python3" + sudo apt-get --assume-yes install python3.6 + sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 + fi; + npm install --legacy-peer-deps --no-save "eslint@${ESLINT}" notifications: email: false script: @@ -30,16 +32,13 @@ script: - npm run build env: jobs: + - ESLINT=8 - ESLINT=7 - - ESLINT=6 jobs: fast_finish: true include: - node_js: 'lts/*' env: LINT=true - exclude: - - node_js: 8 - env: ESLINT=7 after_success: - export NODE_ENV=production - npm run build diff --git a/README.md b/README.md index 48de99531..96ac0e6c1 100644 --- a/README.md +++ b/README.md @@ -945,6 +945,9 @@ function quux (foo) { ### check-examples +> **NOTE**: This rule currently does not work in ESLint 8 (we are waiting for +> [issue 14745](https://github.com/eslint/eslint/issues/14745)). + Ensures that (JavaScript) examples within JSDoc adhere to ESLint rules. Also has options to lint the default values of optional `@param`/`@arg`/`@argument` and `@property`/`@prop` tags or the values of `@default`/`@defaultvalue` tags. @@ -6914,7 +6917,7 @@ class MyClass { */ myClassField = 1 } -// "jsdoc/match-description": ["error"|"warn", {"contexts":["ClassProperty"]}] +// "jsdoc/match-description": ["error"|"warn", {"contexts":["PropertyDefinition"]}] // Message: JSDoc description does not satisfy the regex pattern. /** @@ -7182,7 +7185,7 @@ class MyClass { */ myClassField = 1 } -// "jsdoc/match-description": ["error"|"warn", {"contexts":["ClassProperty"]}] +// "jsdoc/match-description": ["error"|"warn", {"contexts":["PropertyDefinition"]}] /** * Foo. @@ -12813,7 +12816,7 @@ class Animal { @SomeAnnotation('optionalParameter') tail: boolean; } -// "jsdoc/require-jsdoc": ["error"|"warn", {"contexts":["ClassProperty"]}] +// "jsdoc/require-jsdoc": ["error"|"warn", {"contexts":["PropertyDefinition"]}] // Message: Missing JSDoc comment. @Entity('users') @@ -12907,7 +12910,7 @@ export class MyComponentComponent { @Input() public value = new EventEmitter(); } -// "jsdoc/require-jsdoc": ["error"|"warn", {"contexts":["ClassProperty:has(Decorator[expression.callee.name=\"Input\"])"]}] +// "jsdoc/require-jsdoc": ["error"|"warn", {"contexts":["PropertyDefinition > Decorator[expression.callee.name=\"Input\"]"]}] // Message: Missing JSDoc comment. requestAnimationFrame(draw) @@ -12951,6 +12954,12 @@ function comment () { } // "jsdoc/require-jsdoc": ["error"|"warn", {"enableFixer":false,"fixerMessage":" TODO: add comment"}] // Message: Missing JSDoc comment. + +export class InovaAutoCompleteComponent { + public disabled = false; +} +// "jsdoc/require-jsdoc": ["error"|"warn", {"contexts":["PropertyDefinition"],"publicOnly":true}] +// Message: Missing JSDoc comment. ```` The following patterns are not considered problems: diff --git a/package.json b/package.json index 10c679dca..b69c7409c 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,11 @@ "url": "http://gajus.com" }, "dependencies": { - "@es-joy/jsdoccomment": "0.10.8", + "@es-joy/jsdoccomment": "0.12.0", "comment-parser": "1.2.4", "debug": "^4.3.2", "esquery": "^1.4.0", - "jsdoc-type-pratt-parser": "^1.2.0", + "jsdoc-type-pratt-parser": "^2.0.0", "lodash": "^4.17.21", "regextras": "^0.8.0", "semver": "^7.3.5", @@ -26,26 +26,26 @@ "@babel/preset-env": "^7.15.8", "@babel/register": "^7.15.3", "@hkdobrev/run-if-changed": "^0.3.1", - "@typescript-eslint/parser": "^4.33.0", + "@typescript-eslint/parser": "^5.1.0", "babel-plugin-add-module-exports": "^1.0.4", - "babel-plugin-istanbul": "^6.0.0", + "babel-plugin-istanbul": "^6.1.1", "chai": "^4.3.4", "cross-env": "^7.0.3", - "eslint": "7.32.0", - "eslint-config-canonical": "^28.0.0", + "eslint": "^8.1.0", + "eslint-config-canonical": "^30.1.0", "gitdown": "^3.1.4", "glob": "^7.2.0", - "husky": "^7.0.2", - "lint-staged": "^11.2.1", - "mocha": "^9.1.2", + "husky": "^7.0.4", + "lint-staged": "^11.2.3", + "mocha": "^9.1.3", "nyc": "^15.1.0", "open-editor": "^3.0.0", "rimraf": "^3.0.2", "semantic-release": "^18.0.0", - "typescript": "^4.4.3" + "typescript": "^4.4.4" }, "engines": { - "node": "^12 || ^14 || ^16" + "node": "^12 || ^14 || ^16 || ^17" }, "lint-staged": { ".eslintignore": "npm run lint", @@ -65,7 +65,7 @@ "main": "./dist/index.js", "name": "eslint-plugin-jsdoc", "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0" + "eslint": "^7.0.0 || ^8.0.0" }, "repository": { "type": "git", @@ -80,10 +80,10 @@ "lint-fix": "eslint --report-unused-disable-directives --fix ./src ./test", "lint": "eslint --report-unused-disable-directives --ignore-pattern '!.ncurc.js' ./src ./test .ncurc.js", "lint-arg": "eslint --report-unused-disable-directives", - "test-cov": "cross-env BABEL_ENV=test nyc mocha --parallel --reporter dot --recursive --require @babel/register --timeout 12000", - "test-no-cov": "cross-env BABEL_ENV=test mocha --parallel --reporter dot --recursive --require @babel/register --timeout 12000", + "test-cov": "cross-env BABEL_ENV=test nyc mocha --reporter dot --recursive --require @babel/register --timeout 12000", + "test-no-cov": "cross-env BABEL_ENV=test mocha --reporter dot --recursive --require @babel/register --timeout 12000", "test-index": "cross-env BABEL_ENV=test mocha --recursive --require @babel/register --reporter progress --timeout 12000 test/rules/index.js", - "test": "cross-env BABEL_ENV=test nyc --reporter text-summary mocha --parallel --reporter dot --recursive --require @babel/register --timeout 12000", + "test": "cross-env BABEL_ENV=test nyc --reporter text-summary mocha --reporter dot --recursive --require @babel/register --timeout 12000", "prepare": "husky install" }, "nyc": { @@ -95,6 +95,9 @@ "include": [ "src/" ], + "exclude": [ + "src/rules/checkExamples.js" + ], "check-coverage": true, "branches": 100, "lines": 100, diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js index 5b52dde27..b2c50119b 100644 --- a/src/iterateJsdoc.js +++ b/src/iterateJsdoc.js @@ -708,6 +708,9 @@ const makeReport = (context, commentNode) => { end: {line: lineNumber}, start: {line: lineNumber}, }; + + // Todo: Remove ignore once `check-examples` can be restored for ESLint 8+ + // istanbul ignore if if (jsdocLoc.column) { const colNumber = commentNode.loc.start.column + jsdocLoc.column; diff --git a/src/jsdocUtils.js b/src/jsdocUtils.js index 30a651e44..814ac868c 100644 --- a/src/jsdocUtils.js +++ b/src/jsdocUtils.js @@ -718,6 +718,8 @@ const hasNonEmptyResolverCall = (node, resolverName) => { case 'ObjectProperty': /* eslint-disable no-fallthrough */ // istanbul ignore next -- In Babel? + case 'PropertyDefinition': + // istanbul ignore next -- In Babel? case 'ClassProperty': /* eslint-enable no-fallthrough */ case 'Property': @@ -908,9 +910,11 @@ const hasNonFunctionYield = (node, checkYieldReturnValue) => { }); // istanbul ignore next -- In Babel? - case 'ObjectProperty': + case 'PropertyDefinition': /* eslint-disable no-fallthrough */ // istanbul ignore next -- In Babel? + case 'ObjectProperty': + // istanbul ignore next -- In Babel? case 'ClassProperty': /* eslint-enable no-fallthrough */ case 'Property': diff --git a/src/rules/checkExamples.js b/src/rules/checkExamples.js index 84cffd26e..7cc177782 100644 --- a/src/rules/checkExamples.js +++ b/src/rules/checkExamples.js @@ -1,9 +1,9 @@ -// Todo: When peerDeps bump to ESLint 7, see about replacing `CLIEngine` -// with non-deprecated `ESLint` class: +// Todo: When replace `CLIEngine` with `ESLint` when feature set complete per https://github.com/eslint/eslint/issues/14745 // https://github.com/eslint/eslint/blob/master/docs/user-guide/migrating-to-7.0.0.md#-the-cliengine-class-has-been-deprecated import { - CLIEngine, + CLIEngine, ESLint, } from 'eslint'; +import semver from 'semver'; import iterateJsdoc from '../iterateJsdoc'; const zeroBasedLineIndexAdjust = -1; @@ -85,6 +85,17 @@ export default iterateJsdoc(({ context, globalState, }) => { + if (semver.gte(ESLint.version, '8.0.0')) { + report( + 'This rule cannot yet be supported for ESLint 8; you ' + + 'should either downgrade to ESLint 7 or disable this rule. The ' + + 'possibility for ESLint 8 support is being tracked at https://github.com/eslint/eslint/issues/14745', + {column: 1, line: 1}, + ); + + return; + } + if (!globalState.has('checkExamples-matchingFileName')) { globalState.set('checkExamples-matchingFileName', new Map()); } @@ -196,8 +207,7 @@ export default iterateJsdoc(({ matchingFileNameMap.set(fileNameMapKey, cliFile); } - const {results: [{messages}]} = - cliFile.executeOnText(src); + const {results: [{messages}]} = cliFile.executeOnText(src); if (!('line' in tag)) { tag.line = tag.source[0].number; diff --git a/src/rules/requireDescriptionCompleteSentence.js b/src/rules/requireDescriptionCompleteSentence.js index bf59c12c4..0676b59c1 100644 --- a/src/rules/requireDescriptionCompleteSentence.js +++ b/src/rules/requireDescriptionCompleteSentence.js @@ -26,6 +26,8 @@ const extractSentences = (text, abbreviationsRegex) => { .replace(abbreviationsRegex, ''); const sentenceEndGrouping = /([.?!])(?:\s+|$)/u; + + // eslint-disable-next-line unicorn/no-array-method-this-argument const puncts = RegExtras(sentenceEndGrouping).map(txt, (punct) => { return punct; }); diff --git a/src/rules/requireJsdoc.js b/src/rules/requireJsdoc.js index 20fe68c4f..8c123c74e 100644 --- a/src/rules/requireJsdoc.js +++ b/src/rules/requireJsdoc.js @@ -308,7 +308,7 @@ export default { if ( ['VariableDeclarator', 'AssignmentExpression', 'ExportDefaultDeclaration'].includes(node.parent.type) || - ['Property', 'ObjectProperty', 'ClassProperty'].includes(node.parent.type) && node === node.parent.value + ['Property', 'ObjectProperty', 'ClassProperty', 'PropertyDefinition'].includes(node.parent.type) && node === node.parent.value ) { checkJsDoc({isFunctionContext: true}, null, node); } @@ -351,7 +351,7 @@ export default { if ( ['VariableDeclarator', 'AssignmentExpression', 'ExportDefaultDeclaration'].includes(node.parent.type) || - ['Property', 'ObjectProperty', 'ClassProperty'].includes(node.parent.type) && node === node.parent.value + ['Property', 'ObjectProperty', 'ClassProperty', 'PropertyDefinition'].includes(node.parent.type) && node === node.parent.value ) { checkJsDoc({isFunctionContext: true}, null, node); } diff --git a/test/rules/assertions/matchDescription.js b/test/rules/assertions/matchDescription.js index c2c2973a1..725f17f7c 100644 --- a/test/rules/assertions/matchDescription.js +++ b/test/rules/assertions/matchDescription.js @@ -794,7 +794,7 @@ export default { options: [ { contexts: [ - 'ClassProperty', + 'PropertyDefinition', ], }, ], @@ -1269,7 +1269,7 @@ export default { options: [ { contexts: [ - 'ClassProperty', + 'PropertyDefinition', ], }, ], diff --git a/test/rules/assertions/requireJsdoc.js b/test/rules/assertions/requireJsdoc.js index 706288744..454a0da0d 100644 --- a/test/rules/assertions/requireJsdoc.js +++ b/test/rules/assertions/requireJsdoc.js @@ -2,10 +2,6 @@ * @see https://github.com/eslint/eslint/blob/master/tests/lib/rules/require-jsdoc.js */ -import { - CLIEngine, -} from 'eslint'; - export default { invalid: [ { @@ -2591,7 +2587,7 @@ function quux (foo) { ], options: [ { - contexts: ['ClassProperty'], + contexts: ['PropertyDefinition'], }, ], output: ` @@ -3016,15 +3012,7 @@ function quux (foo) { }, ], options: [{ - contexts: [ - // Only fixed to support `:has()` with TS later in ESLint 7, but - // for our testing of ESLint 6, we use `>` which is equivalent in - // this case; after having peerDeps. to ESLint 7+, we can remove - // this check and use of `CLIEngine` - CLIEngine.version.startsWith('6') ? - 'ClassProperty > Decorator[expression.callee.name="Input"]' : - 'ClassProperty:has(Decorator[expression.callee.name="Input"])', - ], + contexts: ['PropertyDefinition > Decorator[expression.callee.name="Input"]'], }], output: ` export class MyComponentComponent { @@ -3262,6 +3250,37 @@ function quux (foo) { } `, }, + { + code: ` + export class InovaAutoCompleteComponent { + public disabled = false; + } + `, + errors: [ + { + line: 3, + message: 'Missing JSDoc comment.', + }, + ], + options: [ + { + contexts: ['PropertyDefinition'], + publicOnly: true, + }, + ], + output: ` + export class InovaAutoCompleteComponent { + /** + * + */ + public disabled = false; + } + `, + parser: require.resolve('@typescript-eslint/parser'), + parserOptions: { + sourceType: 'module', + }, + }, ], valid: [{ code: ` diff --git a/test/rules/index.js b/test/rules/index.js index 8a1828c7d..fe851da0e 100644 --- a/test/rules/index.js +++ b/test/rules/index.js @@ -1,13 +1,20 @@ import { - RuleTester, + ESLint, RuleTester, } from 'eslint'; import _ from 'lodash'; +import semver from 'semver'; import config from '../../src'; import ruleNames from './ruleNames.json'; const ruleTester = new RuleTester(); (process.env.npm_config_rule ? process.env.npm_config_rule.split(',') : ruleNames).forEach(async (ruleName) => { + if (semver.gte(ESLint.version, '8.0.0') && ruleName === 'check-examples') { + // This rule cannot yet be supported for ESLint 8; + // The possibility for ESLint 8 support is being tracked at https://github.com/eslint/eslint/issues/14745 + return; + } + const rule = config.rules[ruleName]; const parserOptions = {