From 4799297ea582c81fd1e5623d32a7ddf7a7f3a126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Fri, 3 Mar 2023 15:26:15 +0100 Subject: [PATCH 01/48] feat: use @eslint-community dependencies (#16784) --- lib/rules/no-control-regex.js | 2 +- lib/rules/no-extra-boolean-cast.js | 2 +- lib/rules/no-extra-parens.js | 2 +- lib/rules/no-implied-eval.js | 2 +- lib/rules/no-import-assign.js | 2 +- lib/rules/no-invalid-regexp.js | 2 +- lib/rules/no-misleading-character-class.js | 4 ++-- lib/rules/no-obj-calls.js | 2 +- lib/rules/no-promise-executor-return.js | 2 +- lib/rules/no-regex-spaces.js | 2 +- lib/rules/no-setter-return.js | 2 +- lib/rules/no-useless-backreference.js | 4 ++-- lib/rules/prefer-exponentiation-operator.js | 2 +- lib/rules/prefer-named-capture-group.js | 4 ++-- lib/rules/prefer-object-spread.js | 2 +- lib/rules/prefer-regex-literals.js | 4 ++-- lib/rules/require-unicode-regexp.js | 2 +- lib/rules/wrap-iife.js | 2 +- lib/source-code/source-code.js | 2 +- lib/source-code/token-store/index.js | 2 +- package.json | 4 ++-- tests/lib/rules/no-invalid-regexp.js | 9 +++++++++ 22 files changed, 35 insertions(+), 26 deletions(-) diff --git a/lib/rules/no-control-regex.js b/lib/rules/no-control-regex.js index ba108437257..c42f86d1277 100644 --- a/lib/rules/no-control-regex.js +++ b/lib/rules/no-control-regex.js @@ -5,7 +5,7 @@ "use strict"; -const RegExpValidator = require("regexpp").RegExpValidator; +const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator; const collector = new (class { constructor() { this._source = ""; diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js index 45252fee0e9..5f9411b4637 100644 --- a/lib/rules/no-extra-boolean-cast.js +++ b/lib/rules/no-extra-boolean-cast.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const eslintUtils = require("eslint-utils"); +const eslintUtils = require("@eslint-community/eslint-utils"); const precedence = astUtils.getPrecedence; diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js index d3ea7d33415..efb01a337a2 100644 --- a/lib/rules/no-extra-parens.js +++ b/lib/rules/no-extra-parens.js @@ -8,7 +8,7 @@ // Rule Definition //------------------------------------------------------------------------------ -const { isParenthesized: isParenthesizedRaw } = require("eslint-utils"); +const { isParenthesized: isParenthesizedRaw } = require("@eslint-community/eslint-utils"); const astUtils = require("./utils/ast-utils.js"); /** @type {import('../shared/types').Rule} */ diff --git a/lib/rules/no-implied-eval.js b/lib/rules/no-implied-eval.js index 44f146171aa..c33b05d465d 100644 --- a/lib/rules/no-implied-eval.js +++ b/lib/rules/no-implied-eval.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const { getStaticValue } = require("eslint-utils"); +const { getStaticValue } = require("@eslint-community/eslint-utils"); //------------------------------------------------------------------------------ // Rule Definition diff --git a/lib/rules/no-import-assign.js b/lib/rules/no-import-assign.js index fc104fe6c46..6cf2e519868 100644 --- a/lib/rules/no-import-assign.js +++ b/lib/rules/no-import-assign.js @@ -9,7 +9,7 @@ // Helpers //------------------------------------------------------------------------------ -const { findVariable } = require("eslint-utils"); +const { findVariable } = require("@eslint-community/eslint-utils"); const astUtils = require("./utils/ast-utils"); const WellKnownMutationFunctions = { diff --git a/lib/rules/no-invalid-regexp.js b/lib/rules/no-invalid-regexp.js index 81b083536d8..b2e11a00821 100644 --- a/lib/rules/no-invalid-regexp.js +++ b/lib/rules/no-invalid-regexp.js @@ -8,7 +8,7 @@ // Requirements //------------------------------------------------------------------------------ -const RegExpValidator = require("regexpp").RegExpValidator; +const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator; const validator = new RegExpValidator(); const validFlags = /[dgimsuy]/gu; const undefined1 = void 0; diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index 5649f2e37fe..9aa7079e538 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -3,8 +3,8 @@ */ "use strict"; -const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils"); -const { RegExpValidator, RegExpParser, visitRegExpAST } = require("regexpp"); +const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils"); +const { RegExpValidator, RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode"); const astUtils = require("./utils/ast-utils.js"); diff --git a/lib/rules/no-obj-calls.js b/lib/rules/no-obj-calls.js index d24d28589f3..2e2cb5b2460 100644 --- a/lib/rules/no-obj-calls.js +++ b/lib/rules/no-obj-calls.js @@ -9,7 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -const { CALL, CONSTRUCT, ReferenceTracker } = require("eslint-utils"); +const { CALL, CONSTRUCT, ReferenceTracker } = require("@eslint-community/eslint-utils"); const getPropertyName = require("./utils/ast-utils").getStaticPropertyName; //------------------------------------------------------------------------------ diff --git a/lib/rules/no-promise-executor-return.js b/lib/rules/no-promise-executor-return.js index caa195ffa07..e81ed1d0451 100644 --- a/lib/rules/no-promise-executor-return.js +++ b/lib/rules/no-promise-executor-return.js @@ -9,7 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -const { findVariable } = require("eslint-utils"); +const { findVariable } = require("@eslint-community/eslint-utils"); //------------------------------------------------------------------------------ // Helpers diff --git a/lib/rules/no-regex-spaces.js b/lib/rules/no-regex-spaces.js index 6d74aabe263..e6564bc4583 100644 --- a/lib/rules/no-regex-spaces.js +++ b/lib/rules/no-regex-spaces.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const regexpp = require("regexpp"); +const regexpp = require("@eslint-community/regexpp"); //------------------------------------------------------------------------------ // Helpers diff --git a/lib/rules/no-setter-return.js b/lib/rules/no-setter-return.js index 25e8f1428b2..a43637e7b55 100644 --- a/lib/rules/no-setter-return.js +++ b/lib/rules/no-setter-return.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const { findVariable } = require("eslint-utils"); +const { findVariable } = require("@eslint-community/eslint-utils"); //------------------------------------------------------------------------------ // Helpers diff --git a/lib/rules/no-useless-backreference.js b/lib/rules/no-useless-backreference.js index f23535bc359..5103e098b14 100644 --- a/lib/rules/no-useless-backreference.js +++ b/lib/rules/no-useless-backreference.js @@ -9,8 +9,8 @@ // Requirements //------------------------------------------------------------------------------ -const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils"); -const { RegExpParser, visitRegExpAST } = require("regexpp"); +const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils"); +const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); //------------------------------------------------------------------------------ // Helpers diff --git a/lib/rules/prefer-exponentiation-operator.js b/lib/rules/prefer-exponentiation-operator.js index fec5319723e..06459e25884 100644 --- a/lib/rules/prefer-exponentiation-operator.js +++ b/lib/rules/prefer-exponentiation-operator.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const { CALL, ReferenceTracker } = require("eslint-utils"); +const { CALL, ReferenceTracker } = require("@eslint-community/eslint-utils"); //------------------------------------------------------------------------------ // Helpers diff --git a/lib/rules/prefer-named-capture-group.js b/lib/rules/prefer-named-capture-group.js index 66259fc7bed..b7055f3a4c5 100644 --- a/lib/rules/prefer-named-capture-group.js +++ b/lib/rules/prefer-named-capture-group.js @@ -14,8 +14,8 @@ const { CONSTRUCT, ReferenceTracker, getStringIfConstant -} = require("eslint-utils"); -const regexpp = require("regexpp"); +} = require("@eslint-community/eslint-utils"); +const regexpp = require("@eslint-community/regexpp"); //------------------------------------------------------------------------------ // Helpers diff --git a/lib/rules/prefer-object-spread.js b/lib/rules/prefer-object-spread.js index 7d8f7857b3c..b70ef64bded 100644 --- a/lib/rules/prefer-object-spread.js +++ b/lib/rules/prefer-object-spread.js @@ -6,7 +6,7 @@ "use strict"; -const { CALL, ReferenceTracker } = require("eslint-utils"); +const { CALL, ReferenceTracker } = require("@eslint-community/eslint-utils"); const { isCommaToken, isOpeningParenToken, diff --git a/lib/rules/prefer-regex-literals.js b/lib/rules/prefer-regex-literals.js index fdf18874e77..c9948658cb1 100644 --- a/lib/rules/prefer-regex-literals.js +++ b/lib/rules/prefer-regex-literals.js @@ -10,8 +10,8 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("eslint-utils"); -const { RegExpValidator, visitRegExpAST, RegExpParser } = require("regexpp"); +const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("@eslint-community/eslint-utils"); +const { RegExpValidator, visitRegExpAST, RegExpParser } = require("@eslint-community/regexpp"); const { canTokensBeAdjacent } = require("./utils/ast-utils"); //------------------------------------------------------------------------------ diff --git a/lib/rules/require-unicode-regexp.js b/lib/rules/require-unicode-regexp.js index 4236af6db47..efac2519fe1 100644 --- a/lib/rules/require-unicode-regexp.js +++ b/lib/rules/require-unicode-regexp.js @@ -14,7 +14,7 @@ const { CONSTRUCT, ReferenceTracker, getStringIfConstant -} = require("eslint-utils"); +} = require("@eslint-community/eslint-utils"); //------------------------------------------------------------------------------ // Rule Definition diff --git a/lib/rules/wrap-iife.js b/lib/rules/wrap-iife.js index 4c2c9275d87..66ea3f8fa46 100644 --- a/lib/rules/wrap-iife.js +++ b/lib/rules/wrap-iife.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const eslintUtils = require("eslint-utils"); +const eslintUtils = require("@eslint-community/eslint-utils"); //---------------------------------------------------------------------- // Helpers diff --git a/lib/source-code/source-code.js b/lib/source-code/source-code.js index 49b2820a846..9e2b68e2d31 100644 --- a/lib/source-code/source-code.js +++ b/lib/source-code/source-code.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const - { isCommentToken } = require("eslint-utils"), + { isCommentToken } = require("@eslint-community/eslint-utils"), TokenStore = require("./token-store"), astUtils = require("../shared/ast-utils"), Traverser = require("../shared/traverser"); diff --git a/lib/source-code/token-store/index.js b/lib/source-code/token-store/index.js index 25db8a4f4db..46a96b2f4b1 100644 --- a/lib/source-code/token-store/index.js +++ b/lib/source-code/token-store/index.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const assert = require("assert"); -const { isCommentToken } = require("eslint-utils"); +const { isCommentToken } = require("@eslint-community/eslint-utils"); const cursors = require("./cursors"); const ForwardTokenCursor = require("./forward-token-cursor"); const PaddedTokenCursor = require("./padded-token-cursor"); diff --git a/package.json b/package.json index 1f86984079d..69026b9d166 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,8 @@ "homepage": "https://eslint.org", "bugs": "https://github.com/eslint/eslint/issues/", "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.0", "@eslint/js": "8.35.0", "@humanwhocodes/config-array": "^0.11.8", @@ -72,7 +74,6 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", "eslint-visitor-keys": "^3.3.0", "espree": "^9.4.0", "esquery": "^1.4.2", @@ -96,7 +97,6 @@ "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" diff --git a/tests/lib/rules/no-invalid-regexp.js b/tests/lib/rules/no-invalid-regexp.js index 047207b5534..ceaa8f13a44 100644 --- a/tests/lib/rules/no-invalid-regexp.js +++ b/tests/lib/rules/no-invalid-regexp.js @@ -71,6 +71,15 @@ ruleTester.run("no-invalid-regexp", rule, { // ES2022 "new RegExp('a+(?z)?', 'd')", + "new RegExp('\\\\p{Script=Cpmn}', 'u')", + "new RegExp('\\\\p{Script=Cypro_Minoan}', 'u')", + "new RegExp('\\\\p{Script=Old_Uyghur}', 'u')", + "new RegExp('\\\\p{Script=Ougr}', 'u')", + "new RegExp('\\\\p{Script=Tangsa}', 'u')", + "new RegExp('\\\\p{Script=Tnsa}', 'u')", + "new RegExp('\\\\p{Script=Toto}', 'u')", + "new RegExp('\\\\p{Script=Vith}', 'u')", + "new RegExp('\\\\p{Script=Vithkuqi}', 'u')", // allowConstructorFlags { From 19d3531d9b54e1004318d28f9a6e18305c5bcc18 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Sat, 4 Mar 2023 08:06:04 +0000 Subject: [PATCH 02/48] docs: Update README --- README.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7a5059f04e4..6f6db1b12a2 100644 --- a/README.md +++ b/README.md @@ -212,11 +212,6 @@ The people who manage releases, review feature requests, and meet regularly to e Nicholas C. Zakas - -
-Brandon Mills -
-
Milos Djermanovic @@ -249,9 +244,9 @@ The people who review and fix bugs and help triage issues. Bryan Mishkin
- -
-Sara Soueidan +
+
+Brandon Mills
From 3398431574b903757bc78b08c8ed36b7b9fce8eb Mon Sep 17 00:00:00 2001 From: Ben Perlmutter <57849986+bpmutter@users.noreply.github.com> Date: Mon, 6 Mar 2023 07:05:13 -0500 Subject: [PATCH 03/48] docs: Custom Parsers cleanup/expansion (#16887) * docs: Custom Parsers cleanup/expansion * clarify TODOs * copy edits based on feedback * update comment copy * Apply suggestions from code review Co-authored-by: Nicholas C. Zakas Co-authored-by: Nitin Kumar * Apply suggestions from code review * Apply suggestions from code review * Update docs/src/extend/custom-parsers.md Co-authored-by: Francesco Trotta --------- Co-authored-by: Nicholas C. Zakas Co-authored-by: Nitin Kumar Co-authored-by: Francesco Trotta --- docs/src/extend/custom-parsers.md | 138 ++++++++++++++++++++++-------- 1 file changed, 104 insertions(+), 34 deletions(-) diff --git a/docs/src/extend/custom-parsers.md b/docs/src/extend/custom-parsers.md index a341da5088a..388f54b726b 100644 --- a/docs/src/extend/custom-parsers.md +++ b/docs/src/extend/custom-parsers.md @@ -8,63 +8,69 @@ eleventyNavigation: --- -If you want to use your own parser and provide additional capabilities for your rules, you can specify your own custom parser. If a `parseForESLint` method is exposed on the parser, this method will be used to parse the code. Otherwise, the `parse` method will be used. Both methods should take in the source code as the first argument, and an optional configuration object as the second argument (provided as `parserOptions` in a config file). The `parse` method should simply return the AST. The `parseForESLint` method should return an object that contains the required property `ast` and optional properties `services`, `scopeManager`, and `visitorKeys`. +ESLint custom parsers let you extend ESLint to support linting new non-standard JavaScript language features or custom syntax in your code. A parser is responsible for taking your code and transforming it into an abstract syntax tree (AST) that ESLint can then analyze and lint. -* `ast` should contain the AST. -* `services` can contain any parser-dependent services (such as type checkers for nodes). The value of the `services` property is available to rules as `context.parserServices`. Default is an empty object. -* `scopeManager` can be a [ScopeManager](./scope-manager-interface) object. Custom parsers can use customized scope analysis for experimental/enhancement syntaxes. Default is the `ScopeManager` object which is created by [eslint-scope](https://github.com/eslint/eslint-scope). - * Support for `scopeManager` was added in ESLint v4.14.0. ESLint versions which support `scopeManager` will provide an `eslintScopeManager: true` property in `parserOptions`, which can be used for feature detection. -* `visitorKeys` can be an object to customize AST traversal. The keys of the object are the type of AST nodes. Each value is an array of the property names which should be traversed. Default is [KEYS of `eslint-visitor-keys`](https://github.com/eslint/eslint-visitor-keys#evkkeys). - * Support for `visitorKeys` was added in ESLint v4.14.0. ESLint versions which support `visitorKeys` will provide an `eslintVisitorKeys: true` property in `parserOptions`, which can be used for feature detection. +## Creating a Custom Parser -You can find an ESLint parser project [here](https://github.com/typescript-eslint/typescript-eslint). +A custom parser is a JavaScript object with either a `parse` or `parseForESLint` method. The `parse` method only returns the AST, whereas `parseForESLint` also returns additional values that let the parser customize the behavior of ESLint even more. -```json -{ - "parser": "./path/to/awesome-custom-parser.js" -} -``` +Both methods should take in the source code as the first argument, and an optional configuration object as the second argument, which is provided as [`parserOptions`](../use/configure/language-options#specifying-parser-options) in a configuration file. ```javascript -var espree = require("espree"); -// awesome-custom-parser.js -exports.parseForESLint = function(code, options) { - return { - ast: espree.parse(code, options), - services: { - foo: function() { - console.log("foo"); - } - }, - scopeManager: null, - visitorKeys: null - }; +// customParser.js + +const espree = require("espree"); + +// Logs the duration it takes to parse each file. +function parse(code, options) { + const label = `Parsing file "${options.filePath}"`; + console.time(label); + const ast = espree.parse(code, options); + console.timeEnd(label); + return ast; // Only the AST is returned. }; +module.exports = { parse }; ``` -## The AST specification +## `parse` Return Object + +The `parse` method should simply return the [AST](#ast-specification) object. + +## `parseForESLint` Return Object + +The `parseForESLint` method should return an object that contains the required property `ast` and optional properties `services`, `scopeManager`, and `visitorKeys`. + +* `ast` should contain the [AST](#ast-specification) object. +* `services` can contain any parser-dependent services (such as type checkers for nodes). The value of the `services` property is available to rules as `context.parserServices`. Default is an empty object. +* `scopeManager` can be a [ScopeManager](./scope-manager-interface) object. Custom parsers can use customized scope analysis for experimental/enhancement syntaxes. The default is the `ScopeManager` object which is created by [eslint-scope](https://github.com/eslint/eslint-scope). + * Support for `scopeManager` was added in ESLint v4.14.0. ESLint versions that support `scopeManager` will provide an `eslintScopeManager: true` property in `parserOptions`, which can be used for feature detection. +* `visitorKeys` can be an object to customize AST traversal. The keys of the object are the type of AST nodes. Each value is an array of the property names which should be traversed. The default is [KEYS of `eslint-visitor-keys`](https://github.com/eslint/eslint-visitor-keys#evkkeys). + * Support for `visitorKeys` was added in ESLint v4.14.0. ESLint versions that support `visitorKeys` will provide an `eslintVisitorKeys: true` property in `parserOptions`, which can be used for feature detection. + +## AST Specification The AST that custom parsers should create is based on [ESTree](https://github.com/estree/estree). The AST requires some additional properties about detail information of the source code. -### All nodes: +### All Nodes All nodes must have `range` property. * `range` (`number[]`) is an array of two numbers. Both numbers are a 0-based index which is the position in the array of source code characters. The first is the start position of the node, the second is the end position of the node. `code.slice(node.range[0], node.range[1])` must be the text of the node. This range does not include spaces/parentheses which are around the node. -* `loc` (`SourceLocation`) must not be `null`. [The `loc` property is defined as nullable by ESTree](https://github.com/estree/estree/blob/25834f7247d44d3156030f8e8a2d07644d771fdb/es5.md#node-objects), but ESLint requires this property. On the other hand, `SourceLocation#source` property can be `undefined`. ESLint does not use the `SourceLocation#source` property. +* `loc` (`SourceLocation`) must not be `null`. [The `loc` property is defined as nullable by ESTree](https://github.com/estree/estree/blob/25834f7247d44d3156030f8e8a2d07644d771fdb/es5.md#node-objects), but ESLint requires this property. The `SourceLocation#source` property can be `undefined`. ESLint does not use the `SourceLocation#source` property. -The `parent` property of all nodes must be rewritable. ESLint sets each node's `parent` property to its parent node while traversing, before any rules have access to the AST. +The `parent` property of all nodes must be rewritable. Before any rules have access to the AST, ESLint sets each node's `parent` property to its parent node while traversing. -### The `Program` node: +### The `Program` Node -The `Program` node must have `tokens` and `comments` properties. Both properties are an array of the below Token interface. +The `Program` node must have `tokens` and `comments` properties. Both properties are an array of the below `Token` interface. ```ts interface Token { type: string; loc: SourceLocation; - range: [number, number]; // See "All nodes:" section for details of `range` property. + // See the "All Nodes" section for details of the `range` property. + range: [number, number]; value: string; } ``` @@ -74,8 +80,72 @@ interface Token { The range indexes of all tokens and comments must not overlap with the range of other tokens and comments. -### The `Literal` node: +### The `Literal` Node The `Literal` node must have `raw` property. * `raw` (`string`) is the source code of this literal. This is the same as `code.slice(node.range[0], node.range[1])`. + +## Packaging a Custom Parser + +To publish your custom parser to npm, perform the following: + +1. Create a custom parser following the [Creating a Custom Parser](#creating-a-custom-parser) section above. +1. [Create an npm package](https://docs.npmjs.com/creating-node-js-modules) for the custom parser. +1. In your `package.json` file, set the [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) field as the file that exports your custom parser. +1. [Publish the npm package.](https://docs.npmjs.com/creating-and-publishing-unscoped-public-packages) + +For more information on publishing an npm package, refer to the [npm documentation](https://docs.npmjs.com/). + +Once you've published the npm package, you can use it by adding the package to your project. For example: + +```shell +npm install eslint-parser-myparser --save-dev +``` + +Then add the custom parser to your ESLint configuration file with the `parser` property. For example: + +```js +// .eslintrc.js + +module.exports = { + parser: 'eslint-parser-myparser', + // ... rest of configuration +}; +``` + +To learn more about using ESLint parsers in your project, refer to [Configure a Parser](../use/configure/parser). + +## Example + +For a complex example of a custom parser, refer to the [`@typescript-eslint/parser`](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/parser) source code. + +A simple custom parser that provides a `context.parserServices.foo()` method to rules. + +```javascript +// awesome-custom-parser.js +var espree = require("espree"); +function parseForESLint(code, options) { + return { + ast: espree.parse(code, options), + services: { + foo: function() { + console.log("foo"); + } + }, + scopeManager: null, + visitorKeys: null + }; +}; + +module.exports = { parseForESLint }; +``` + +Include the custom parser in an ESLint configuration file: + +```js +// .eslintrc.json +{ + "parser": "./path/to/awesome-custom-parser.js" +} +``` From 75acdd21c5ce7024252e9d41ed77d2f30587caac Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 6 Mar 2023 23:15:00 +0100 Subject: [PATCH 04/48] chore: lint more js files in docs (#16964) * chore: lint more js files in docs * removed unused disable directive --- .eslintignore | 5 +++-- .eslintrc.js | 5 +++-- docs/postcss.config.js | 8 +++++--- docs/tools/validate-links.js | 20 ++++++++++++++------ eslint.config.js | 8 +++++--- tools/fetch-docs-links.js | 2 +- 6 files changed, 31 insertions(+), 17 deletions(-) diff --git a/.eslintignore b/.eslintignore index 905f2a39ddf..ffc34e9a954 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,7 +1,8 @@ /build/** /coverage/** -/docs/** -!/docs/.eleventy.js +/docs/* +!/docs/*.js +!/docs/tools/ /jsdoc/** /templates/** /tests/bench/** diff --git a/.eslintrc.js b/.eslintrc.js index f504850ad57..cd47323f3e9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -83,9 +83,10 @@ module.exports = { }, overrides: [ { - files: ["tools/*.js"], + files: ["tools/*.js", "docs/tools/*.js"], rules: { - "no-console": "off" + "no-console": "off", + "n/no-process-exit": "off" } }, { diff --git a/docs/postcss.config.js b/docs/postcss.config.js index 319fa67a5bc..128e741f027 100644 --- a/docs/postcss.config.js +++ b/docs/postcss.config.js @@ -1,7 +1,9 @@ +"use strict"; + module.exports = { plugins: [ - require('autoprefixer'), - require('cssnano') + require("autoprefixer"), + require("cssnano") ], map: false - } +}; diff --git a/docs/tools/validate-links.js b/docs/tools/validate-links.js index 5c1ca43578d..4d88ee7526a 100644 --- a/docs/tools/validate-links.js +++ b/docs/tools/validate-links.js @@ -1,3 +1,5 @@ +"use strict"; + const path = require("path"); const TapRender = require("@munter/tap-render"); const spot = require("tap-spot"); @@ -16,13 +18,19 @@ const skipPatterns = [ "/team", "/donate", "/docs/latest", - `src="null"`, + 'src="null"' ]; -const skipFilter = (report) => - Object.values(report).some((value) => - skipPatterns.some((pattern) => String(value).includes(pattern)) - ); +/** + * Filter function to mark tests as skipped. + * Tests for which this function returns `true' are not considered failed. + * @param {Object} report hyperlink's test report for a link. + * @returns {boolean} `true` if the report contains any of `skipPatterns`. + */ +function skipFilter(report) { + return Object.values(report).some(value => + skipPatterns.some(pattern => String(value).includes(pattern))); +} (async () => { try { @@ -35,7 +43,7 @@ const skipFilter = (report) => internalOnly: true, pretty: true, concurrency: 25, - skipFilter, + skipFilter }, tapRenderInstance ); diff --git a/eslint.config.js b/eslint.config.js index 2a9907aa075..dfed655ec36 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -85,7 +85,8 @@ module.exports = [ "build/**", "coverage/**", "docs/*", - "!docs/.eleventy.js", + "!docs/*.js", + "!docs/tools/", "jsdoc/**", "templates/**", "tests/bench/**", @@ -119,9 +120,10 @@ module.exports = [ } }, { - files: ["tools/*.js"], + files: ["tools/*.js", "docs/tools/*.js"], rules: { - "no-console": "off" + "no-console": "off", + "n/no-process-exit": "off" } }, { diff --git a/tools/fetch-docs-links.js b/tools/fetch-docs-links.js index d29dff1b3a0..9cdd05aab1a 100644 --- a/tools/fetch-docs-links.js +++ b/tools/fetch-docs-links.js @@ -99,7 +99,7 @@ async function fetchLinkMeta(url) { console.error("Could not fetch data for", url); console.error(ex.message); console.error(ex.stack); - process.exit(1); // eslint-disable-line n/no-process-exit -- used in tools + process.exit(1); } } } From caf08ce0cc74917f7c0eec92d25fd784dc33ac4d Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 7 Mar 2023 11:29:34 +0100 Subject: [PATCH 05/48] docs: fix estree link in custom formatters docs (#16967) --- docs/src/extend/custom-formatters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/extend/custom-formatters.md b/docs/src/extend/custom-formatters.md index dc8024433bf..b95db0f8bd6 100644 --- a/docs/src/extend/custom-formatters.md +++ b/docs/src/extend/custom-formatters.md @@ -110,7 +110,7 @@ Each `message` object contains information about the ESLint rule that was trigge * **message**: the human readable description of the error. * **line**: the line where the issue is located. * **column**: the column where the issue is located. -* **nodeType**: the type of the node in the [AST](https://github.com/estree/estree/blob/master/spec.md#node-objects) +* **nodeType**: the type of the node in the [AST](https://github.com/estree/estree/blob/master/es5.md#node-objects) ### The `context` Argument From f5f5e11bd5fd3daab9ccae41e270739c836c305e Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 8 Mar 2023 01:05:35 -0700 Subject: [PATCH 06/48] feat: Serialize parsers/processors in flat config (#16944) * feat: Serialize parsers/processors in flat config This implements a check for a 'meta' key on parsers and processors that contains information to help flat config serialize these objects for use with caching and also --print-config on the command line. Fixes #16875 * Add support for non-meta properties * Fix edge case --- lib/config/flat-config-array.js | 65 +++++- tests/lib/config/flat-config-array.js | 309 +++++++++++++++++++++++++- 2 files changed, 365 insertions(+), 9 deletions(-) diff --git a/lib/config/flat-config-array.js b/lib/config/flat-config-array.js index 46d436db5cf..78b4ef44bac 100644 --- a/lib/config/flat-config-array.js +++ b/lib/config/flat-config-array.js @@ -36,6 +36,45 @@ function splitPluginIdentifier(identifier) { }; } +/** + * Returns the name of an object in the config by reading its `meta` key. + * @param {Object} object The object to check. + * @returns {string?} The name of the object if found or `null` if there + * is no name. + */ +function getObjectId(object) { + + // first check old-style name + let name = object.name; + + if (!name) { + + if (!object.meta) { + return null; + } + + name = object.meta.name; + + if (!name) { + return null; + } + } + + // now check for old-style version + let version = object.version; + + if (!version) { + version = object.meta && object.meta.version; + } + + // if there's a version then append that + if (version) { + return `${name}@${version}`; + } + + return name; +} + const originalBaseConfig = Symbol("originalBaseConfig"); //----------------------------------------------------------------------------- @@ -151,16 +190,25 @@ class FlatConfigArray extends ConfigArray { // Check parser value if (languageOptions && languageOptions.parser) { - if (typeof languageOptions.parser === "string") { - const { pluginName, objectName: localParserName } = splitPluginIdentifier(languageOptions.parser); + const { parser } = languageOptions; + + if (typeof parser === "string") { + const { pluginName, objectName: localParserName } = splitPluginIdentifier(parser); - parserName = languageOptions.parser; + parserName = parser; if (!plugins || !plugins[pluginName] || !plugins[pluginName].parsers || !plugins[pluginName].parsers[localParserName]) { throw new TypeError(`Key "parser": Could not find "${localParserName}" in plugin "${pluginName}".`); } languageOptions.parser = plugins[pluginName].parsers[localParserName]; + } else if (typeof parser === "object") { + parserName = getObjectId(parser); + + if (!parserName) { + invalidParser = true; + } + } else { invalidParser = true; } @@ -178,6 +226,13 @@ class FlatConfigArray extends ConfigArray { } config.processor = plugins[pluginName].processors[localProcessorName]; + } else if (typeof processor === "object") { + processorName = getObjectId(processor); + + if (!processorName) { + invalidProcessor = true; + } + } else { invalidProcessor = true; } @@ -191,11 +246,11 @@ class FlatConfigArray extends ConfigArray { value: function() { if (invalidParser) { - throw new Error("Caching is not supported when parser is an object."); + throw new Error("Could not serialize parser object (missing 'meta' object)."); } if (invalidProcessor) { - throw new Error("Caching is not supported when processor is an object."); + throw new Error("Could not serialize processor object (missing 'meta' object)."); } return { diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index fc3e455bfe6..c06e4da7798 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -219,7 +219,7 @@ describe("FlatConfigArray", () => { assert.strictEqual(stringify(actual), stringify(expected)); }); - it("should throw an error when config with parser object is normalized", () => { + it("should throw an error when config with unnamed parser object is normalized", () => { const configs = new FlatConfigArray([{ languageOptions: { @@ -235,11 +235,176 @@ describe("FlatConfigArray", () => { assert.throws(() => { config.toJSON(); - }, /Caching is not supported/u); + }, /Could not serialize parser/u); }); - it("should throw an error when config with processor object is normalized", () => { + it("should throw an error when config with unnamed parser object with empty meta object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: {}, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Could not serialize parser/u); + + }); + + it("should throw an error when config with unnamed parser object with only meta version is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + version: "0.1.1" + }, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Could not serialize parser/u); + + }); + + it("should not throw an error when config with named parser object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + name: "custom-parser" + }, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should not throw an error when config with named and versioned parser object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + name: "custom-parser", + version: "0.1.0" + }, + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should not throw an error when config with meta-named and versioned parser object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + meta: { + name: "custom-parser" + }, + version: "0.1.0", + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should not throw an error when config with named and versioned parser object outside of meta object is normalized", () => { + + const configs = new FlatConfigArray([{ + languageOptions: { + parser: { + name: "custom-parser", + version: "0.1.0", + parse() { /* empty */ } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: void 0 + }); + + }); + + it("should throw an error when config with unnamed processor object is normalized", () => { const configs = new FlatConfigArray([{ processor: { @@ -254,10 +419,146 @@ describe("FlatConfigArray", () => { assert.throws(() => { config.toJSON(); - }, /Caching is not supported/u); + }, /Could not serialize processor/u); }); + it("should throw an error when config with processor object with empty meta object is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + meta: {}, + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Could not serialize processor/u); + + }); + + + it("should not throw an error when config with named processor object is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + meta: { + name: "custom-processor" + }, + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "@/espree", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor" + }); + + }); + + it("should not throw an error when config with named processor object without meta is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + name: "custom-processor", + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "@/espree", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor" + }); + + }); + + it("should not throw an error when config with named and versioned processor object is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + meta: { + name: "custom-processor", + version: "1.2.3" + }, + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "@/espree", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor@1.2.3" + }); + + }); + + it("should not throw an error when config with named and versioned processor object without meta is normalized", () => { + + const configs = new FlatConfigArray([{ + processor: { + name: "custom-processor", + version: "1.2.3", + preprocess() { /* empty */ }, + postprocess() { /* empty */ } + } + }]); + + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + languageOptions: { + ecmaVersion: "latest", + parser: "@/espree", + parserOptions: {}, + sourceType: "module" + }, + plugins: ["@"], + processor: "custom-processor@1.2.3" + }); + + }); }); From c89a485c49450532ee3db74f2638429f1f37d0dd Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Wed, 8 Mar 2023 08:51:14 +0000 Subject: [PATCH 07/48] feat: Add `checkJSDoc` option to multiline-comment-style (#16807) * feat: Add support for checkJSDoc param for "multiline-comment-style" * Update lib/rules/multiline-comment-style.js Co-authored-by: Milos Djermanovic * Apply suggestions from code review Co-authored-by: Milos Djermanovic * Update docs/src/rules/multiline-comment-style.md Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- docs/src/rules/multiline-comment-style.md | 39 +++++++++++++++++-- lib/rules/multiline-comment-style.js | 45 ++++++++++++++++++++-- tests/lib/rules/multiline-comment-style.js | 14 +++++++ 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/docs/src/rules/multiline-comment-style.md b/docs/src/rules/multiline-comment-style.md index 2113389d94c..28e6648ad03 100644 --- a/docs/src/rules/multiline-comment-style.md +++ b/docs/src/rules/multiline-comment-style.md @@ -16,10 +16,10 @@ This rule aims to enforce a particular style for multiline comments. This rule has a string option, which can have one of the following values: * `"starred-block"` (default): Disallows consecutive line comments in favor of block comments. Additionally, requires block comments to have an aligned `*` character before each line. -* `"bare-block"`: Disallows consecutive line comments in favor of block comments, and disallows block comments from having a `"*"` character before each line. -* `"separate-lines"`: Disallows block comments in favor of consecutive line comments +* `"bare-block"`: Disallows consecutive line comments in favor of block comments, and disallows block comments from having a `"*"` character before each line. This option ignores JSDoc comments. +* `"separate-lines"`: Disallows block comments in favor of consecutive line comments. By default, this option ignores JSDoc comments. To also apply this rule to JSDoc comments, set the `checkJSDoc` option to `true`. -The rule always ignores directive comments such as `/* eslint-disable */`. Additionally, unless the mode is `"starred-block"`, the rule ignores JSDoc comments. +The rule always ignores directive comments such as `/* eslint-disable */`. Examples of **incorrect** code for this rule with the default `"starred-block"` option: @@ -146,6 +146,39 @@ foo(); ::: +Examples of **incorrect** code for this rule with the `"separate-lines"` option and `checkJSDoc` set to `true`: + +::: incorrect + +```js + +/* eslint multiline-comment-style: ["error", "separate-lines", { "checkJSDoc": true }] */ + +/** + * I am a JSDoc comment + * and I'm not allowed + */ +foo(); + +``` + +::: + +Examples of **correct** code for this rule with the `"separate-lines"` option and `checkJSDoc` set to `true`: + +::: correct + +```js +/* eslint multiline-comment-style: ["error", "separate-lines", { "checkJSDoc": true }] */ + +// I am a JSDoc comment +// and I'm not allowed +foo(); + +``` + +::: + ## When Not To Use It If you don't want to enforce a particular style for multiline comments, you can disable the rule. diff --git a/lib/rules/multiline-comment-style.js b/lib/rules/multiline-comment-style.js index 68cd666532d..9cb7f3473e5 100644 --- a/lib/rules/multiline-comment-style.js +++ b/lib/rules/multiline-comment-style.js @@ -22,7 +22,37 @@ module.exports = { }, fixable: "whitespace", - schema: [{ enum: ["starred-block", "separate-lines", "bare-block"] }], + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["starred-block", "bare-block"] + } + ], + additionalItems: false + }, + { + type: "array", + items: [ + { + enum: ["separate-lines"] + }, + { + type: "object", + properties: { + checkJSDoc: { + type: "boolean" + } + }, + additionalProperties: false + } + ], + additionalItems: false + } + ] + }, messages: { expectedBlock: "Expected a block comment instead of consecutive line comments.", expectedBareBlock: "Expected a block comment without padding stars.", @@ -37,6 +67,8 @@ module.exports = { create(context) { const sourceCode = context.getSourceCode(); const option = context.options[0] || "starred-block"; + const params = context.options[1] || {}; + const checkJSDoc = !!params.checkJSDoc; //---------------------------------------------------------------------- // Helpers @@ -333,11 +365,18 @@ module.exports = { "separate-lines"(commentGroup) { const [firstComment] = commentGroup; - if (firstComment.type !== "Block" || isJSDocComment(commentGroup)) { + const isJSDoc = isJSDocComment(commentGroup); + + if (firstComment.type !== "Block" || (!checkJSDoc && isJSDoc)) { return; } - const commentLines = getCommentLines(commentGroup); + let commentLines = getCommentLines(commentGroup); + + if (isJSDoc) { + commentLines = commentLines.slice(1, commentLines.length - 1); + } + const tokenAfter = sourceCode.getTokenAfter(firstComment, { includeComments: true }); if (tokenAfter && firstComment.loc.end.line === tokenAfter.loc.start.line) { diff --git a/tests/lib/rules/multiline-comment-style.js b/tests/lib/rules/multiline-comment-style.js index 1f2302f9a22..a127d7ec4cb 100644 --- a/tests/lib/rules/multiline-comment-style.js +++ b/tests/lib/rules/multiline-comment-style.js @@ -628,6 +628,20 @@ ruleTester.run("multiline-comment-style", rule, { options: ["separate-lines"], errors: [{ messageId: "expectedLines", line: 2 }] }, + { + code: ` + /** + * JSDoc + * Comment + */ + `, + output: ` + // JSDoc + // Comment + `, + options: ["separate-lines", { checkJSDoc: true }], + errors: [{ messageId: "expectedLines", line: 2 }] + }, { code: ` /* foo From b98fdd413a3b07b262bfce6f704c1c1bb8582770 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Thu, 9 Mar 2023 08:07:07 +0000 Subject: [PATCH 08/48] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f6db1b12a2..040654c1424 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

Chrome Frameworks Fund Automattic

Gold Sponsors

RIDI Salesforce Airbnb

Silver Sponsors

-

Sentry Liftoff

Bronze Sponsors

+

Sentry Liftoff American Express

Bronze Sponsors

PayDay Say ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

From 698c5aad50e628ff00281dbc786e42de79834035 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 10 Mar 2023 22:06:05 +0100 Subject: [PATCH 09/48] chore: upgrade espree@9.5.0 (#16976) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 69026b9d166..d3dd63e4b47 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.1.1", "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", + "espree": "^9.5.0", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", From 00afb84e5039874c8745a45c953fceaf0c71c454 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 10 Mar 2023 22:33:29 +0100 Subject: [PATCH 10/48] chore: upgrade @eslint/eslintrc@2.0.1 (#16977) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d3dd63e4b47..b0f789565d0 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.0", + "@eslint/eslintrc": "^2.0.1", "@eslint/js": "8.35.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", From 43c2345c27024aeab6127e6bbfd55c8b70bd317e Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 10 Mar 2023 16:36:24 -0500 Subject: [PATCH 11/48] chore: package.json update for @eslint/js release --- packages/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/package.json b/packages/js/package.json index e539362899c..7dfa6130e8d 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "8.35.0", + "version": "8.36.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From 602b11121910a97ab2bc4a95a46dd0ccd0a89309 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 10 Mar 2023 23:01:01 +0100 Subject: [PATCH 12/48] chore: upgrade @eslint/js@8.36.0 (#16978) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b0f789565d0..b9acb455f8f 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.1", - "@eslint/js": "8.35.0", + "@eslint/js": "8.36.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", From a0c856a82266107c8c93a695700b1f69a238316e Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 10 Mar 2023 17:15:49 -0500 Subject: [PATCH 13/48] Build: changelog update for 8.36.0 --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6ff53c69ee..a68f37a80b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +v8.36.0 - March 10, 2023 + +* [`602b111`](https://github.com/eslint/eslint/commit/602b11121910a97ab2bc4a95a46dd0ccd0a89309) chore: upgrade @eslint/js@8.36.0 (#16978) (Milos Djermanovic) +* [`43c2345`](https://github.com/eslint/eslint/commit/43c2345c27024aeab6127e6bbfd55c8b70bd317e) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`00afb84`](https://github.com/eslint/eslint/commit/00afb84e5039874c8745a45c953fceaf0c71c454) chore: upgrade @eslint/eslintrc@2.0.1 (#16977) (Milos Djermanovic) +* [`698c5aa`](https://github.com/eslint/eslint/commit/698c5aad50e628ff00281dbc786e42de79834035) chore: upgrade espree@9.5.0 (#16976) (Milos Djermanovic) +* [`b98fdd4`](https://github.com/eslint/eslint/commit/b98fdd413a3b07b262bfce6f704c1c1bb8582770) docs: Update README (GitHub Actions Bot) +* [`c89a485`](https://github.com/eslint/eslint/commit/c89a485c49450532ee3db74f2638429f1f37d0dd) feat: Add `checkJSDoc` option to multiline-comment-style (#16807) (Laurent Cozic) +* [`f5f5e11`](https://github.com/eslint/eslint/commit/f5f5e11bd5fd3daab9ccae41e270739c836c305e) feat: Serialize parsers/processors in flat config (#16944) (Nicholas C. Zakas) +* [`caf08ce`](https://github.com/eslint/eslint/commit/caf08ce0cc74917f7c0eec92d25fd784dc33ac4d) docs: fix estree link in custom formatters docs (#16967) (Milos Djermanovic) +* [`75acdd2`](https://github.com/eslint/eslint/commit/75acdd21c5ce7024252e9d41ed77d2f30587caac) chore: lint more js files in docs (#16964) (Milos Djermanovic) +* [`3398431`](https://github.com/eslint/eslint/commit/3398431574b903757bc78b08c8ed36b7b9fce8eb) docs: Custom Parsers cleanup/expansion (#16887) (Ben Perlmutter) +* [`19d3531`](https://github.com/eslint/eslint/commit/19d3531d9b54e1004318d28f9a6e18305c5bcc18) docs: Update README (GitHub Actions Bot) +* [`4799297`](https://github.com/eslint/eslint/commit/4799297ea582c81fd1e5623d32a7ddf7a7f3a126) feat: use @eslint-community dependencies (#16784) (Michaël De Boey) +* [`b09a512`](https://github.com/eslint/eslint/commit/b09a512107249a4eb19ef5a37b0bd672266eafdb) docs: detect and fix broken links (#16837) (Nitin Kumar) +* [`92c1943`](https://github.com/eslint/eslint/commit/92c1943ba73ea01e87086236e8736539b0eed558) fix: correctly iterate files matched by glob patterns (#16831) (Nitin Kumar) +* [`89d9844`](https://github.com/eslint/eslint/commit/89d9844b3151f09b5b21b6eeeda671009ec301e9) ci: bump actions/add-to-project from 0.4.0 to 0.4.1 (#16943) (dependabot[bot]) + v8.35.0 - February 26, 2023 * [`cdcbe12`](https://github.com/eslint/eslint/commit/cdcbe127de20cbcc4e24131a808c13b1024e61a2) chore: upgrade @eslint/js@8.35.0 (#16935) (Brandon Mills) From 75df535681d15d7d685468d637945a200301f9ee Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Fri, 10 Mar 2023 17:15:50 -0500 Subject: [PATCH 14/48] 8.36.0 --- docs/package.json | 2 +- docs/src/use/formatters/html-formatter-example.html | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/package.json b/docs/package.json index 785689e424c..64125d3954b 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.35.0", + "version": "8.36.0", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index fd4a6f65e0e..0e3b6f25cce 100644 --- a/docs/src/use/formatters/html-formatter-example.html +++ b/docs/src/use/formatters/html-formatter-example.html @@ -118,7 +118,7 @@

ESLint Report

- 9 problems (5 errors, 4 warnings) - Generated on Sun Feb 26 2023 04:15:37 GMT-0500 (Eastern Standard Time) + 9 problems (5 errors, 4 warnings) - Generated on Fri Mar 10 2023 17:15:51 GMT-0500 (Eastern Standard Time)
diff --git a/package.json b/package.json index b9acb455f8f..c8c7f5fbbb8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.35.0", + "version": "8.36.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 3e1cf6bfc5ebc29314ddbe462d6cb580e9ab085c Mon Sep 17 00:00:00 2001 From: Ben Perlmutter <57849986+bpmutter@users.noreply.github.com> Date: Mon, 13 Mar 2023 11:11:34 -0400 Subject: [PATCH 15/48] docs: Copy edits on Maintain ESLint docs (#16939) * docs: Copy edits on Maintain ESLint docs * Apply suggestions from code review * Apply suggestions from code review Co-authored-by: Amaresh S M Co-authored-by: Nicholas C. Zakas * Apply suggestions from code review * release team -> release manager * 1 more team --> manager * Apply suggestions from code review Co-authored-by: Nitin Kumar * Update docs/src/maintain/manage-releases.md Co-authored-by: Milos Djermanovic --------- Co-authored-by: Amaresh S M Co-authored-by: Nicholas C. Zakas Co-authored-by: Nitin Kumar Co-authored-by: Milos Djermanovic --- docs/src/maintain/index.md | 2 +- docs/src/maintain/manage-issues.md | 76 +++++++++++------------ docs/src/maintain/manage-releases.md | 24 ++++--- docs/src/maintain/review-pull-requests.md | 8 +-- docs/src/maintain/working-groups.md | 2 +- 5 files changed, 59 insertions(+), 53 deletions(-) diff --git a/docs/src/maintain/index.md b/docs/src/maintain/index.md index 88d537aa053..013a5a1ec0b 100644 --- a/docs/src/maintain/index.md +++ b/docs/src/maintain/index.md @@ -11,7 +11,7 @@ This guide is intended for those who work as part of the ESLint project team. ## [Manage Issues](manage-issues) -Describes how to deal with issues when they're opened, when interacting with users, and how to close them effectively. +Describes how to deal with issues when they're opened, how to interact with users who open issues, and how to close issues effectively. ## [Review Pull Requests](review-pull-requests) diff --git a/docs/src/maintain/manage-issues.md b/docs/src/maintain/manage-issues.md index 085b1083782..58cc9deb9e6 100644 --- a/docs/src/maintain/manage-issues.md +++ b/docs/src/maintain/manage-issues.md @@ -12,7 +12,7 @@ New issues are filed frequently, and how we respond to those issues directly aff ## Things to Keep in Mind -1. **Be nice.** Even if the people are being rude or aggressive on an issue, as a project team member you must be the mature one in the conversation. Do your best to work with everyone no matter their style. Remember, poor wording choice can also be a sign of someone who doesn't know English very well, so be sure to consider that when trying to determine the tone of someone's message. Being rude, even when someone is being rude to you, reflects poorly on the team and the project as a whole. +1. **Be nice.** Even if the people are being rude or aggressive on an issue, you must be the mature one in the conversation as a project team member. Do your best to work with everyone no matter their style. Remember, poor wording choice can also be a sign of someone who doesn't know English very well, so be sure to consider that when trying to determine the tone of someone's message. Being rude, even when someone is being rude to you, reflects poorly on the team and the project as a whole. 1. **Be inquisitive.** Ask questions on the issue whenever something isn't clear. Don't assume you understand what's being reported if there are details missing. Whenever you are unsure, it's best to ask for more information. 1. **Not all requests are equal.** It's unlikely we'll be able to accommodate every request, so don't be afraid to say that something doesn't fit into the scope of the project or isn't practical. It's better to give such feedback if that's the case. 1. **Close when appropriate.** Don't be afraid to close issues that you don't think will be done, or when it's become clear from the conversation that there's no further work to do. Issues can always be reopened if they are closed incorrectly, so feel free to close issues when appropriate. Just be sure to leave a comment explaining why the issue is being closed (if not closed by a commit). @@ -21,10 +21,10 @@ New issues are filed frequently, and how we respond to those issues directly aff There are four primary issue categories: -1. **Bug** - something isn't working the way it's expected to work. -1. **Enhancement** - a change to something that already exists. For instance, adding a new option to an existing rule or a bug in a rule where fixing it will result in the rule reporting more problems (in this case, use both "Bug" and "Enhancement"). -1. **Feature** - adding something that doesn't already exist. For example, adding a new rule, new formatter, or new command line flag. -1. **Question** - an inquiry about how something works that won't result in a code change. We'd prefer if people use discussions or Discord for questions, but sometimes they'll open an issue. +1. **Bug**: Something isn't working the way it's expected to work. +1. **Enhancement**: A change to something that already exists. For instance, adding a new option to an existing rule or fixing a bug in a rule where fixing it will result in the rule reporting more problems (in this case, use both "Bug" and "Enhancement"). +1. **Feature**: Adding something that doesn't already exist. For example, adding a new rule, new formatter, or new command line flag. +1. **Question**: An inquiry about how something works that won't result in a code change. We prefer if people use GitHub Discussions or Discord for questions, but sometimes they'll open an issue. The first goal when evaluating an issue is to determine which category the issue falls into. @@ -32,17 +32,17 @@ The first goal when evaluating an issue is to determine which category the issue All of ESLint's issues, across all GitHub repositories, are managed on our [Triage Project](https://github.com/orgs/eslint/projects/2). Please use the Triage project instead of the issues list when reviewing issues to determine what to work on. The Triage project has several columns: -* **Needs Triage** - issues that have not yet been reviewed by anyone -* **Triaging** - issues that someone has reviewed but has not been able to fully triage yet -* **Ready for Dev Team** - issues that have been triaged and have all of the information necessary for the dev team to take a look -* **Evaluating** - the dev team is evaluating these issues to determine whether to move forward or not -* **Feedback Needed** - a team member is requesting more input from the rest of the team before proceeding -* **Waiting for RFC** - the next step in the process is for an RFC to be written -* **RFC Opened** - an RFC is opened to address these issues -* **Blocked** - the issue can't move forward due to some dependency -* **Ready to Implement** - these issues have all of the details necessary to start implementation -* **PR Opened** - there is an open pull request for each of these issues -* **Completed** - the issue has been closed (either via pull request merge or by the team manually closing the issue) +* **Needs Triage**: Issues that have not yet been reviewed by anyone +* **Triaging**: Issues that someone has reviewed but has not been able to fully triage yet +* **Ready for Dev Team**: Issues that have been triaged and have all the information necessary for the dev team to take a look +* **Evaluating**: The dev team is evaluating these issues to determine whether to move forward or not +* **Feedback Needed**: A team member is requesting more input from the rest of the team before proceeding +* **Waiting for RFC**: The next step in the process is for an RFC to be written +* **RFC Opened**: An RFC is opened to address these issues +* **Blocked**: The issue can't move forward due to some dependency +* **Ready to Implement**: These issues have all the details necessary to start implementation +* **PR Opened**: There is an open pull request for each of these issues +* **Completed**: The issue has been closed (either via pull request merge or by the team manually closing the issue) We make every attempt to automate movement between as many columns as we can, but sometimes moving issues needs to be done manually. @@ -50,12 +50,12 @@ We make every attempt to automate movement between as many columns as we can, bu When an issue is opened, it is automatically added to the "Needs Triage" column in the Triage project. These issues need to be evaluated to determine next steps. Anyone on the support team or dev team can follow these steps to properly triage issues. -**Note:** If an issue is in the "Triaging" column, that means someone is already triaging it and you should let them finish. There's no need to comment on issues in the "Triaging" column unless someone asks for help. +**Note:** If an issue is in the "Triaging" column, that means someone is already triaging it, and you should let them finish. There's no need to comment on issues in the "Triaging" column unless someone asks for help. The steps for triaging an issue are: -1. Move the issue from "Needs Triage" to "Triaging" in the Triage project -1. Check: Has all of the information in the issue template been provided? +1. Move the issue from "Needs Triage" to "Triaging" in the Triage project. +1. Check: Has all the information in the issue template been provided? * **No:** If information is missing from the issue template, or you can't tell what is being requested, please ask the author to provide the missing information: * Add the "needs info" label to the issue so we know that this issue is stalled due to lack of information. * Don't move on to other steps until the necessary information has been provided. @@ -65,15 +65,15 @@ The steps for triaging an issue are: * If the issue is reporting a bug, try to reproduce the issue following the instructions in the issue. If you can reproduce the bug, please add the "repro:yes" label. (The bot will automatically remove the "repro:needed" label.) If you can't reproduce the bug, ask the author for more information about their environment or to clarify reproduction steps. * If the issue is reporting something that works as intended, please add the "works as intended" label and close the issue. * For all issues, please add labels describing the part of ESLint affected: - * "3rd party plugin" - related to third-party functionality (plugins, parsers, rules, etc.) - * "build" - related to commands run during a build (testing, linting, release scripts, etc.) - * "cli" - related to command line input or output, or to `CLIEngine` - * "core" - related to internal APIs - * "documentation" - related to content on eslint.org - * "infrastructure" - related to resources needed for builds or deployment (VMs, CI tools, bots, etc.) - * "rule" - related to core rules - * If you can't properly triage the issue, move the issue back to the "Needs Triage" column in the Triage project so someone else can triage it - * If you have triaged the issue, move the issue to the "Ready for Dev Team" column in the Triage project + * **3rd party plugin**: Related to third-party functionality (plugins, parsers, rules, etc.) + * **build**: Related to commands run during a build (testing, linting, release scripts, etc.) + * **cli**: Related to command line input or output, or to `CLIEngine` + * **core**: Related to internal APIs + * **documentation**: Related to content on eslint.org + * **infrastructure**: Related to resources needed for builds or deployment (VMs, CI tools, bots, etc.) + * **rule**: Related to core rules + * If you can't properly triage the issue, move the issue back to the "Needs Triage" column in the Triage project so someone else can triage it. + * If you have triaged the issue, move the issue to the "Ready for Dev Team" column in the Triage project. ## Evaluation Process @@ -81,12 +81,12 @@ When an issue has been moved to the "Ready for Dev Team" column, any dev team me 1. Move the issue into the "Evaluating" column. 1. Next steps: - * **Bugs:** if you can verify the bug, add the "accepted" label and ask if they would like to submit a pull request. - * **New Rules:** if you are willing to champion the rule (meaning you believe it should be included in ESLint core and you will take ownership of the process for including it), add a comment saying you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. - * **Rule Changes:** if you are willing to champion the change and it would not be a breaking change (requiring a major version increment), add a comment saying that you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. - * **Breaking Changes:** if you suspect or can verify that a change would be breaking, label it as "Breaking". - * **Duplicates:** if you can verify the issue is a duplicate, add a comment mentioning the duplicate issue (such as, "Duplicate of #1234") and close the issue. -1. Regardless of the above, always leave a comment. Don't just add labels, engage with the person who opened the issue by asking a question (request more information if necessary) or stating your opinion of the issue. If it's a verified bug, ask if the user would like to submit a pull request. + * **Bugs**: If you can verify the bug, add the "accepted" label and ask if they would like to submit a pull request. + * **New Rules**: If you are willing to champion the rule (meaning you believe it should be included in ESLint core and you will take ownership of the process for including it), add a comment saying you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. + * **Rule Changes**: If you are willing to champion the change and it would not be a breaking change (requiring a major version increment), add a comment saying that you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. + * **Breaking Changes**: If you suspect or can verify that a change would be breaking, label it as "Breaking". + * **Duplicates**: If you can verify the issue is a duplicate, add a comment mentioning the duplicate issue (such as, "Duplicate of #1234") and close the issue. +1. Regardless of the above, always leave a comment. Don't just add labels; engage with the person who opened the issue by asking a question (request more information if necessary) or stating your opinion of the issue. If it's a verified bug, ask if the user would like to submit a pull request. 1. If the issue can't be implemented because it needs an external dependency to be updated or needs to wait for another issue to be resolved, move the issue to the "Blocked" column. 1. If the issue has been accepted and an RFC is required as the next step, move the issue to the "Waiting for RFC" column and comment on the issue that an RFC is needed. @@ -110,7 +110,7 @@ New rules and rule changes require a champion. As champion, it's your job to: * Gain [consensus](#consensus) from the ESLint team on inclusion * Guide the rule creation process until it's complete (so only champion a rule that you have time to implement or help another contributor implement) -Once consensus has been reached on inclusion, add the "accepted" and, optionally, "help wanted" and "good first issue" labels, as necessary. +Once consensus has been reached on inclusion, add the "accepted" label. Optionally, add "help wanted" and "good first issue" labels, as necessary. ## Consensus @@ -129,9 +129,9 @@ The issue will be discussed at the next TSC meeting and the resolution will be p In addition to the above, changes to the core (including CLI changes) that would result in a minor or major version release must be approved by the TSC by standard TSC motion. Add the label "tsc agenda" to the issue and it will be discussed at the next TSC meeting. In general, requests should meet the following criteria to be considered: -1. The feature or enhancement is in scope for the project and should be added to the roadmap -1. Someone is committed to including the change within the next year -1. There is reasonable certainty about who will do the work +1. The feature or enhancement is in scope for the project and should be added to the roadmap. +1. Someone is committed to including the change within the next year. +1. There is reasonable certainty about who will do the work. When a suggestion is too ambitious or would take too much time to complete, it's better not to accept the proposal. Stick to small, incremental changes and lay out a roadmap of where you'd like the project to go eventually. Don't let the project get bogged down in big features that will take a long time to complete. diff --git a/docs/src/maintain/manage-releases.md b/docs/src/maintain/manage-releases.md index 10d681a4fd8..118edc79399 100644 --- a/docs/src/maintain/manage-releases.md +++ b/docs/src/maintain/manage-releases.md @@ -13,18 +13,20 @@ Releases are when a project formally publishes a new version so the community ca * Regular releases that follow [semantic versioning](https://semver.org/) and are considered production-ready. * Prereleases that are not considered production-ready and are intended to give the community a preview of upcoming changes. -## Release Team +## Release Manager -A two-person release team is assigned to each scheduled release. This two-person team is responsible for: +One member of the Technical Steering Committee (TSC) is assigned to manage each scheduled release. The release manager is determined at the TSC meeting the day before the release. + +The release manager is responsible for: 1. The scheduled release on Friday 1. Monitoring issues over the weekend 1. Determining if a patch release is necessary on Monday 1. Publishing the patch release (if necessary) -The two-person team should seek input from the whole team on the Monday following a release to double-check if a patch release is necessary. +The release manager should seek input from the whole team on the Monday following a release to double-check if a patch release is necessary. -At least one member of the release team needs to have access to eslint's two-factor authentication for npm in order to do a release. +The release manager needs to have access to ESLint's two-factor authentication for npm in order to do a release. ## Release Communication @@ -32,10 +34,10 @@ Each scheduled release should be associated with a release issue ([example](http ## Process -On the day of a scheduled release, the release team should follow these steps: +On the day of a scheduled release, the release manager should follow these steps: 1. Review open pull requests to see if any should be merged. In general, you can merge pull requests that: - * Have been open at least two days and have been reviewed (these are just waiting for merge). + * Have been open for at least two days and approved (these are just waiting for merge). * Important pull requests (as determined by the team). You should stop and have people review before merging if they haven't been already. * Documentation changes. * Small bugfixes written by a team member. @@ -49,19 +51,23 @@ On the day of a scheduled release, the release team should follow these steps: 1. Make a release announcement on the release issue. Document any problems that occurred during the release, and remind the team not to merge anything other than documentation changes and bugfixes. Leave the release issue open. 1. Add the `patch release pending` label to the release issue. (When this label is present, `eslint-github-bot` will create a pending status check on non-semver-patch pull requests, to ensure that they aren't accidentally merged while a patch release is pending.) -On the Monday following the scheduled release, the release team needs to determine if a patch release is necessary. A patch release is considered necessary if any of the following occurred since the scheduled release: +All release-related communications occur in the `#team` channel on Discord. + +On the Monday following the scheduled release, the release manager needs to determine if a patch release is necessary. A patch release is considered necessary if any of the following occurred since the scheduled release: * A regression bug is causing people's lint builds to fail when it previously passed. * Any bug that is causing a lot of problems for users (frequently happens due to new functionality). The patch release decision should be made as early on Monday as possible. If a patch release is necessary, then follow the same steps as the scheduled release process. -In rare cases, a second patch release might be necessary if the release is known to have a severe regression that hasn't been fixed by Monday. If this occurs, the release team should announce the situation on the release issue, and leave the issue open until all patch releases are complete. However, it's usually better to fix bugs for the next release cycle rather than doing a second patch release. +In rare cases, a second patch release might be necessary if the release is known to have a severe regression that hasn't been fixed by Monday. If this occurs, the release manager should announce the situation on the release issue, and leave the issue open until all patch releases are complete. However, it's usually better to fix bugs for the next release cycle rather than doing a second patch release. After the patch release has been published (or no patch release is necessary), close the release issue and inform the team that they can start merging in semver-minor changes again. ## Emergency Releases -In general, we try not to do emergency releases (an emergency release is unplanned and isn't the regularly scheduled release or the anticipated patch release). Even if there is a regression, it's best to wait the weekend to see if any other problems arise so a patch release can fix as many issues as possible. +An emergency release is unplanned and isn't the regularly scheduled release or the anticipated patch release. + +In general, we try not to do emergency releases. Even if there is a regression, it's best to wait until Monday to see if any other problems arise so a patch release can fix as many issues as possible. The only real exception is if ESLint is completely unusable by most of the current users. For instance, we once pushed a release that errored for everyone because it was missing some core files. In that case, an emergency release is appropriate. diff --git a/docs/src/maintain/review-pull-requests.md b/docs/src/maintain/review-pull-requests.md index 976a7fd1696..e71ef9add7b 100644 --- a/docs/src/maintain/review-pull-requests.md +++ b/docs/src/maintain/review-pull-requests.md @@ -28,10 +28,10 @@ Once the bot checks have been satisfied, you check the following: 1. Double-check that the commit message tag ("Fix:", "New:", etc.) is correct based on the issue (or, if no issue is referenced, based on the stated problem). 1. If the pull request makes a change to core, ensure that an issue exists and the pull request references the issue in the commit message. -1. Does the code follow our conventions (including header comments, JSDoc comments, etc.)? If not, please leave that feedback and reference the conventions document. +1. Does the code follow our conventions (including header comments, JSDoc comments, etc.)? If not, please leave that feedback and reference the [Code Conventions](../contribute/code-conventions) documentation. 1. For code changes: * Are there tests that verify the change? If not, please ask for them. - * Is documentation needed for the change? If yes, please let the submitter know. + * Is documentation needed for the change? If yes, please ask the submitter to add the necessary documentation. 1. Are there any automated testing errors? If yes, please ask the submitter to check on them. 1. If you've reviewed the pull request and there are no outstanding issues, leave a comment "LGTM" to indicate your approval. If you would like someone else to verify the change, comment "LGTM but would like someone else to verify." @@ -91,8 +91,8 @@ If the pull request was created from a branch on the `eslint/eslint` repository There are several times when it's appropriate to close a pull request without merging: -1. The pull request addresses an issue that is already fixed -1. The pull request hasn't been updated in 17 days +1. The pull request addresses an issue that is already fixed. +1. The pull request hasn't been updated in 17 days. 1. The pull request submitter isn't willing to follow project guidelines. In any of these cases, please be sure to leave a comment stating why the pull request is being closed. diff --git a/docs/src/maintain/working-groups.md b/docs/src/maintain/working-groups.md index 97d31dfd29f..cfb5c40ec74 100644 --- a/docs/src/maintain/working-groups.md +++ b/docs/src/maintain/working-groups.md @@ -7,7 +7,7 @@ eleventyNavigation: order: 4 --- -The ESLint TSC may form working groups to focus on a specific area of the project. +The ESLint [Technical Steering Committee](../contribute/governance#technical-steering-committee-tsc) (TSC) may form working groups to focus on a specific area of the project. ## Creating a Working Group From fd47998af6efadcdf5ba93e0bd1f4c02d97d22b3 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Mon, 13 Mar 2023 16:12:29 +0100 Subject: [PATCH 16/48] docs: update `Array.prototype.toSorted` specification link (#16982) --- docs/src/rules/array-callback-return.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/rules/array-callback-return.md b/docs/src/rules/array-callback-return.md index 01d734d59ed..965d18537af 100644 --- a/docs/src/rules/array-callback-return.md +++ b/docs/src/rules/array-callback-return.md @@ -35,7 +35,7 @@ This rule finds callback functions of the following methods, then checks usage o * [`Array.prototype.reduceRight`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.reduceright) * [`Array.prototype.some`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.some) * [`Array.prototype.sort`](https://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.sort) -* [`Array.prototype.toSorted`](https://tc39.es/proposal-change-array-by-copy/#sec-array.prototype.toSorted) +* [`Array.prototype.toSorted`](https://tc39.es/ecma262/#sec-array.prototype.tosorted) * And above of typed arrays. Examples of **incorrect** code for this rule: From 6157d813e19b80481a46f8cbdf9eae18a55e5619 Mon Sep 17 00:00:00 2001 From: alope107 Date: Tue, 14 Mar 2023 02:19:50 -0700 Subject: [PATCH 17/48] docs: Add example to guard-for-in docs. (#16983) Adds an example to the correct code for the guard-for-in rule that uses Object.hasOwn Fixes #16981 --- docs/src/rules/guard-for-in.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/src/rules/guard-for-in.md b/docs/src/rules/guard-for-in.md index 9481d2ad07f..f358e503039 100644 --- a/docs/src/rules/guard-for-in.md +++ b/docs/src/rules/guard-for-in.md @@ -44,6 +44,12 @@ Examples of **correct** code for this rule: ```js /*eslint guard-for-in: "error"*/ +for (key in foo) { + if (Object.hasOwn(foo, key)) { + doSomething(key); + } +} + for (key in foo) { if (Object.prototype.hasOwnProperty.call(foo, key)) { doSomething(key); From 69bc0e2f4412998f9384600a100d7882ea4dd3f3 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 15 Mar 2023 09:59:58 +0100 Subject: [PATCH 18/48] ci: pin Node 19 to 19.7.0 (#16987) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f8b968a550..3b740e3bfc9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node: [19.x, 18.x, 17.x, 16.x, 14.x, 12.x, "12.22.0"] + node: ["19.7.0", 18.x, 17.x, 16.x, 14.x, 12.x, "12.22.0"] include: - os: windows-latest node: "lts/*" From 5251a921866e8d3b380dfe8db8a6e6ab97773d5e Mon Sep 17 00:00:00 2001 From: alope107 Date: Wed, 15 Mar 2023 02:07:27 -0700 Subject: [PATCH 19/48] docs: Describe guard options for guard-for-in (#16986) Describes the different checks that can be used to satisfy guard-for-in and links prefer-object-has-own as a related rule. --- docs/src/rules/guard-for-in.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/rules/guard-for-in.md b/docs/src/rules/guard-for-in.md index f358e503039..0cdecb43a53 100644 --- a/docs/src/rules/guard-for-in.md +++ b/docs/src/rules/guard-for-in.md @@ -2,6 +2,7 @@ title: guard-for-in rule_type: suggestion related_rules: +- prefer-object-has-own - no-prototype-builtins further_reading: - https://javascriptweblog.wordpress.com/2011/01/04/exploring-javascript-for-in-loops/ @@ -17,6 +18,10 @@ for (key in foo) { } ``` +For codebases that do not support ES2022, `Object.prototype.hasOwnProperty.call(foo, key)` can be used as a check that the property is not inherited. + +For codebases that do support ES2022, `Object.hasOwn(foo, key)` can be used as a shorter alternative; see [prefer-object-has-own](prefer-object-has-own). + Note that simply checking `foo.hasOwnProperty(key)` is likely to cause an error in some cases; see [no-prototype-builtins](no-prototype-builtins). ## Rule Details From d049f974103e530ef76ede25af701635caf1f405 Mon Sep 17 00:00:00 2001 From: Ben Perlmutter <57849986+bpmutter@users.noreply.github.com> Date: Wed, 15 Mar 2023 06:17:12 -0400 Subject: [PATCH 20/48] docs: 'How ESLint is Maintained' page (#16961) * docs: How eslint is maintained page * implement reviewer feedback * remove trailing space * remove todo * Apply suggestions from code review Co-authored-by: Nitin Kumar Co-authored-by: Nicholas C. Zakas * Apply suggestions from code review Co-authored-by: Milos Djermanovic * Update docs/src/maintain/overview.md Co-authored-by: Ben Perlmutter <57849986+bpmutter@users.noreply.github.com> --------- Co-authored-by: Nitin Kumar Co-authored-by: Nicholas C. Zakas Co-authored-by: Milos Djermanovic --- docs/src/maintain/index.md | 4 +++ docs/src/maintain/manage-issues.md | 2 +- docs/src/maintain/manage-releases.md | 2 +- docs/src/maintain/overview.md | 44 +++++++++++++++++++++++ docs/src/maintain/review-pull-requests.md | 2 +- docs/src/maintain/working-groups.md | 2 +- 6 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 docs/src/maintain/overview.md diff --git a/docs/src/maintain/index.md b/docs/src/maintain/index.md index 013a5a1ec0b..29c3ab89f30 100644 --- a/docs/src/maintain/index.md +++ b/docs/src/maintain/index.md @@ -9,6 +9,10 @@ eleventyNavigation: This guide is intended for those who work as part of the ESLint project team. +## [How ESLint is Maintained](overview) + +Explains how ESLint is maintained, including information about team, governance, and funding. + ## [Manage Issues](manage-issues) Describes how to deal with issues when they're opened, how to interact with users who open issues, and how to close issues effectively. diff --git a/docs/src/maintain/manage-issues.md b/docs/src/maintain/manage-issues.md index 58cc9deb9e6..6cfdbc93b19 100644 --- a/docs/src/maintain/manage-issues.md +++ b/docs/src/maintain/manage-issues.md @@ -4,7 +4,7 @@ eleventyNavigation: key: manage issues parent: maintain eslint title: Manage Issues - order: 1 + order: 2 --- diff --git a/docs/src/maintain/manage-releases.md b/docs/src/maintain/manage-releases.md index 118edc79399..7a5e763a009 100644 --- a/docs/src/maintain/manage-releases.md +++ b/docs/src/maintain/manage-releases.md @@ -4,7 +4,7 @@ eleventyNavigation: key: manage releases parent: maintain eslint title: Manage Releases - order: 3 + order: 4 --- diff --git a/docs/src/maintain/overview.md b/docs/src/maintain/overview.md new file mode 100644 index 00000000000..362637a8a3c --- /dev/null +++ b/docs/src/maintain/overview.md @@ -0,0 +1,44 @@ +--- +title: How ESLint is Maintained +eleventyNavigation: + key: how eslint is maintained + parent: maintain eslint + title: How ESLint is Maintained + order: 1 + +--- + +This page explains the different roles and structures involved in maintaining ESLint. + +## The ESLint Team + +The ESLint team works together to develop and maintain ESLint. To learn more about the different roles on the ESLint team, refer to [Governance](../contribute/governance). To see the current team members, refer to [Team](/team/). + +## Organization Structure + +ESLint is part of the [OpenJS Foundation](https://openjsf.org/), a nonprofit organization that supports open-source projects and communities in the JavaScript ecosystem. + +The OpenJS Foundation provides legal infrastructure for JavaScript projects like ESLint. It is the owner of the intellectual property related to ESLint, including copyrights and trademarks, and ensures the independence of the project. They are also a resource for ESLint if we need legal advice or representation. + +The OpenJS Foundation does not participate in the day-to-day functioning of ESLint. + +## Funding + +ESLint is funded through several sources, including: + +* [**Open Collective**](https://opencollective.com/eslint): A platform for financing open source projects. +* [**GitHub Sponsors**](https://github.com/sponsors/eslint): A platform for funding open source projects associated with Github. +* [**Tidelift**](https://tidelift.com/subscription/pkg/npm-eslint): A subscription service that lets enterprises manage and fund the open source projects that their organization uses. +* [**Carbon Ads**](https://www.carbonads.net/open-source): Developer-centric advertising provider used on [eslint.org](https://eslint.org/). +* [**Stackaid.us**](https://simulation.stackaid.us/github/eslint/eslint): Tool that developers can use to allocate funding to the open source projects they use. + +ESLint uses this funding for the following purposes: + +* Pay team members and contractors +* Fund projects +* Pay for services that keep ESLint running (web hosting, software subscriptions, etc.) +* Provide financial support to our dependencies and ecosystem + +## Joining the Maintainer Team + +ESLint is an open-source project, and anyone can contribute to the project. If you're interested in becoming part of the maintainer team, stop by our [Discord](https://eslint.org/chat) and introduce yourself. diff --git a/docs/src/maintain/review-pull-requests.md b/docs/src/maintain/review-pull-requests.md index e71ef9add7b..c1711dfd65c 100644 --- a/docs/src/maintain/review-pull-requests.md +++ b/docs/src/maintain/review-pull-requests.md @@ -4,7 +4,7 @@ eleventyNavigation: key: review pull requests parent: maintain eslint title: Review Pull Requests - order: 2 + order: 3 --- diff --git a/docs/src/maintain/working-groups.md b/docs/src/maintain/working-groups.md index cfb5c40ec74..659ac04f73d 100644 --- a/docs/src/maintain/working-groups.md +++ b/docs/src/maintain/working-groups.md @@ -4,7 +4,7 @@ eleventyNavigation: key: working groups parent: maintain eslint title: Working Groups - order: 4 + order: 5 --- The ESLint [Technical Steering Committee](../contribute/governance#technical-steering-committee-tsc) (TSC) may form working groups to focus on a specific area of the project. From c3da975e69fde46f35338ce48528841a8dc1ffd2 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 15 Mar 2023 13:06:54 -0700 Subject: [PATCH 21/48] chore: Remove triage label from template (#16990) --- .github/ISSUE_TEMPLATE/change.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/change.yml b/.github/ISSUE_TEMPLATE/change.yml index cc2066dd3d3..c7a21127eba 100644 --- a/.github/ISSUE_TEMPLATE/change.yml +++ b/.github/ISSUE_TEMPLATE/change.yml @@ -3,7 +3,6 @@ description: "Request a change that is not a bug fix, rule change, or new rule" title: "Change Request: (fill in)" labels: - enhancement - - triage - core body: - type: markdown From ada6a3e6e3607523958f35e1260537630ec0e976 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Thu, 16 Mar 2023 02:02:23 +0100 Subject: [PATCH 22/48] ci: unpin Node 19 (#16993) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b740e3bfc9..5f8b968a550 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node: ["19.7.0", 18.x, 17.x, 16.x, 14.x, 12.x, "12.22.0"] + node: [19.x, 18.x, 17.x, 16.x, 14.x, 12.x, "12.22.0"] include: - os: windows-latest node: "lts/*" From 892e6e58c5a07a549d3104de3b6b5879797dc97f Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Wed, 15 Mar 2023 18:06:37 -0700 Subject: [PATCH 23/48] feat: languageOptions.parser must be an object. (#16985) * feat: languageOptions.parser must be an object. Changes languageOptions.parser to disallow string values. Fixes #16930 * Add parser:null check * Remove unnecessary check --- .../use/configure/configuration-files-new.md | 6 +- lib/config/default-config.js | 5 +- lib/config/flat-config-array.js | 12 +--- lib/config/flat-config-schema.js | 22 ++---- tests/lib/config/flat-config-array.js | 70 +++++-------------- tests/lib/eslint/flat-eslint.js | 2 +- tests/lib/linter/linter.js | 28 ++------ 7 files changed, 31 insertions(+), 114 deletions(-) diff --git a/docs/src/use/configure/configuration-files-new.md b/docs/src/use/configure/configuration-files-new.md index 02318bceb76..a57bbf0f235 100644 --- a/docs/src/use/configure/configuration-files-new.md +++ b/docs/src/use/configure/configuration-files-new.md @@ -41,8 +41,8 @@ Each configuration object contains all of the information ESLint needs to execut * `ecmaVersion` - The version of ECMAScript to support. May be any year (i.e., `2022`) or version (i.e., `5`). Set to `"latest"` for the most recent supported version. (default: `"latest"`) * `sourceType` - The type of JavaScript source code. Possible values are `"script"` for traditional script files, `"module"` for ECMAScript modules (ESM), and `"commonjs"` for CommonJS files. (default: `"module"` for `.js` and `.mjs` files; `"commonjs"` for `.cjs` files) * `globals` - An object specifying additional objects that should be added to the global scope during linting. - * `parser` - Either an object containing a `parse()` method or a string indicating the name of a parser inside of a plugin (i.e., `"pluginName/parserName"`). (default: `"@/espree"`) - * `parserOptions` - An object specifying additional options that are passed directly to the `parser()` method on the parser. The available options are parser-dependent. + * `parser` - An object containing a `parse()` method or a `parseForESLint()` method. (default: [`espree`](https://github.com/eslint/espree)) + * `parserOptions` - An object specifying additional options that are passed directly to the `parse()` or `parseForESLint()` method on the parser. The available options are parser-dependent. * `linterOptions` - An object containing settings related to the linting process. * `noInlineConfig` - A Boolean value indicating if inline configuration is allowed. * `reportUnusedDisableDirectives` - A Boolean value indicating if unused disable directives should be tracked and reported. @@ -251,7 +251,7 @@ export default [ #### Configuring a custom parser and its options -In many cases, you can use the default parser that ESLint ships with for parsing your JavaScript code. You can optionally override the default parser by using the `parser` property. The `parser` property can be either a string in the format `"pluginName/parserName"` (indicating to retrieve the parser from a plugin) or an object containing either a `parse()` method or a `parseForESLint()` method. For example, you can use the [`@babel/eslint-parser`](https://www.npmjs.com/package/@babel/eslint-parser) package to allow ESLint to parse experimental syntax: +In many cases, you can use the default parser that ESLint ships with for parsing your JavaScript code. You can optionally override the default parser by using the `parser` property. The `parser` property must be an object containing either a `parse()` method or a `parseForESLint()` method. For example, you can use the [`@babel/eslint-parser`](https://www.npmjs.com/package/@babel/eslint-parser) package to allow ESLint to parse experimental syntax: ```js import babelParser from "@babel/eslint-parser"; diff --git a/lib/config/default-config.js b/lib/config/default-config.js index aa0dfb2a522..99ea7b9f84e 100644 --- a/lib/config/default-config.js +++ b/lib/config/default-config.js @@ -19,9 +19,6 @@ exports.defaultConfig = [ { plugins: { "@": { - parsers: { - espree: require("espree") - }, /* * Because we try to delay loading rules until absolutely @@ -43,7 +40,7 @@ exports.defaultConfig = [ languageOptions: { sourceType: "module", ecmaVersion: "latest", - parser: "@/espree", + parser: require("espree"), parserOptions: {} } }, diff --git a/lib/config/flat-config-array.js b/lib/config/flat-config-array.js index 78b4ef44bac..4e82391fc7d 100644 --- a/lib/config/flat-config-array.js +++ b/lib/config/flat-config-array.js @@ -192,17 +192,7 @@ class FlatConfigArray extends ConfigArray { if (languageOptions && languageOptions.parser) { const { parser } = languageOptions; - if (typeof parser === "string") { - const { pluginName, objectName: localParserName } = splitPluginIdentifier(parser); - - parserName = parser; - - if (!plugins || !plugins[pluginName] || !plugins[pluginName].parsers || !plugins[pluginName].parsers[localParserName]) { - throw new TypeError(`Key "parser": Could not find "${localParserName}" in plugin "${pluginName}".`); - } - - languageOptions.parser = plugins[pluginName].parsers[localParserName]; - } else if (typeof parser === "object") { + if (typeof parser === "object") { parserName = getObjectId(parser); if (!parserName) { diff --git a/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js index cb8e7961add..bb6e9f899ac 100644 --- a/lib/config/flat-config-schema.js +++ b/lib/config/flat-config-schema.js @@ -179,18 +179,6 @@ function assertIsObject(value) { } } -/** - * Validates that a value is an object or a string. - * @param {any} value The value to check. - * @returns {void} - * @throws {TypeError} If the value isn't an object or a string. - */ -function assertIsObjectOrString(value) { - if ((!value || typeof value !== "object") && typeof value !== "string") { - throw new TypeError("Expected an object or string."); - } -} - //----------------------------------------------------------------------------- // Low-Level Schemas //----------------------------------------------------------------------------- @@ -242,15 +230,13 @@ const globalsSchema = { const parserSchema = { merge: "replace", validate(value) { - assertIsObjectOrString(value); - if (typeof value === "object" && typeof value.parse !== "function" && typeof value.parseForESLint !== "function") { - throw new TypeError("Expected object to have a parse() or parseForESLint() method."); + if (!value || typeof value !== "object" || + (typeof value.parse !== "function" && typeof value.parseForESLint !== "function") + ) { + throw new TypeError("Expected object with parse() or parseForESLint() method."); } - if (typeof value === "string") { - assertIsPluginMemberName(value); - } } }; diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index c06e4da7798..2b8e8f64ec3 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -16,6 +16,7 @@ const { recommended: recommendedConfig } = require("@eslint/js").configs; const stringify = require("json-stable-stringify-without-jsonify"); +const espree = require("espree"); //----------------------------------------------------------------------------- // Helpers @@ -190,6 +191,7 @@ describe("FlatConfigArray", () => { }); describe("Serialization of configs", () => { + it("should convert config into normalized JSON object", () => { const configs = new FlatConfigArray([{ @@ -207,7 +209,7 @@ describe("FlatConfigArray", () => { languageOptions: { ecmaVersion: "latest", sourceType: "module", - parser: "@/espree", + parser: `espree@${espree.version}`, parserOptions: {} }, processor: void 0 @@ -463,7 +465,7 @@ describe("FlatConfigArray", () => { assert.deepStrictEqual(config.toJSON(), { languageOptions: { ecmaVersion: "latest", - parser: "@/espree", + parser: `espree@${espree.version}`, parserOptions: {}, sourceType: "module" }, @@ -490,7 +492,7 @@ describe("FlatConfigArray", () => { assert.deepStrictEqual(config.toJSON(), { languageOptions: { ecmaVersion: "latest", - parser: "@/espree", + parser: `espree@${espree.version}`, parserOptions: {}, sourceType: "module" }, @@ -521,7 +523,7 @@ describe("FlatConfigArray", () => { assert.deepStrictEqual(config.toJSON(), { languageOptions: { ecmaVersion: "latest", - parser: "@/espree", + parser: `espree@${espree.version}`, parserOptions: {}, sourceType: "module" }, @@ -550,7 +552,7 @@ describe("FlatConfigArray", () => { assert.deepStrictEqual(config.toJSON(), { languageOptions: { ecmaVersion: "latest", - parser: "@/espree", + parser: `espree@${espree.version}`, parserOptions: {}, sourceType: "module" }, @@ -1340,21 +1342,21 @@ describe("FlatConfigArray", () => { parser: true } } - ], "Expected an object or string."); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); - it("should error when an unexpected value is found", async () => { + it("should error when a null is found", async () => { await assertInvalidConfig([ { languageOptions: { - parser: "true" + parser: null } } - ], /Expected string in the form "pluginName\/objectName"/u); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); - it("should error when a plugin parser can't be found", async () => { + it("should error when a parser is a string", async () => { await assertInvalidConfig([ { @@ -1362,7 +1364,7 @@ describe("FlatConfigArray", () => { parser: "foo/bar" } } - ], "Key \"parser\": Could not find \"bar\" in plugin \"foo\"."); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); it("should error when a value doesn't have a parse() method", async () => { @@ -1373,7 +1375,7 @@ describe("FlatConfigArray", () => { parser: {} } } - ], "Expected object to have a parse() or parseForESLint() method."); + ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); }); it("should merge two objects when second object has overrides", () => { @@ -1388,24 +1390,12 @@ describe("FlatConfigArray", () => { } }, { - plugins: { - "@foo/baz": { - parsers: { - bar: stubParser - } - } - }, languageOptions: { - parser: "@foo/baz/bar" + parser: stubParser } } ], { plugins: { - "@foo/baz": { - parsers: { - bar: stubParser - } - }, ...baseConfig.plugins }, languageOptions: { @@ -1420,27 +1410,14 @@ describe("FlatConfigArray", () => { return assertMergedResult([ { - plugins: { - foo: { - parsers: { - bar: stubParser - } - } - }, - languageOptions: { - parser: "foo/bar" + parser: stubParser } }, { } ], { plugins: { - foo: { - parsers: { - bar: stubParser - } - }, ...baseConfig.plugins }, @@ -1460,25 +1437,12 @@ describe("FlatConfigArray", () => { { }, { - plugins: { - foo: { - parsers: { - bar: stubParser - } - } - }, - languageOptions: { - parser: "foo/bar" + parser: stubParser } } ], { plugins: { - foo: { - parsers: { - bar: stubParser - } - }, ...baseConfig.plugins }, diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js index 187b09d7baa..d79e4f92d4b 100644 --- a/tests/lib/eslint/flat-eslint.js +++ b/tests/lib/eslint/flat-eslint.js @@ -773,7 +773,7 @@ describe("FlatESLint", () => { overrideConfigFile: true }); - await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Expected string in the form "pluginName\/objectName" but found "test11"/u); + await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Expected object with parse\(\) or parseForESLint\(\) method/u); }); it("should report zero messages when given a directory with a .js2 file", async () => { diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index 7a1ba3779c0..0ece277dc9a 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -7878,15 +7878,8 @@ describe("Linter with FlatConfigArray", () => { }; const config = { - plugins: { - test: { - parsers: { - "test-parser": parser - } - } - }, languageOptions: { - parser: "test/test-parser" + parser } }; @@ -7925,15 +7918,8 @@ describe("Linter with FlatConfigArray", () => { it("should use parseForESLint() in custom parser when custom parser is specified", () => { const config = { - plugins: { - test: { - parsers: { - "enhanced-parser": testParsers.enhancedParser - } - } - }, languageOptions: { - parser: "test/enhanced-parser" + parser: testParsers.enhancedParser } }; @@ -7949,9 +7935,6 @@ describe("Linter with FlatConfigArray", () => { const config = { plugins: { test: { - parsers: { - "enhanced-parser": testParsers.enhancedParser - }, rules: { "test-service-rule": { create: context => ({ @@ -7967,7 +7950,7 @@ describe("Linter with FlatConfigArray", () => { } }, languageOptions: { - parser: "test/enhanced-parser" + parser: testParsers.enhancedParser }, rules: { "test/test-service-rule": 2 @@ -7988,9 +7971,6 @@ describe("Linter with FlatConfigArray", () => { const config = { plugins: { test: { - parsers: { - "enhanced-parser": testParsers.enhancedParser - }, rules: { "test-service-rule": { create: context => ({ @@ -8006,7 +7986,7 @@ describe("Linter with FlatConfigArray", () => { } }, languageOptions: { - parser: "test/enhanced-parser" + parser: testParsers.enhancedParser }, rules: { "test/test-service-rule": 2 From 129e252132c7c476d7de17f40b54a333ddb2e6bb Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Sat, 18 Mar 2023 20:24:56 +0100 Subject: [PATCH 24/48] fix: Fix typo in `logical-assignment-operators` rule description (#17000) --- lib/rules/logical-assignment-operators.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/logical-assignment-operators.js b/lib/rules/logical-assignment-operators.js index cd533e63a73..701b3114a9f 100644 --- a/lib/rules/logical-assignment-operators.js +++ b/lib/rules/logical-assignment-operators.js @@ -159,7 +159,7 @@ module.exports = { type: "suggestion", docs: { - description: "Require or disallow logical assignment logical operator shorthand", + description: "Require or disallow logical assignment operator shorthand", recommended: false, url: "https://eslint.org/docs/rules/logical-assignment-operators" }, From 1fbf1184fed57df02640aad4659afb54dc26a2e9 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Wed, 22 Mar 2023 12:48:51 +0100 Subject: [PATCH 25/48] fix: `getFirstToken`/`getLastToken` on comment-only node (#16889) * fix: `getFirstToken`/`getLastToken` on comment-only node * Remove unnecessary array bound checks * Split `if`-statement for clarity, remove inaccurate unit test * Fix for trailing comments in root node --- lib/source-code/token-store/utils.js | 18 ++++- tests/fixtures/parsers/all-comments-parser.js | 28 +++++++ tests/lib/source-code/token-store.js | 80 +++++++++++++++++-- 3 files changed, 114 insertions(+), 12 deletions(-) create mode 100644 tests/fixtures/parsers/all-comments-parser.js diff --git a/lib/source-code/token-store/utils.js b/lib/source-code/token-store/utils.js index a2bd77de71a..859831916ea 100644 --- a/lib/source-code/token-store/utils.js +++ b/lib/source-code/token-store/utils.js @@ -49,13 +49,18 @@ exports.getFirstIndex = function getFirstIndex(tokens, indexMap, startLoc) { } if ((startLoc - 1) in indexMap) { const index = indexMap[startLoc - 1]; - const token = (index >= 0 && index < tokens.length) ? tokens[index] : null; + const token = tokens[index]; + + // If the mapped index is out of bounds, the returned cursor index will point after the end of the tokens array. + if (!token) { + return tokens.length; + } /* * For the map of "comment's location -> token's index", it points the next token of a comment. * In that case, +1 is unnecessary. */ - if (token && token.range[0] >= startLoc) { + if (token.range[0] >= startLoc) { return index; } return index + 1; @@ -77,13 +82,18 @@ exports.getLastIndex = function getLastIndex(tokens, indexMap, endLoc) { } if ((endLoc - 1) in indexMap) { const index = indexMap[endLoc - 1]; - const token = (index >= 0 && index < tokens.length) ? tokens[index] : null; + const token = tokens[index]; + + // If the mapped index is out of bounds, the returned cursor index will point before the end of the tokens array. + if (!token) { + return tokens.length - 1; + } /* * For the map of "comment's location -> token's index", it points the next token of a comment. * In that case, -1 is necessary. */ - if (token && token.range[1] > endLoc) { + if (token.range[1] > endLoc) { return index - 1; } return index; diff --git a/tests/fixtures/parsers/all-comments-parser.js b/tests/fixtures/parsers/all-comments-parser.js new file mode 100644 index 00000000000..595392a0970 --- /dev/null +++ b/tests/fixtures/parsers/all-comments-parser.js @@ -0,0 +1,28 @@ +// Similar to the default parser, but considers leading and trailing comments to be part of the root node. +// Some custom parsers like @typescript-eslint/parser behave in this way. + +const espree = require("espree"); +exports.parse = function(code, options) { + const ast = espree.parse(code, options); + + if (ast.range && ast.comments && ast.comments.length > 0) { + const firstComment = ast.comments[0]; + const lastComment = ast.comments[ast.comments.length - 1]; + + if (ast.range[0] > firstComment.range[0]) { + ast.range[0] = firstComment.range[0]; + ast.start = firstComment.start; + if (ast.loc) { + ast.loc.start = firstComment.loc.start; + } + } + if (ast.range[1] < lastComment.range[1]) { + ast.range[1] = lastComment.range[1]; + ast.end = lastComment.end; + if (ast.loc) { + ast.loc.end = lastComment.loc.end; + } + } + } + return ast; +}; diff --git a/tests/lib/source-code/token-store.js b/tests/lib/source-code/token-store.js index ff54a6a838a..3b9db7cb95f 100644 --- a/tests/lib/source-code/token-store.js +++ b/tests/lib/source-code/token-store.js @@ -627,8 +627,8 @@ describe("TokenStore", () => { const tokenStore = new TokenStore(ast.tokens, ast.comments); /* - * Actually, the first of nodes is always tokens, not comments. - * But I think this test case is needed for completeness. + * A node must not start with a token: it can start with a comment or be empty. + * This test case is needed for completeness. */ const token = tokenStore.getFirstToken( { range: [ast.comments[0].range[0], ast.tokens[5].range[1]] }, @@ -644,8 +644,8 @@ describe("TokenStore", () => { const tokenStore = new TokenStore(ast.tokens, ast.comments); /* - * Actually, the first of nodes is always tokens, not comments. - * But I think this test case is needed for completeness. + * A node must not start with a token: it can start with a comment or be empty. + * This test case is needed for completeness. */ const token = tokenStore.getFirstToken( { range: [ast.comments[0].range[0], ast.tokens[5].range[1]] } @@ -654,6 +654,38 @@ describe("TokenStore", () => { assert.strictEqual(token.value, "c"); }); + it("should retrieve the first token if the root node contains a trailing comment", () => { + const parser = require("../../fixtures/parsers/all-comments-parser"); + const code = "foo // comment"; + const ast = parser.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getFirstToken(ast); + + assert.strictEqual(token, ast.tokens[0]); + }); + + it("should return null if the source contains only comments", () => { + const code = "// comment"; + const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getFirstToken(ast, { + filter() { + assert.fail("Unexpected call to filter callback"); + } + }); + + assert.strictEqual(token, null); + }); + + it("should return null if the source is empty", () => { + const code = ""; + const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getFirstToken(ast); + + assert.strictEqual(token, null); + }); + }); describe("when calling getLastTokens", () => { @@ -814,8 +846,8 @@ describe("TokenStore", () => { const tokenStore = new TokenStore(ast.tokens, ast.comments); /* - * Actually, the last of nodes is always tokens, not comments. - * But I think this test case is needed for completeness. + * A node must not end with a token: it can end with a comment or be empty. + * This test case is needed for completeness. */ const token = tokenStore.getLastToken( { range: [ast.tokens[0].range[0], ast.comments[0].range[1]] }, @@ -831,8 +863,8 @@ describe("TokenStore", () => { const tokenStore = new TokenStore(ast.tokens, ast.comments); /* - * Actually, the last of nodes is always tokens, not comments. - * But I think this test case is needed for completeness. + * A node must not end with a token: it can end with a comment or be empty. + * This test case is needed for completeness. */ const token = tokenStore.getLastToken( { range: [ast.tokens[0].range[0], ast.comments[0].range[1]] } @@ -841,6 +873,38 @@ describe("TokenStore", () => { assert.strictEqual(token.value, "b"); }); + it("should retrieve the last token if the root node contains a trailing comment", () => { + const parser = require("../../fixtures/parsers/all-comments-parser"); + const code = "foo // comment"; + const ast = parser.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getLastToken(ast); + + assert.strictEqual(token, ast.tokens[0]); + }); + + it("should return null if the source contains only comments", () => { + const code = "// comment"; + const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getLastToken(ast, { + filter() { + assert.fail("Unexpected call to filter callback"); + } + }); + + assert.strictEqual(token, null); + }); + + it("should return null if the source is empty", () => { + const code = ""; + const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getLastToken(ast); + + assert.strictEqual(token, null); + }); + }); describe("when calling getFirstTokensBetween", () => { From 721c71782a7c11025689a1500e7690fb3794fcce Mon Sep 17 00:00:00 2001 From: Ben Perlmutter <57849986+bpmutter@users.noreply.github.com> Date: Thu, 23 Mar 2023 13:58:01 -0400 Subject: [PATCH 26/48] docs: Custom Processors cleanup and expansion (#16838) * docs: Custom Processors cleanup and expansion * remove TODO * remove extra white space * remove TODO comment * implement NZ feedback * Add multiple custom processors * implement NZ's suggestion about defining `myCustomProcessor` * add message type * Apply suggestions from code review * Update docs/src/extend/custom-processors.md * Update docs/src/extend/custom-processors.md Co-authored-by: Brandon Mills --------- Co-authored-by: Brandon Mills --- docs/src/extend/custom-processors.md | 93 +++++++++++++++++++++++----- 1 file changed, 76 insertions(+), 17 deletions(-) diff --git a/docs/src/extend/custom-processors.md b/docs/src/extend/custom-processors.md index d2fd3df621d..8f330883b03 100644 --- a/docs/src/extend/custom-processors.md +++ b/docs/src/extend/custom-processors.md @@ -7,11 +7,12 @@ eleventyNavigation: order: 2 --- -You can also create custom processors that tell ESLint how to process files other than JavaScript. + +You can also create custom processors that tell ESLint how to process files other than standard JavaScript. For example, you could write a custom processor to extract and process JavaScript from Markdown files ([eslint-plugin-markdown](https://www.npmjs.com/package/eslint-plugin-markdown) includes a custom processor for this). ## Custom Processor Specification -In order to create a processor, the object that is exported from your module has to conform to the following interface: +In order to create a custom processor, the object exported from your module has to conform to the following interface: ```js module.exports = { @@ -46,29 +47,66 @@ module.exports = { **The `preprocess` method** takes the file contents and filename as arguments, and returns an array of code blocks to lint. The code blocks will be linted separately but still be registered to the filename. -A code block has two properties `text` and `filename`; the `text` property is the content of the block and the `filename` property is the name of the block. Name of the block can be anything, but should include the file extension, that would tell the linter how to process the current block. The linter will check [`--ext` CLI option](../use/command-line-interface#--ext) to see if the current block should be linted, and resolve `overrides` configs to check how to process the current block. +A code block has two properties `text` and `filename`. The `text` property is the content of the block and the `filename` property is the name of the block. The name of the block can be anything, but should include the file extension, which tells the linter how to process the current block. The linter checks the [`--ext` CLI option](../use/command-line-interface#--ext) to see if the current block should be linted and resolves `overrides` configs to check how to process the current block. -It's up to the plugin to decide if it needs to return just one part, or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts, but for `.md` file where each JavaScript block might be independent, you can return multiple items. +It's up to the plugin to decide if it needs to return just one part of the non-JavaScript file or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts. However, for `.md` files, you can return multiple items because each JavaScript block might be independent. **The `postprocess` method** takes a two-dimensional array of arrays of lint messages and the filename. Each item in the input array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the locations of all errors to correspond to locations in the original, unprocessed code, and aggregate them into a single flat array and return it. -Reported problems have the following location information: +Reported problems have the following location information in each lint message: ```typescript -{ - line: number, - column: number, +type LintMessage = { + + /// The 1-based line number where the message occurs. + line: number; + + /// The 1-based column number where the message occurs. + column: number; + + /// The 1-based line number of the end location. + endLine: number; + + /// The 1-based column number of the end location. + endColumn: number; + + /// If `true`, this is a fatal error. + fatal: boolean; + + /// Information for an autofix. + fix: Fix; + + /// The error message. + message: string; + + /// The ID of the rule which generated the message, or `null` if not applicable. + ruleId: string | null; + + /// The severity of the message. + severity: 0 | 1 | 2; + + /// Information for suggestions. + suggestions?: Suggestion[]; +}; + +type Fix = { + range: [number, number]; + text: string; +} - endLine?: number, - endColumn?: number +type Suggestion = { + desc?: string; + messageId?: string; + fix: Fix; } + ``` -By default, ESLint will not perform autofixes when a processor is used, even when the `--fix` flag is enabled on the command line. To allow ESLint to autofix code when using your processor, you should take the following additional steps: +By default, ESLint does not perform autofixes when a custom processor is used, even when the `--fix` flag is enabled on the command line. To allow ESLint to autofix code when using your processor, you should take the following additional steps: -1. Update the `postprocess` method to additionally transform the `fix` property of reported problems. All autofixable problems will have a `fix` property, which is an object with the following schema: +1. Update the `postprocess` method to additionally transform the `fix` property of reported problems. All autofixable problems have a `fix` property, which is an object with the following schema: - ```js + ```typescript { range: [number, number], text: string @@ -81,8 +119,7 @@ By default, ESLint will not perform autofixes when a processor is used, even whe 2. Add a `supportsAutofix: true` property to the processor. -You can have both rules and processors in a single plugin. You can also have multiple processors in one plugin. -To support multiple extensions, add each one to the `processors` element and point them to the same object. +You can have both rules and custom processors in a single plugin. You can also have multiple processors in one plugin. To support multiple extensions, add each one to the `processors` element and point them to the same object. ## Specifying Processor in Config Files @@ -102,7 +139,7 @@ See [Specify a Processor](../use/configure/plugins#specify-a-processor) in the P ## File Extension-named Processor -If a processor name starts with `.`, ESLint handles the processor as a **file extension-named processor** especially and applies the processor to the kind of files automatically. People don't need to specify the file extension-named processors in their config files. +If a custom processor name starts with `.`, ESLint handles the processor as a **file extension-named processor**. ESLint applies the processor to files with that filename extension automatically. Users don't need to specify the file extension-named processors in their config files. For example: @@ -110,11 +147,33 @@ For example: module.exports = { processors: { // This processor will be applied to `*.md` files automatically. - // Also, people can use this processor as "plugin-id/.md" explicitly. + // Also, you can use this processor as "plugin-id/.md" explicitly. ".md": { preprocess(text, filename) { /* ... */ }, postprocess(messageLists, filename) { /* ... */ } } + // This processor will not be applied to any files automatically. + // To use this processor, you must explicitly specify it + // in your configuration as "plugin-id/markdown". + "markdown": { + preprocess(text, filename) { /* ... */ }, + postprocess(messageLists, filename) { /* ... */ } + } + } +} +``` + +You can also use the same custom processor with multiple filename extensions. The following example shows using the same processor for both `.md` and `.mdx` files: + +```js +const myCustomProcessor = { /* processor methods */ }; + +module.exports = { + // The same custom processor is applied to both + // `.md` and `.mdx` files. + processors: { + ".md": myCustomProcessor, + ".mdx": myCustomProcessor } } ``` From b3634f695ddab6a82c0a9b1d8695e62b60d23366 Mon Sep 17 00:00:00 2001 From: Samuel Roldan Date: Thu, 23 Mar 2023 14:20:14 -0500 Subject: [PATCH 27/48] feat: docs license (#17010) --- docs/README.md | 4 ++++ docs/src/_data/sites/en.yml | 2 +- docs/src/_data/sites/zh-hans.yml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 140608aa2d2..dbf3c02e8f4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -39,3 +39,7 @@ To autofix JS files, run this from the root folder (not the `docs` folder): ```shell npm run fix:docsjs ``` + +## License + +© OpenJS Foundation and ESLint contributors, [www.openjsf.org](https://www.openjsf.org/). Content licensed under [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/). diff --git a/docs/src/_data/sites/en.yml b/docs/src/_data/sites/en.yml index dc6bbc5ca49..7c7de804bde 100644 --- a/docs/src/_data/sites/en.yml +++ b/docs/src/_data/sites/en.yml @@ -86,7 +86,7 @@ footer: change_language: Change Language language: Language copyright: > - © OpenJS Foundation and ESLint contributors, www.openjsf.org + © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. links: open_jsf: The OpenJS Foundation terms: Terms of Use diff --git a/docs/src/_data/sites/zh-hans.yml b/docs/src/_data/sites/zh-hans.yml index 531485e5338..0bff9291937 100644 --- a/docs/src/_data/sites/zh-hans.yml +++ b/docs/src/_data/sites/zh-hans.yml @@ -84,7 +84,7 @@ footer: change_language: 更改语言 language: 语言 copyright: > - © OpenJS Foundation and ESLint contributors, www.openjsf.org + © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. links: open_jsf: OpenJS 基金会 terms: 使用条款 From 1665c029acb92bf8812267f1647ad1a7054cbcb4 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Thu, 23 Mar 2023 12:47:51 -0700 Subject: [PATCH 28/48] feat: Use plugin metadata for flat config serialization (#16992) * feat: Use plugin metadata for flat config serialization Updates `FlatConfigArray` to look for meta information on plugins when serializing a config. Fixes #16284 * Update docs/src/extend/plugins.md Co-authored-by: Nitin Kumar * Rebase --------- Co-authored-by: Nitin Kumar --- docs/src/extend/plugins.md | 28 ++++++++++++ lib/config/flat-config-array.js | 11 ++++- tests/lib/config/flat-config-array.js | 66 +++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/docs/src/extend/plugins.md b/docs/src/extend/plugins.md index f180c034faa..9d7983e67f2 100644 --- a/docs/src/extend/plugins.md +++ b/docs/src/extend/plugins.md @@ -18,6 +18,34 @@ Each plugin is an npm module with a name in the format of `eslint-plugin- { + + const pluginId = getObjectId(plugin); + + if (!pluginId) { + return namespace; + } + + return `${namespace}:${pluginId}`; + }), languageOptions: { ...languageOptions, parser: parserName diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index 2b8e8f64ec3..7ca78a388f2 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -221,6 +221,72 @@ describe("FlatConfigArray", () => { assert.strictEqual(stringify(actual), stringify(expected)); }); + it("should convert config with plugin name/version into normalized JSON object", () => { + + const configs = new FlatConfigArray([{ + plugins: { + a: {}, + b: { + name: "b-plugin", + version: "2.3.1" + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + const expected = { + plugins: ["@", "a", "b:b-plugin@2.3.1"], + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + parser: `espree@${espree.version}`, + parserOptions: {} + }, + processor: void 0 + }; + const actual = config.toJSON(); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(stringify(actual), stringify(expected)); + }); + + it("should convert config with plugin meta into normalized JSON object", () => { + + const configs = new FlatConfigArray([{ + plugins: { + a: {}, + b: { + meta: { + name: "b-plugin", + version: "2.3.1" + } + } + } + }]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + const expected = { + plugins: ["@", "a", "b:b-plugin@2.3.1"], + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + parser: `espree@${espree.version}`, + parserOptions: {} + }, + processor: void 0 + }; + const actual = config.toJSON(); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(stringify(actual), stringify(expected)); + }); + it("should throw an error when config with unnamed parser object is normalized", () => { const configs = new FlatConfigArray([{ From 10022b1f4bda1ad89193512ecf18c2ee61db8202 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 24 Mar 2023 06:22:04 -0700 Subject: [PATCH 29/48] feat: Copy getScope() to SourceCode (#17004) * feat: Copy getScope() to SourceCode Refs #16999 * Fix no-obj-calls * Add getScope() tests * Throw error if argument is missing * Update docs * Add caching * Clean up caching --- docs/src/extend/custom-rules.md | 102 +++---- lib/linter/linter.js | 35 +-- lib/rules/camelcase.js | 5 +- lib/rules/consistent-this.js | 6 +- lib/rules/global-require.js | 4 +- lib/rules/handle-callback-err.js | 3 +- lib/rules/id-blacklist.js | 5 +- lib/rules/id-denylist.js | 5 +- lib/rules/id-match.js | 5 +- lib/rules/logical-assignment-operators.js | 4 +- lib/rules/no-alert.js | 4 +- lib/rules/no-catch-shadow.js | 4 +- lib/rules/no-console.js | 5 +- lib/rules/no-constant-binary-expression.js | 6 +- lib/rules/no-constant-condition.js | 5 +- lib/rules/no-else-return.js | 25 +- lib/rules/no-eval.js | 8 +- lib/rules/no-extend-native.js | 5 +- lib/rules/no-global-assign.js | 5 +- lib/rules/no-implicit-globals.js | 5 +- lib/rules/no-implied-eval.js | 7 +- lib/rules/no-import-assign.js | 4 +- lib/rules/no-invalid-this.js | 4 +- lib/rules/no-label-var.js | 3 +- lib/rules/no-lone-blocks.js | 5 +- lib/rules/no-loop-func.js | 4 +- lib/rules/no-misleading-character-class.js | 14 +- lib/rules/no-native-reassign.js | 5 +- lib/rules/no-new-func.js | 13 +- lib/rules/no-new-native-nonconstructor.js | 14 +- lib/rules/no-new-object.js | 5 +- lib/rules/no-new-symbol.js | 14 +- lib/rules/no-obj-calls.js | 12 +- lib/rules/no-promise-executor-return.js | 3 +- lib/rules/no-redeclare.js | 6 +- lib/rules/no-regex-spaces.js | 4 +- lib/rules/no-restricted-globals.js | 6 +- lib/rules/no-setter-return.js | 3 +- lib/rules/no-shadow.js | 5 +- lib/rules/no-undef-init.js | 2 +- lib/rules/no-undef.js | 5 +- lib/rules/no-undefined.js | 6 +- lib/rules/no-unmodified-loop-condition.js | 4 +- lib/rules/no-unused-vars.js | 2 +- lib/rules/no-use-before-define.js | 5 +- lib/rules/no-useless-backreference.js | 12 +- lib/rules/object-shorthand.js | 5 +- lib/rules/prefer-arrow-callback.js | 2 +- lib/rules/prefer-exponentiation-operator.js | 8 +- lib/rules/prefer-named-capture-group.js | 12 +- lib/rules/prefer-object-has-own.js | 6 +- lib/rules/prefer-object-spread.js | 22 +- lib/rules/prefer-regex-literals.js | 46 +-- lib/rules/prefer-rest-params.js | 7 +- lib/rules/radix.js | 22 +- lib/rules/require-atomic-updates.js | 4 +- lib/rules/require-unicode-regexp.js | 13 +- lib/rules/symbol-description.js | 12 +- lib/rules/valid-typeof.js | 6 +- lib/source-code/source-code.js | 51 ++++ .../testers/rule-tester/no-test-global.js | 7 +- tests/lib/source-code/source-code.js | 274 +++++++++++++++++- 62 files changed, 638 insertions(+), 272 deletions(-) diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index 0118757c6a4..11ccc02563a 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -131,62 +131,13 @@ Additionally, the `context` object has the following methods: * Otherwise, if the node does not declare any variables, an empty array is returned. * `getFilename()` - returns the filename associated with the source. * `getPhysicalFilename()` - when linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `—stdin-filename` or `` if not specified. -* `getScope()` - returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. +* `getScope()` - (**Deprecated: Use `SourceCode.getScope(node)` instead.**) returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. * `getSourceCode()` - returns a [`SourceCode`](#contextgetsourcecode) object that you can use to work with the source that was passed to ESLint. * `markVariableAsUsed(name)` - marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. * `report(descriptor)` - reports a problem in the code (see the [dedicated section](#contextreport)). **Note:** Earlier versions of ESLint supported additional methods on the `context` object. Those methods were removed in the new format and should not be relied upon. -### context.getScope() - -This method returns the scope of the current node. It is a useful method for finding information about the variables in a given scope, and how they are used in other scopes. - -#### Scope types - -The following table contains a list of AST node types and the scope type that they correspond to. For more information about the scope types, refer to the [`Scope` object documentation](./scope-manager-interface#scope-interface). - -| AST Node Type | Scope Type | -|:--------------------------|:-----------| -| `Program` | `global` | -| `FunctionDeclaration` | `function` | -| `FunctionExpression` | `function` | -| `ArrowFunctionExpression` | `function` | -| `ClassDeclaration` | `class` | -| `ClassExpression` | `class` | -| `BlockStatement` ※1 | `block` | -| `SwitchStatement` ※1 | `switch` | -| `ForStatement` ※2 | `for` | -| `ForInStatement` ※2 | `for` | -| `ForOfStatement` ※2 | `for` | -| `WithStatement` | `with` | -| `CatchClause` | `catch` | -| others | ※3 | - -**※1** Only if the configured parser provided the block-scope feature. The default parser provides the block-scope feature if `parserOptions.ecmaVersion` is not less than `6`.
-**※2** Only if the `for` statement defines the iteration variable as a block-scoped variable (E.g., `for (let i = 0;;) {}`).
-**※3** The scope of the closest ancestor node which has own scope. If the closest ancestor node has multiple scopes then it chooses the innermost scope (E.g., the `Program` node has a `global` scope and a `module` scope if `Program#sourceType` is `"module"`. The innermost scope is the `module` scope.). - -#### Scope Variables - -The `Scope#variables` property contains an array of [`Variable` objects](./scope-manager-interface#variable-interface). These are the variables declared in current scope. You can use these `Variable` objects to track references to a variable throughout the entire module. - -Inside of each `Variable`, the `Variable#references` property contains an array of [`Reference` objects](./scope-manager-interface#reference-interface). The `Reference` array contains all the locations where the variable is referenced in the module's source code. - -Also inside of each `Variable`, the `Variable#defs` property contains an array of [`Definition` objects](./scope-manager-interface#definition-interface). You can use the `Definitions` to find where the variable was defined. - -Global variables have the following additional properties: - -* `Variable#writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only. -* `Variable#eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file. -* `Variable#eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments. -* `Variable#eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments. - -For examples of using `context.getScope()` to track variables, refer to the source code for the following built-in rules: - -* [no-shadow](https://github.com/eslint/eslint/blob/main/lib/rules/no-shadow.js): Calls `context.getScopes()` at the global scope and parses all child scopes to make sure a variable name is not reused at a lower scope. ([no-shadow](../rules/no-shadow) documentation) -* [no-redeclare](https://github.com/eslint/eslint/blob/main/lib/rules/no-redeclare.js): Calls `context.getScope()` at each scope to make sure that a variable is not declared twice at that scope. ([no-redeclare](../rules/no-redeclare) documentation) - ### context.report() The main method you'll use is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties: @@ -684,6 +635,57 @@ Finally, comments can be accessed through many of `sourceCode`'s methods using t Shebangs are represented by tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined above. +### Accessing Variable Scopes + +The `SourceCode#getScope(node)` method returns the scope of the given node. It is a useful method for finding information about the variables in a given scope and how they are used in other scopes. + +**Deprecated:** The `context.getScope()` is deprecated; make sure to use `SourceCode#getScope(node)` instead. + +#### Scope types + +The following table contains a list of AST node types and the scope type that they correspond to. For more information about the scope types, refer to the [`Scope` object documentation](./scope-manager-interface#scope-interface). + +| AST Node Type | Scope Type | +|:--------------------------|:-----------| +| `Program` | `global` | +| `FunctionDeclaration` | `function` | +| `FunctionExpression` | `function` | +| `ArrowFunctionExpression` | `function` | +| `ClassDeclaration` | `class` | +| `ClassExpression` | `class` | +| `BlockStatement` ※1 | `block` | +| `SwitchStatement` ※1 | `switch` | +| `ForStatement` ※2 | `for` | +| `ForInStatement` ※2 | `for` | +| `ForOfStatement` ※2 | `for` | +| `WithStatement` | `with` | +| `CatchClause` | `catch` | +| others | ※3 | + +**※1** Only if the configured parser provided the block-scope feature. The default parser provides the block-scope feature if `parserOptions.ecmaVersion` is not less than `6`.
+**※2** Only if the `for` statement defines the iteration variable as a block-scoped variable (E.g., `for (let i = 0;;) {}`).
+**※3** The scope of the closest ancestor node which has own scope. If the closest ancestor node has multiple scopes then it chooses the innermost scope (E.g., the `Program` node has a `global` scope and a `module` scope if `Program#sourceType` is `"module"`. The innermost scope is the `module` scope.). + +#### Scope Variables + +The `Scope#variables` property contains an array of [`Variable` objects](./scope-manager-interface#variable-interface). These are the variables declared in current scope. You can use these `Variable` objects to track references to a variable throughout the entire module. + +Inside of each `Variable`, the `Variable#references` property contains an array of [`Reference` objects](./scope-manager-interface#reference-interface). The `Reference` array contains all the locations where the variable is referenced in the module's source code. + +Also inside of each `Variable`, the `Variable#defs` property contains an array of [`Definition` objects](./scope-manager-interface#definition-interface). You can use the `Definitions` to find where the variable was defined. + +Global variables have the following additional properties: + +* `Variable#writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only. +* `Variable#eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file. +* `Variable#eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments. +* `Variable#eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments. + +For examples of using `SourceCode#getScope()` to track variables, refer to the source code for the following built-in rules: + +* [no-shadow](https://github.com/eslint/eslint/blob/main/lib/rules/no-shadow.js): Calls `sourceCode.getScope()` at the `Program` node and inspects all child scopes to make sure a variable name is not reused at a lower scope. ([no-shadow](../rules/no-shadow) documentation) +* [no-redeclare](https://github.com/eslint/eslint/blob/main/lib/rules/no-redeclare.js): Calls `sourceCode.getScope()` at each scope to make sure that a variable is not declared twice in the same scope. ([no-redeclare](../rules/no-redeclare) documentation) + ### Accessing Code Paths ESLint analyzes code paths while traversing AST. diff --git a/lib/linter/linter.js b/lib/linter/linter.js index 0f1bd4f7761..3b099004d2b 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -857,47 +857,22 @@ function parse(text, languageOptions, filePath) { } } -/** - * Gets the scope for the current node - * @param {ScopeManager} scopeManager The scope manager for this AST - * @param {ASTNode} currentNode The node to get the scope of - * @returns {eslint-scope.Scope} The scope information for this node - */ -function getScope(scopeManager, currentNode) { - - // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope. - const inner = currentNode.type !== "Program"; - - for (let node = currentNode; node; node = node.parent) { - const scope = scopeManager.acquire(node, inner); - - if (scope) { - if (scope.type === "function-expression-name") { - return scope.childScopes[0]; - } - return scope; - } - } - - return scopeManager.scopes[0]; -} - /** * Marks a variable as used in the current scope - * @param {ScopeManager} scopeManager The scope manager for this AST. The scope may be mutated by this function. + * @param {SourceCode} sourceCode The source code for the currently linted file. * @param {ASTNode} currentNode The node currently being traversed * @param {LanguageOptions} languageOptions The options used to parse this text * @param {string} name The name of the variable that should be marked as used. * @returns {boolean} True if the variable was found and marked as used, false if not. */ -function markVariableAsUsed(scopeManager, currentNode, languageOptions, name) { +function markVariableAsUsed(sourceCode, currentNode, languageOptions, name) { const parserOptions = languageOptions.parserOptions; const sourceType = languageOptions.sourceType; const hasGlobalReturn = (parserOptions.ecmaFeatures && parserOptions.ecmaFeatures.globalReturn) || sourceType === "commonjs"; const specialScope = hasGlobalReturn || sourceType === "module"; - const currentScope = getScope(scopeManager, currentNode); + const currentScope = sourceCode.getScope(currentNode); // Special Node.js scope means we need to start one level deeper const initialScope = currentScope.type === "global" && specialScope ? currentScope.childScopes[0] : currentScope; @@ -1026,9 +1001,9 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO getCwd: () => cwd, getFilename: () => filename, getPhysicalFilename: () => physicalFilename || filename, - getScope: () => getScope(sourceCode.scopeManager, currentNode), + getScope: () => sourceCode.getScope(currentNode), getSourceCode: () => sourceCode, - markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, currentNode, languageOptions, name), + markVariableAsUsed: name => markVariableAsUsed(sourceCode, currentNode, languageOptions, name), parserOptions: { ...languageOptions.parserOptions }, diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js index ee1b6bf598d..910e8b6e583 100644 --- a/lib/rules/camelcase.js +++ b/lib/rules/camelcase.js @@ -73,6 +73,7 @@ module.exports = { const ignoreImports = options.ignoreImports; const ignoreGlobals = options.ignoreGlobals; const allow = options.allow || []; + const sourceCode = context.getSourceCode(); //-------------------------------------------------------------------------- // Helpers @@ -245,8 +246,8 @@ module.exports = { return { // Report camelcase of global variable references ------------------ - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); if (!ignoreGlobals) { diff --git a/lib/rules/consistent-this.js b/lib/rules/consistent-this.js index 947873b8e4a..b1fbd0ebedf 100644 --- a/lib/rules/consistent-this.js +++ b/lib/rules/consistent-this.js @@ -36,6 +36,7 @@ module.exports = { create(context) { let aliases = []; + const sourceCode = context.getSourceCode(); if (context.options.length === 0) { aliases.push("that"); @@ -115,10 +116,11 @@ module.exports = { /** * Check each alias to ensure that is was assigned to the correct value. + * @param {ASTNode} node The node that represents the scope to check. * @returns {void} */ - function ensureWasAssigned() { - const scope = context.getScope(); + function ensureWasAssigned(node) { + const scope = sourceCode.getScope(node); aliases.forEach(alias => { checkWasAssigned(alias, scope); diff --git a/lib/rules/global-require.js b/lib/rules/global-require.js index ceb0a8e8415..c8a5fc309a1 100644 --- a/lib/rules/global-require.js +++ b/lib/rules/global-require.js @@ -71,9 +71,11 @@ module.exports = { }, create(context) { + const sourceCode = context.getSourceCode(); + return { CallExpression(node) { - const currentScope = context.getScope(); + const currentScope = sourceCode.getScope(node); if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) { const isGoodRequire = context.getAncestors().every(parent => ACCEPTABLE_PARENTS.has(parent.type)); diff --git a/lib/rules/handle-callback-err.js b/lib/rules/handle-callback-err.js index 5189564b668..300dbb0dee2 100644 --- a/lib/rules/handle-callback-err.js +++ b/lib/rules/handle-callback-err.js @@ -38,6 +38,7 @@ module.exports = { create(context) { const errorArgument = context.options[0] || "err"; + const sourceCode = context.getSourceCode(); /** * Checks if the given argument should be interpreted as a regexp pattern. @@ -79,7 +80,7 @@ module.exports = { * @returns {void} */ function checkForError(node) { - const scope = context.getScope(), + const scope = sourceCode.getScope(node), parameters = getParameters(scope), firstParameter = parameters[0]; diff --git a/lib/rules/id-blacklist.js b/lib/rules/id-blacklist.js index 5ea61e94f69..9d1efac3787 100644 --- a/lib/rules/id-blacklist.js +++ b/lib/rules/id-blacklist.js @@ -140,6 +140,7 @@ module.exports = { const denyList = new Set(context.options); const reportedNodes = new Set(); + const sourceCode = context.getSourceCode(); let globalScope; @@ -231,8 +232,8 @@ module.exports = { return { - Program() { - globalScope = context.getScope(); + Program(node) { + globalScope = sourceCode.getScope(node); }, Identifier(node) { diff --git a/lib/rules/id-denylist.js b/lib/rules/id-denylist.js index fe0a0b50bd2..0d9328137b2 100644 --- a/lib/rules/id-denylist.js +++ b/lib/rules/id-denylist.js @@ -121,6 +121,7 @@ module.exports = { const denyList = new Set(context.options); const reportedNodes = new Set(); + const sourceCode = context.getSourceCode(); let globalScope; @@ -210,8 +211,8 @@ module.exports = { return { - Program() { - globalScope = context.getScope(); + Program(node) { + globalScope = sourceCode.getScope(node); }, [[ diff --git a/lib/rules/id-match.js b/lib/rules/id-match.js index ec87af18d5b..73a3bd2f97e 100644 --- a/lib/rules/id-match.js +++ b/lib/rules/id-match.js @@ -67,6 +67,7 @@ module.exports = { onlyDeclarations = !!options.onlyDeclarations, ignoreDestructuring = !!options.ignoreDestructuring; + const sourceCode = context.getSourceCode(); let globalScope; //-------------------------------------------------------------------------- @@ -170,8 +171,8 @@ module.exports = { return { - Program() { - globalScope = context.getScope(); + Program(node) { + globalScope = sourceCode.getScope(node); }, Identifier(node) { diff --git a/lib/rules/logical-assignment-operators.js b/lib/rules/logical-assignment-operators.js index 701b3114a9f..f1c0119d99f 100644 --- a/lib/rules/logical-assignment-operators.js +++ b/lib/rules/logical-assignment-operators.js @@ -206,7 +206,7 @@ module.exports = { const mode = context.options[0] === "never" ? "never" : "always"; const checkIf = mode === "always" && context.options.length > 1 && context.options[1].enforceForIfStatements; const sourceCode = context.getSourceCode(); - const isStrict = context.getScope().isStrict; + const isStrict = sourceCode.getScope(sourceCode.ast).isStrict; /** * Returns false if the access could be a getter @@ -409,7 +409,7 @@ module.exports = { } const body = hasBody ? ifNode.consequent.body[0] : ifNode.consequent; - const scope = context.getScope(); + const scope = sourceCode.getScope(ifNode); const existence = getExistence(ifNode.test, scope); if ( diff --git a/lib/rules/no-alert.js b/lib/rules/no-alert.js index ba0125c877b..8af188971c5 100644 --- a/lib/rules/no-alert.js +++ b/lib/rules/no-alert.js @@ -101,10 +101,12 @@ module.exports = { }, create(context) { + const sourceCode = context.getSourceCode(); + return { CallExpression(node) { const callee = skipChainExpression(node.callee), - currentScope = context.getScope(); + currentScope = sourceCode.getScope(node); // without window. if (callee.type === "Identifier") { diff --git a/lib/rules/no-catch-shadow.js b/lib/rules/no-catch-shadow.js index 49f1ba9649b..5e8b51e092d 100644 --- a/lib/rules/no-catch-shadow.js +++ b/lib/rules/no-catch-shadow.js @@ -39,6 +39,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- @@ -60,7 +62,7 @@ module.exports = { return { "CatchClause[param!=null]"(node) { - let scope = context.getScope(); + let scope = sourceCode.getScope(node); /* * When ecmaVersion >= 6, CatchClause creates its own scope diff --git a/lib/rules/no-console.js b/lib/rules/no-console.js index bad6b6f4ee8..4651282214d 100644 --- a/lib/rules/no-console.js +++ b/lib/rules/no-console.js @@ -51,6 +51,7 @@ module.exports = { create(context) { const options = context.options[0] || {}; const allowed = options.allow || []; + const sourceCode = context.getSourceCode(); /** * Checks whether the given reference is 'console' or not. @@ -109,8 +110,8 @@ module.exports = { } return { - "Program:exit"() { - const scope = context.getScope(); + "Program:exit"(node) { + const scope = sourceCode.getScope(node); const consoleVar = astUtils.getVariableByName(scope, "console"); const shadowed = consoleVar && consoleVar.defs.length > 0; diff --git a/lib/rules/no-constant-binary-expression.js b/lib/rules/no-constant-binary-expression.js index 2cd8928ebfb..4b2337b4771 100644 --- a/lib/rules/no-constant-binary-expression.js +++ b/lib/rules/no-constant-binary-expression.js @@ -453,10 +453,12 @@ module.exports = { }, create(context) { + const sourceCode = context.getSourceCode(); + return { LogicalExpression(node) { const { operator, left } = node; - const scope = context.getScope(); + const scope = sourceCode.getScope(node); if ((operator === "&&" || operator === "||") && isConstant(scope, left, true)) { context.report({ node: left, messageId: "constantShortCircuit", data: { property: "truthiness", operator } }); @@ -465,7 +467,7 @@ module.exports = { } }, BinaryExpression(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const { right, left, operator } = node; const rightConstantOperand = findBinaryExpressionConstantOperand(scope, left, right, operator); const leftConstantOperand = findBinaryExpressionConstantOperand(scope, right, left, operator); diff --git a/lib/rules/no-constant-condition.js b/lib/rules/no-constant-condition.js index 2ef687f6dca..de548472b7d 100644 --- a/lib/rules/no-constant-condition.js +++ b/lib/rules/no-constant-condition.js @@ -48,6 +48,7 @@ module.exports = { const options = context.options[0] || {}, checkLoops = options.checkLoops !== false, loopSetStack = []; + const sourceCode = context.getSourceCode(); let loopsInCurrentScope = new Set(); @@ -62,7 +63,7 @@ module.exports = { * @private */ function trackConstantConditionLoop(node) { - if (node.test && isConstant(context.getScope(), node.test, true)) { + if (node.test && isConstant(sourceCode.getScope(node), node.test, true)) { loopsInCurrentScope.add(node); } } @@ -87,7 +88,7 @@ module.exports = { * @private */ function reportIfConstant(node) { - if (node.test && isConstant(context.getScope(), node.test, true)) { + if (node.test && isConstant(sourceCode.getScope(node), node.test, true)) { context.report({ node: node.test, messageId: "unexpected" }); } } diff --git a/lib/rules/no-else-return.js b/lib/rules/no-else-return.js index f3ceedb4cd7..56234d54d3b 100644 --- a/lib/rules/no-else-return.js +++ b/lib/rules/no-else-return.js @@ -47,6 +47,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- @@ -169,25 +171,24 @@ module.exports = { /** * Display the context report if rule is violated - * @param {Node} node The 'else' node + * @param {Node} elseNode The 'else' node * @returns {void} */ - function displayReport(node) { - const currentScope = context.getScope(); + function displayReport(elseNode) { + const currentScope = sourceCode.getScope(elseNode.parent); context.report({ - node, + node: elseNode, messageId: "unexpected", fix(fixer) { - if (!isSafeFromNameCollisions(node, currentScope)) { + if (!isSafeFromNameCollisions(elseNode, currentScope)) { return null; } - const sourceCode = context.getSourceCode(); - const startToken = sourceCode.getFirstToken(node); + const startToken = sourceCode.getFirstToken(elseNode); const elseToken = sourceCode.getTokenBefore(startToken); - const source = sourceCode.getText(node); + const source = sourceCode.getText(elseNode); const lastIfToken = sourceCode.getTokenBefore(elseToken); let fixedSource, firstTokenOfElseBlock; @@ -203,14 +204,14 @@ module.exports = { * safe to remove the else keyword, because ASI will not add a semicolon * after the if block */ - const ifBlockMaybeUnsafe = node.parent.consequent.type !== "BlockStatement" && lastIfToken.value !== ";"; + const ifBlockMaybeUnsafe = elseNode.parent.consequent.type !== "BlockStatement" && lastIfToken.value !== ";"; const elseBlockUnsafe = /^[([/+`-]/u.test(firstTokenOfElseBlock.value); if (ifBlockMaybeUnsafe && elseBlockUnsafe) { return null; } - const endToken = sourceCode.getLastToken(node); + const endToken = sourceCode.getLastToken(elseNode); const lastTokenOfElseBlock = sourceCode.getTokenBefore(endToken); if (lastTokenOfElseBlock.value !== ";") { @@ -244,8 +245,8 @@ module.exports = { * Also, to avoid name collisions between two else blocks. */ return new FixTracker(fixer, sourceCode) - .retainEnclosingFunction(node) - .replaceTextRange([elseToken.range[0], node.range[1]], fixedSource); + .retainEnclosingFunction(elseNode) + .replaceTextRange([elseToken.range[0], elseNode.range[1]], fixedSource); } }); } diff --git a/lib/rules/no-eval.js b/lib/rules/no-eval.js index a1b32cc307b..76d6859ab5d 100644 --- a/lib/rules/no-eval.js +++ b/lib/rules/no-eval.js @@ -84,7 +84,7 @@ module.exports = { * @returns {void} */ function enterThisScope(node) { - const strict = context.getScope().isStrict; + const strict = sourceCode.getScope(node).isStrict; funcInfo = { upper: funcInfo, @@ -221,7 +221,7 @@ module.exports = { }, Program(node) { - const scope = context.getScope(), + const scope = sourceCode.getScope(node), features = context.parserOptions.ecmaFeatures || {}, strict = scope.isStrict || @@ -239,8 +239,8 @@ module.exports = { }; }, - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); exitThisScope(); reportAccessingEval(globalScope); diff --git a/lib/rules/no-extend-native.js b/lib/rules/no-extend-native.js index 52c6bd31103..b1965964394 100644 --- a/lib/rules/no-extend-native.js +++ b/lib/rules/no-extend-native.js @@ -51,6 +51,7 @@ module.exports = { create(context) { const config = context.options[0] || {}; + const sourceCode = context.getSourceCode(); const exceptions = new Set(config.exceptions || []); const modifiedBuiltins = new Set( Object.keys(globals.builtin) @@ -159,8 +160,8 @@ module.exports = { return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); modifiedBuiltins.forEach(builtin => { const builtinVar = globalScope.set.get(builtin); diff --git a/lib/rules/no-global-assign.js b/lib/rules/no-global-assign.js index 9f2f0ee3642..4659dcc94c1 100644 --- a/lib/rules/no-global-assign.js +++ b/lib/rules/no-global-assign.js @@ -41,6 +41,7 @@ module.exports = { create(context) { const config = context.options[0]; + const sourceCode = context.getSourceCode(); const exceptions = (config && config.exceptions) || []; /** @@ -84,8 +85,8 @@ module.exports = { } return { - Program() { - const globalScope = context.getScope(); + Program(node) { + const globalScope = sourceCode.getScope(node); globalScope.variables.forEach(checkVariable); } diff --git a/lib/rules/no-implicit-globals.js b/lib/rules/no-implicit-globals.js index c2cdd03f2ce..de9c4c274d4 100644 --- a/lib/rules/no-implicit-globals.js +++ b/lib/rules/no-implicit-globals.js @@ -43,6 +43,7 @@ module.exports = { create(context) { const checkLexicalBindings = context.options[0] && context.options[0].lexicalBindings === true; + const sourceCode = context.getSourceCode(); /** * Reports the node. @@ -62,8 +63,8 @@ module.exports = { } return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); scope.variables.forEach(variable => { diff --git a/lib/rules/no-implied-eval.js b/lib/rules/no-implied-eval.js index c33b05d465d..c99af2d3968 100644 --- a/lib/rules/no-implied-eval.js +++ b/lib/rules/no-implied-eval.js @@ -37,6 +37,7 @@ module.exports = { create(context) { const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]); const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u; + const sourceCode = context.getSourceCode(); /** * Checks whether a node is evaluated as a string or not. @@ -66,7 +67,7 @@ module.exports = { if (firstArgument) { - const staticValue = getStaticValue(firstArgument, context.getScope()); + const staticValue = getStaticValue(firstArgument, sourceCode.getScope(node)); const isStaticString = staticValue && typeof staticValue.value === "string"; const isString = isStaticString || isEvaluatedString(firstArgument); @@ -117,8 +118,8 @@ module.exports = { reportImpliedEvalCallExpression(node); } }, - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); GLOBAL_CANDIDATES .map(candidate => astUtils.getVariableByName(globalScope, candidate)) diff --git a/lib/rules/no-import-assign.js b/lib/rules/no-import-assign.js index 6cf2e519868..dcfebaf631b 100644 --- a/lib/rules/no-import-assign.js +++ b/lib/rules/no-import-assign.js @@ -194,9 +194,11 @@ module.exports = { }, create(context) { + const sourceCode = context.getSourceCode(); + return { ImportDeclaration(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); for (const variable of context.getDeclaredVariables(node)) { const shouldCheckMembers = variable.defs.some( diff --git a/lib/rules/no-invalid-this.js b/lib/rules/no-invalid-this.js index b9cb43af5d7..1048f22861a 100644 --- a/lib/rules/no-invalid-this.js +++ b/lib/rules/no-invalid-this.js @@ -95,7 +95,7 @@ module.exports = { } if (codePath.origin === "program") { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const features = context.parserOptions.ecmaFeatures || {}; // `this` at the top level of scripts always refers to the global object @@ -120,7 +120,7 @@ module.exports = { * always valid, so we can set `init: true` right away. */ stack.push({ - init: !context.getScope().isStrict, + init: !sourceCode.getScope(node).isStrict, node, valid: true }); diff --git a/lib/rules/no-label-var.js b/lib/rules/no-label-var.js index a07d283f522..440d09d149d 100644 --- a/lib/rules/no-label-var.js +++ b/lib/rules/no-label-var.js @@ -34,6 +34,7 @@ module.exports = { }, create(context) { + const sourceCode = context.getSourceCode(); //-------------------------------------------------------------------------- // Helpers @@ -59,7 +60,7 @@ module.exports = { LabeledStatement(node) { // Fetch the innermost scope. - const scope = context.getScope(); + const scope = sourceCode.getScope(node); /* * Recursively find the identifier walking up the scope, starting diff --git a/lib/rules/no-lone-blocks.js b/lib/rules/no-lone-blocks.js index eb97f958c3c..23f581d3fc6 100644 --- a/lib/rules/no-lone-blocks.js +++ b/lib/rules/no-lone-blocks.js @@ -33,6 +33,7 @@ module.exports = { // A stack of lone blocks to be checked for block-level bindings const loneBlocks = []; let ruleDef; + const sourceCode = context.getSourceCode(); /** * Reports a node as invalid. @@ -120,8 +121,8 @@ module.exports = { } }; - ruleDef.FunctionDeclaration = function() { - if (context.getScope().isStrict) { + ruleDef.FunctionDeclaration = function(node) { + if (sourceCode.getScope(node).isStrict) { markLoneBlock(); } }; diff --git a/lib/rules/no-loop-func.js b/lib/rules/no-loop-func.js index f81a7133680..ff066817226 100644 --- a/lib/rules/no-loop-func.js +++ b/lib/rules/no-loop-func.js @@ -168,6 +168,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Reports functions which match the following condition: * @@ -183,7 +185,7 @@ module.exports = { return; } - const references = context.getScope().through; + const references = sourceCode.getScope(node).through; const unsafeRefs = references.filter(r => !isSafe(loopNode, r)).map(r => r.identifier.name); if (unsafeRefs.length > 0) { diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index 9aa7079e538..ef6e1418002 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -223,8 +223,8 @@ module.exports = { return fixer.insertTextAfter(node, "u"); }); }, - "Program"() { - const scope = context.getScope(); + "Program"(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); /* @@ -232,22 +232,22 @@ module.exports = { * E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`, * `const {RegExp: a} = window; new a()`, etc... */ - for (const { node } of tracker.iterateGlobalReferences({ + for (const { node: refNode } of tracker.iterateGlobalReferences({ RegExp: { [CALL]: true, [CONSTRUCT]: true } })) { - const [patternNode, flagsNode] = node.arguments; + const [patternNode, flagsNode] = refNode.arguments; const pattern = getStringIfConstant(patternNode, scope); const flags = getStringIfConstant(flagsNode, scope); if (typeof pattern === "string") { - verify(node, pattern, flags || "", fixer => { + verify(refNode, pattern, flags || "", fixer => { if (!isValidWithUnicodeFlag(pattern)) { return null; } - if (node.arguments.length === 1) { - const penultimateToken = sourceCode.getLastToken(node, { skip: 1 }); // skip closing parenthesis + if (refNode.arguments.length === 1) { + const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis return fixer.insertTextAfter( penultimateToken, diff --git a/lib/rules/no-native-reassign.js b/lib/rules/no-native-reassign.js index 634fea93308..27fd38ab86a 100644 --- a/lib/rules/no-native-reassign.js +++ b/lib/rules/no-native-reassign.js @@ -47,6 +47,7 @@ module.exports = { create(context) { const config = context.options[0]; const exceptions = (config && config.exceptions) || []; + const sourceCode = context.getSourceCode(); /** * Reports write references. @@ -87,8 +88,8 @@ module.exports = { } return { - Program() { - const globalScope = context.getScope(); + Program(node) { + const globalScope = sourceCode.getScope(node); globalScope.variables.forEach(checkVariable); } diff --git a/lib/rules/no-new-func.js b/lib/rules/no-new-func.js index 4759f380b29..4680ae5d7ca 100644 --- a/lib/rules/no-new-func.js +++ b/lib/rules/no-new-func.js @@ -40,27 +40,28 @@ module.exports = { }, create(context) { + const sourceCode = context.getSourceCode(); return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); const variable = globalScope.set.get("Function"); if (variable && variable.defs.length === 0) { variable.references.forEach(ref => { - const node = ref.identifier; - const { parent } = node; + const idNode = ref.identifier; + const { parent } = idNode; let evalNode; if (parent) { - if (node === parent.callee && ( + if (idNode === parent.callee && ( parent.type === "NewExpression" || parent.type === "CallExpression" )) { evalNode = parent; } else if ( parent.type === "MemberExpression" && - node === parent.object && + idNode === parent.object && callMethods.has(astUtils.getStaticPropertyName(parent)) ) { const maybeCallee = parent.parent.type === "ChainExpression" ? parent.parent : parent; diff --git a/lib/rules/no-new-native-nonconstructor.js b/lib/rules/no-new-native-nonconstructor.js index a8405002b7f..05171c92b32 100644 --- a/lib/rules/no-new-native-nonconstructor.js +++ b/lib/rules/no-new-native-nonconstructor.js @@ -35,21 +35,23 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); for (const nonConstructorName of nonConstructorGlobalFunctionNames) { const variable = globalScope.set.get(nonConstructorName); if (variable && variable.defs.length === 0) { variable.references.forEach(ref => { - const node = ref.identifier; - const parent = node.parent; + const idNode = ref.identifier; + const parent = idNode.parent; - if (parent && parent.type === "NewExpression" && parent.callee === node) { + if (parent && parent.type === "NewExpression" && parent.callee === idNode) { context.report({ - node, + node: idNode, messageId: "noNewNonconstructor", data: { name: nonConstructorName } }); diff --git a/lib/rules/no-new-object.js b/lib/rules/no-new-object.js index 4dbe8db7365..6351a279fa1 100644 --- a/lib/rules/no-new-object.js +++ b/lib/rules/no-new-object.js @@ -34,10 +34,13 @@ module.exports = { }, create(context) { + + const sourceCode = context.getSourceCode(); + return { NewExpression(node) { const variable = astUtils.getVariableByName( - context.getScope(), + sourceCode.getScope(node), node.callee.name ); diff --git a/lib/rules/no-new-symbol.js b/lib/rules/no-new-symbol.js index 534201c0ba6..551f4a9a414 100644 --- a/lib/rules/no-new-symbol.js +++ b/lib/rules/no-new-symbol.js @@ -29,19 +29,21 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); const variable = globalScope.set.get("Symbol"); if (variable && variable.defs.length === 0) { variable.references.forEach(ref => { - const node = ref.identifier; - const parent = node.parent; + const idNode = ref.identifier; + const parent = idNode.parent; - if (parent && parent.type === "NewExpression" && parent.callee === node) { + if (parent && parent.type === "NewExpression" && parent.callee === idNode) { context.report({ - node, + node: idNode, messageId: "noNewSymbol" }); } diff --git a/lib/rules/no-obj-calls.js b/lib/rules/no-obj-calls.js index 2e2cb5b2460..40df43e1501 100644 --- a/lib/rules/no-obj-calls.js +++ b/lib/rules/no-obj-calls.js @@ -58,9 +58,11 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const traceMap = {}; @@ -71,12 +73,12 @@ module.exports = { }; } - for (const { node, path } of tracker.iterateGlobalReferences(traceMap)) { - const name = getReportNodeName(node.callee); + for (const { node: refNode, path } of tracker.iterateGlobalReferences(traceMap)) { + const name = getReportNodeName(refNode.callee); const ref = path[0]; const messageId = name === ref ? "unexpectedCall" : "unexpectedRefCall"; - context.report({ node, messageId, data: { name, ref } }); + context.report({ node: refNode, messageId, data: { name, ref } }); } } }; diff --git a/lib/rules/no-promise-executor-return.js b/lib/rules/no-promise-executor-return.js index e81ed1d0451..2a99c6fe812 100644 --- a/lib/rules/no-promise-executor-return.js +++ b/lib/rules/no-promise-executor-return.js @@ -84,6 +84,7 @@ module.exports = { create(context) { let funcInfo = null; + const sourceCode = context.getSourceCode(); /** * Reports the given node. @@ -99,7 +100,7 @@ module.exports = { onCodePathStart(_, node) { funcInfo = { upper: funcInfo, - shouldCheck: functionTypesToCheck.has(node.type) && isPromiseExecutor(node, context.getScope()) + shouldCheck: functionTypesToCheck.has(node.type) && isPromiseExecutor(node, sourceCode.getScope(node)) }; if (funcInfo.shouldCheck && node.type === "ArrowFunctionExpression" && node.expression) { diff --git a/lib/rules/no-redeclare.js b/lib/rules/no-redeclare.js index 59749cb6643..c16c030f9a1 100644 --- a/lib/rules/no-redeclare.js +++ b/lib/rules/no-redeclare.js @@ -129,7 +129,7 @@ module.exports = { * @private */ function checkForBlock(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); /* * In ES5, some node type such as `BlockStatement` doesn't have that scope. @@ -141,8 +141,8 @@ module.exports = { } return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); findVariablesInScope(scope); diff --git a/lib/rules/no-regex-spaces.js b/lib/rules/no-regex-spaces.js index e6564bc4583..48ee8c3d024 100644 --- a/lib/rules/no-regex-spaces.js +++ b/lib/rules/no-regex-spaces.js @@ -54,6 +54,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Validate regular expression * @param {ASTNode} nodeToReport Node to report. @@ -149,7 +151,7 @@ module.exports = { * @private */ function checkFunction(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const regExpVar = astUtils.getVariableByName(scope, "RegExp"); const shadowed = regExpVar && regExpVar.defs.length > 0; const patternNode = node.arguments[0]; diff --git a/lib/rules/no-restricted-globals.js b/lib/rules/no-restricted-globals.js index b666238382d..ffc39c801f0 100644 --- a/lib/rules/no-restricted-globals.js +++ b/lib/rules/no-restricted-globals.js @@ -50,6 +50,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + // If no globals are restricted, we don't need to do anything if (context.options.length === 0) { return {}; @@ -99,8 +101,8 @@ module.exports = { } return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); // Report variables declared elsewhere (ex: variables defined as "global" by eslint) scope.variables.forEach(variable => { diff --git a/lib/rules/no-setter-return.js b/lib/rules/no-setter-return.js index a43637e7b55..46969d6dd70 100644 --- a/lib/rules/no-setter-return.js +++ b/lib/rules/no-setter-return.js @@ -156,6 +156,7 @@ module.exports = { create(context) { let funcInfo = null; + const sourceCode = context.getSourceCode(); /** * Creates and pushes to the stack a function info object for the given function node. @@ -163,7 +164,7 @@ module.exports = { * @returns {void} */ function enterFunction(node) { - const outerScope = getOuterScope(context.getScope()); + const outerScope = getOuterScope(sourceCode.getScope(node)); funcInfo = { upper: funcInfo, diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 3af9354ebd7..dda9f5fd7be 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -67,6 +67,7 @@ module.exports = { allow: (context.options[0] && context.options[0].allow) || [], ignoreOnInitialization: context.options[0] && context.options[0].ignoreOnInitialization }; + const sourceCode = context.getSourceCode(); /** * Checks whether or not a given location is inside of the range of a given node. @@ -318,8 +319,8 @@ module.exports = { } return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); const stack = globalScope.childScopes.slice(); while (stack.length) { diff --git a/lib/rules/no-undef-init.js b/lib/rules/no-undef-init.js index 2cb1c3f3710..6e8a1fad72a 100644 --- a/lib/rules/no-undef-init.js +++ b/lib/rules/no-undef-init.js @@ -39,7 +39,7 @@ module.exports = { VariableDeclarator(node) { const name = sourceCode.getText(node.id), init = node.init && node.init.name, - scope = context.getScope(), + scope = sourceCode.getScope(node), undefinedVar = astUtils.getVariableByName(scope, "undefined"), shadowed = undefinedVar && undefinedVar.defs.length > 0, lastToken = sourceCode.getLastToken(node); diff --git a/lib/rules/no-undef.js b/lib/rules/no-undef.js index e920ce6c288..4cd3fa9b679 100644 --- a/lib/rules/no-undef.js +++ b/lib/rules/no-undef.js @@ -54,10 +54,11 @@ module.exports = { create(context) { const options = context.options[0]; const considerTypeOf = options && options.typeof === true || false; + const sourceCode = context.getSourceCode(); return { - "Program:exit"(/* node */) { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); globalScope.through.forEach(ref => { const identifier = ref.identifier; diff --git a/lib/rules/no-undefined.js b/lib/rules/no-undefined.js index e006320b522..7203894c397 100644 --- a/lib/rules/no-undefined.js +++ b/lib/rules/no-undefined.js @@ -28,6 +28,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Report an invalid "undefined" identifier node. * @param {ASTNode} node The node to report. @@ -66,8 +68,8 @@ module.exports = { } return { - "Program:exit"() { - const globalScope = context.getScope(); + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); const stack = [globalScope]; diff --git a/lib/rules/no-unmodified-loop-condition.js b/lib/rules/no-unmodified-loop-condition.js index 12f61e98e6a..3df0a7d87df 100644 --- a/lib/rules/no-unmodified-loop-condition.js +++ b/lib/rules/no-unmodified-loop-condition.js @@ -340,8 +340,8 @@ module.exports = { } return { - "Program:exit"() { - const queue = [context.getScope()]; + "Program:exit"(node) { + const queue = [sourceCode.getScope(node)]; groupMap = new Map(); diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js index 778889a7676..79f972f4b04 100644 --- a/lib/rules/no-unused-vars.js +++ b/lib/rules/no-unused-vars.js @@ -673,7 +673,7 @@ module.exports = { return { "Program:exit"(programNode) { - const unusedVars = collectUnusedVariables(context.getScope(), []); + const unusedVars = collectUnusedVariables(sourceCode.getScope(programNode), []); for (let i = 0, l = unusedVars.length; i < l; ++i) { const unusedVar = unusedVars[i]; diff --git a/lib/rules/no-use-before-define.js b/lib/rules/no-use-before-define.js index 5fd25940128..60cb905b0d6 100644 --- a/lib/rules/no-use-before-define.js +++ b/lib/rules/no-use-before-define.js @@ -258,6 +258,7 @@ module.exports = { create(context) { const options = parseOptions(context.options[0]); + const sourceCode = context.getSourceCode(); /** * Determines whether a given reference should be checked. @@ -339,8 +340,8 @@ module.exports = { } return { - Program() { - checkReferencesInScope(context.getScope()); + Program(node) { + checkReferencesInScope(sourceCode.getScope(node)); } }; } diff --git a/lib/rules/no-useless-backreference.js b/lib/rules/no-useless-backreference.js index 5103e098b14..bef1bee11f9 100644 --- a/lib/rules/no-useless-backreference.js +++ b/lib/rules/no-useless-backreference.js @@ -82,6 +82,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Checks and reports useless backreferences in the given regular expression. * @param {ASTNode} node Node that represents regular expression. A regex literal or RegExp constructor call. @@ -167,8 +169,8 @@ module.exports = { checkRegex(node, pattern, flags); }, - Program() { - const scope = context.getScope(), + Program(node) { + const scope = sourceCode.getScope(node), tracker = new ReferenceTracker(scope), traceMap = { RegExp: { @@ -177,13 +179,13 @@ module.exports = { } }; - for (const { node } of tracker.iterateGlobalReferences(traceMap)) { - const [patternNode, flagsNode] = node.arguments, + for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { + const [patternNode, flagsNode] = refNode.arguments, pattern = getStringIfConstant(patternNode, scope), flags = getStringIfConstant(flagsNode, scope); if (typeof pattern === "string") { - checkRegex(node, pattern, flags || ""); + checkRegex(refNode, pattern, flags || ""); } } } diff --git a/lib/rules/object-shorthand.js b/lib/rules/object-shorthand.js index b755aea3f48..64a506ba6cc 100644 --- a/lib/rules/object-shorthand.js +++ b/lib/rules/object-shorthand.js @@ -354,11 +354,12 @@ module.exports = { /** * Enters a function. This creates a new lexical identifier scope, so a new Set of arrow functions is pushed onto the stack. * Also, this marks all `arguments` identifiers so that they can be detected later. + * @param {ASTNode} node The node representing the function. * @returns {void} */ - function enterFunction() { + function enterFunction(node) { lexicalScopeStack.unshift(new Set()); - context.getScope().variables.filter(variable => variable.name === "arguments").forEach(variable => { + sourceCode.getScope(node).variables.filter(variable => variable.name === "arguments").forEach(variable => { variable.references.map(ref => ref.identifier).forEach(identifier => argumentsIdentifiers.add(identifier)); }); } diff --git a/lib/rules/prefer-arrow-callback.js b/lib/rules/prefer-arrow-callback.js index 340e5e35a11..68c630e0a98 100644 --- a/lib/rules/prefer-arrow-callback.js +++ b/lib/rules/prefer-arrow-callback.js @@ -270,7 +270,7 @@ module.exports = { } // Skip if it's using arguments. - const variable = getVariableOfArguments(context.getScope()); + const variable = getVariableOfArguments(sourceCode.getScope(node)); if (variable && variable.references.length > 0) { return; diff --git a/lib/rules/prefer-exponentiation-operator.js b/lib/rules/prefer-exponentiation-operator.js index 06459e25884..a0eac79be10 100644 --- a/lib/rules/prefer-exponentiation-operator.js +++ b/lib/rules/prefer-exponentiation-operator.js @@ -172,8 +172,8 @@ module.exports = { } return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const trackMap = { Math: { @@ -181,8 +181,8 @@ module.exports = { } }; - for (const { node } of tracker.iterateGlobalReferences(trackMap)) { - report(node); + for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { + report(refNode); } } }; diff --git a/lib/rules/prefer-named-capture-group.js b/lib/rules/prefer-named-capture-group.js index b7055f3a4c5..4fbf6886f27 100644 --- a/lib/rules/prefer-named-capture-group.js +++ b/lib/rules/prefer-named-capture-group.js @@ -151,8 +151,8 @@ module.exports = { checkRegex(node.regex.pattern, node, node, node.regex.flags.includes("u")); } }, - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const traceMap = { RegExp: { @@ -161,12 +161,12 @@ module.exports = { } }; - for (const { node } of tracker.iterateGlobalReferences(traceMap)) { - const regex = getStringIfConstant(node.arguments[0]); - const flags = getStringIfConstant(node.arguments[1]); + for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { + const regex = getStringIfConstant(refNode.arguments[0]); + const flags = getStringIfConstant(refNode.arguments[1]); if (regex) { - checkRegex(regex, node, node.arguments[0], flags && flags.includes("u")); + checkRegex(regex, refNode, refNode.arguments[0], flags && flags.includes("u")); } } } diff --git a/lib/rules/prefer-object-has-own.js b/lib/rules/prefer-object-has-own.js index 023d0a64f4c..55eba59d6ac 100644 --- a/lib/rules/prefer-object-has-own.js +++ b/lib/rules/prefer-object-has-own.js @@ -61,6 +61,9 @@ module.exports = { fixable: "code" }, create(context) { + + const sourceCode = context.getSourceCode(); + return { CallExpression(node) { if (!(node.callee.type === "MemberExpression" && node.callee.object.type === "MemberExpression")) { @@ -72,7 +75,7 @@ module.exports = { const isObject = hasLeftHandObject(node.callee.object); // check `Object` scope - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const variable = astUtils.getVariableByName(scope, "Object"); if ( @@ -85,7 +88,6 @@ module.exports = { node, messageId: "useHasOwn", fix(fixer) { - const sourceCode = context.getSourceCode(); if (sourceCode.getCommentsInside(node.callee).length > 0) { return null; diff --git a/lib/rules/prefer-object-spread.js b/lib/rules/prefer-object-spread.js index b70ef64bded..f574d2aadc8 100644 --- a/lib/rules/prefer-object-spread.js +++ b/lib/rules/prefer-object-spread.js @@ -265,8 +265,8 @@ module.exports = { const sourceCode = context.getSourceCode(); return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const trackMap = { Object: { @@ -275,22 +275,22 @@ module.exports = { }; // Iterate all calls of `Object.assign` (only of the global variable `Object`). - for (const { node } of tracker.iterateGlobalReferences(trackMap)) { + for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { if ( - node.arguments.length >= 1 && - node.arguments[0].type === "ObjectExpression" && - !hasArraySpread(node) && + refNode.arguments.length >= 1 && + refNode.arguments[0].type === "ObjectExpression" && + !hasArraySpread(refNode) && !( - node.arguments.length > 1 && - hasArgumentsWithAccessors(node) + refNode.arguments.length > 1 && + hasArgumentsWithAccessors(refNode) ) ) { - const messageId = node.arguments.length === 1 + const messageId = refNode.arguments.length === 1 ? "useLiteralMessage" : "useSpreadMessage"; - const fix = defineFixer(node, sourceCode); + const fix = defineFixer(refNode, sourceCode); - context.report({ node, messageId, fix }); + context.report({ node: refNode, messageId, fix }); } } } diff --git a/lib/rules/prefer-regex-literals.js b/lib/rules/prefer-regex-literals.js index c9948658cb1..b264050611c 100644 --- a/lib/rules/prefer-regex-literals.js +++ b/lib/rules/prefer-regex-literals.js @@ -163,7 +163,7 @@ module.exports = { * @returns {boolean} True if the identifier is a reference to a global variable. */ function isGlobalReference(node) { - const scope = context.getScope(); + const scope = sourceCode.getScope(node); const variable = findVariable(scope, node); return variable !== null && variable.scope.type === "global" && variable.defs.length === 0; @@ -375,8 +375,8 @@ module.exports = { } return { - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const traceMap = { RegExp: { @@ -385,16 +385,16 @@ module.exports = { } }; - for (const { node } of tracker.iterateGlobalReferences(traceMap)) { - if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(node)) { - const regexNode = node.arguments[0]; + for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { + if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(refNode)) { + const regexNode = refNode.arguments[0]; - if (node.arguments.length === 2) { + if (refNode.arguments.length === 2) { const suggests = []; - const argFlags = getStringValue(node.arguments[1]) || ""; + const argFlags = getStringValue(refNode.arguments[1]) || ""; - if (canFixTo(node, regexNode.regex.pattern, argFlags)) { + if (canFixTo(refNode, regexNode.regex.pattern, argFlags)) { suggests.push({ messageId: "replaceWithLiteralAndFlags", pattern: regexNode.regex.pattern, @@ -407,7 +407,7 @@ module.exports = { if ( !areFlagsEqual(mergedFlags, argFlags) && - canFixTo(node, regexNode.regex.pattern, mergedFlags) + canFixTo(refNode, regexNode.regex.pattern, mergedFlags) ) { suggests.push({ messageId: "replaceWithIntendedLiteralAndFlags", @@ -417,7 +417,7 @@ module.exports = { } context.report({ - node, + node: refNode, messageId: "unexpectedRedundantRegExpWithFlags", suggest: suggests.map(({ flags, pattern, messageId }) => ({ messageId, @@ -425,42 +425,42 @@ module.exports = { flags }, fix(fixer) { - return fixer.replaceText(node, getSafeOutput(node, `/${pattern}/${flags}`)); + return fixer.replaceText(refNode, getSafeOutput(refNode, `/${pattern}/${flags}`)); } })) }); } else { const outputs = []; - if (canFixTo(node, regexNode.regex.pattern, regexNode.regex.flags)) { + if (canFixTo(refNode, regexNode.regex.pattern, regexNode.regex.flags)) { outputs.push(sourceCode.getText(regexNode)); } context.report({ - node, + node: refNode, messageId: "unexpectedRedundantRegExp", suggest: outputs.map(output => ({ messageId: "replaceWithLiteral", fix(fixer) { return fixer.replaceText( - node, - getSafeOutput(node, output) + refNode, + getSafeOutput(refNode, output) ); } })) }); } - } else if (hasOnlyStaticStringArguments(node)) { - let regexContent = getStringValue(node.arguments[0]); + } else if (hasOnlyStaticStringArguments(refNode)) { + let regexContent = getStringValue(refNode.arguments[0]); let noFix = false; let flags; - if (node.arguments[1]) { - flags = getStringValue(node.arguments[1]); + if (refNode.arguments[1]) { + flags = getStringValue(refNode.arguments[1]); } - if (!canFixTo(node, regexContent, flags)) { + if (!canFixTo(refNode, regexContent, flags)) { noFix = true; } @@ -494,12 +494,12 @@ module.exports = { const newRegExpValue = `/${regexContent || "(?:)"}/${flags || ""}`; context.report({ - node, + node: refNode, messageId: "unexpectedRegExp", suggest: noFix ? [] : [{ messageId: "replaceWithLiteral", fix(fixer) { - return fixer.replaceText(node, getSafeOutput(node, newRegExpValue)); + return fixer.replaceText(refNode, getSafeOutput(refNode, newRegExpValue)); } }] }); diff --git a/lib/rules/prefer-rest-params.js b/lib/rules/prefer-rest-params.js index 14b9ae55a4b..9c8f291bd5a 100644 --- a/lib/rules/prefer-rest-params.js +++ b/lib/rules/prefer-rest-params.js @@ -79,6 +79,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Reports a given reference. * @param {eslint-scope.Reference} reference A reference to report. @@ -94,10 +96,11 @@ module.exports = { /** * Reports references of the implicit `arguments` variable if exist. + * @param {ASTNode} node The node representing the function. * @returns {void} */ - function checkForArguments() { - const argumentsVar = getVariableOfArguments(context.getScope()); + function checkForArguments(node) { + const argumentsVar = getVariableOfArguments(sourceCode.getScope(node)); if (argumentsVar) { argumentsVar diff --git a/lib/rules/radix.js b/lib/rules/radix.js index 0618d9844ad..4210c123a67 100644 --- a/lib/rules/radix.js +++ b/lib/rules/radix.js @@ -104,6 +104,7 @@ module.exports = { create(context) { const mode = context.options[0] || MODE_ALWAYS; + const sourceCode = context.getSourceCode(); /** * Checks the arguments of a given CallExpression node and reports it if it @@ -131,7 +132,6 @@ module.exports = { { messageId: "addRadixParameter10", fix(fixer) { - const sourceCode = context.getSourceCode(); const tokens = sourceCode.getTokens(node); const lastToken = tokens[tokens.length - 1]; // Parenthesis. const secondToLastToken = tokens[tokens.length - 2]; // May or may not be a comma. @@ -162,18 +162,18 @@ module.exports = { } return { - "Program:exit"() { - const scope = context.getScope(); + "Program:exit"(node) { + const scope = sourceCode.getScope(node); let variable; // Check `parseInt()` variable = astUtils.getVariableByName(scope, "parseInt"); if (variable && !isShadowed(variable)) { variable.references.forEach(reference => { - const node = reference.identifier; + const idNode = reference.identifier; - if (astUtils.isCallee(node)) { - checkArguments(node.parent); + if (astUtils.isCallee(idNode)) { + checkArguments(idNode.parent); } }); } @@ -182,12 +182,12 @@ module.exports = { variable = astUtils.getVariableByName(scope, "Number"); if (variable && !isShadowed(variable)) { variable.references.forEach(reference => { - const node = reference.identifier.parent; - const maybeCallee = node.parent.type === "ChainExpression" - ? node.parent - : node; + const parentNode = reference.identifier.parent; + const maybeCallee = parentNode.parent.type === "ChainExpression" + ? parentNode.parent + : parentNode; - if (isParseIntMethod(node) && astUtils.isCallee(maybeCallee)) { + if (isParseIntMethod(parentNode) && astUtils.isCallee(maybeCallee)) { checkArguments(maybeCallee.parent); } }); diff --git a/lib/rules/require-atomic-updates.js b/lib/rules/require-atomic-updates.js index 7a5f822ab28..4ed256a4a51 100644 --- a/lib/rules/require-atomic-updates.js +++ b/lib/rules/require-atomic-updates.js @@ -204,8 +204,8 @@ module.exports = { let stack = null; return { - onCodePathStart(codePath) { - const scope = context.getScope(); + onCodePathStart(codePath, node) { + const scope = sourceCode.getScope(node); const shouldVerify = scope.type === "function" && (scope.block.async || scope.block.generator); diff --git a/lib/rules/require-unicode-regexp.js b/lib/rules/require-unicode-regexp.js index efac2519fe1..acedb956e75 100644 --- a/lib/rules/require-unicode-regexp.js +++ b/lib/rules/require-unicode-regexp.js @@ -39,6 +39,9 @@ module.exports = { }, create(context) { + + const sourceCode = context.getSourceCode(); + return { "Literal[regex]"(node) { const flags = node.regex.flags || ""; @@ -48,19 +51,19 @@ module.exports = { } }, - Program() { - const scope = context.getScope(); + Program(node) { + const scope = sourceCode.getScope(node); const tracker = new ReferenceTracker(scope); const trackMap = { RegExp: { [CALL]: true, [CONSTRUCT]: true } }; - for (const { node } of tracker.iterateGlobalReferences(trackMap)) { - const flagsNode = node.arguments[1]; + for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { + const flagsNode = refNode.arguments[1]; const flags = getStringIfConstant(flagsNode, scope); if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) { - context.report({ node, messageId: "requireUFlag" }); + context.report({ node: refNode, messageId: "requireUFlag" }); } } } diff --git a/lib/rules/symbol-description.js b/lib/rules/symbol-description.js index 1c8a364986c..e96a428ce1d 100644 --- a/lib/rules/symbol-description.js +++ b/lib/rules/symbol-description.js @@ -35,6 +35,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Reports if node does not conform the rule in case rule is set to * report missing description @@ -51,16 +53,16 @@ module.exports = { } return { - "Program:exit"() { - const scope = context.getScope(); + "Program:exit"(node) { + const scope = sourceCode.getScope(node); const variable = astUtils.getVariableByName(scope, "Symbol"); if (variable && variable.defs.length === 0) { variable.references.forEach(reference => { - const node = reference.identifier; + const idNode = reference.identifier; - if (astUtils.isCallee(node)) { - checkArgument(node.parent); + if (astUtils.isCallee(idNode)) { + checkArgument(idNode.parent); } }); } diff --git a/lib/rules/valid-typeof.js b/lib/rules/valid-typeof.js index 908d5725e22..6ba5d466118 100644 --- a/lib/rules/valid-typeof.js +++ b/lib/rules/valid-typeof.js @@ -44,7 +44,7 @@ module.exports = { const VALID_TYPES = new Set(["symbol", "undefined", "object", "boolean", "number", "string", "function", "bigint"]), OPERATORS = new Set(["==", "===", "!=", "!=="]); - + const sourceCode = context.getSourceCode(); const requireStringLiterals = context.options[0] && context.options[0].requireStringLiterals; let globalScope; @@ -77,8 +77,8 @@ module.exports = { return { - Program() { - globalScope = context.getScope(); + Program(node) { + globalScope = sourceCode.getScope(node); }, UnaryExpression(node) { diff --git a/lib/source-code/source-code.js b/lib/source-code/source-code.js index 9e2b68e2d31..7e1630cc73b 100644 --- a/lib/source-code/source-code.js +++ b/lib/source-code/source-code.js @@ -143,6 +143,8 @@ function isSpaceBetween(sourceCode, first, second, checkInsideOfJSXText) { // Public Interface //------------------------------------------------------------------------------ +const caches = Symbol("caches"); + /** * Represents parsed source code. */ @@ -175,6 +177,13 @@ class SourceCode extends TokenStore { validate(ast); super(ast.tokens, ast.comments); + /** + * General purpose caching for the class. + */ + this[caches] = new Map([ + ["scopes", new WeakMap()] + ]); + /** * The flag to indicate that the source code has Unicode BOM. * @type {boolean} @@ -588,6 +597,48 @@ class SourceCode extends TokenStore { return positionIndex; } + + /** + * Gets the scope for the given node + * @param {ASTNode} currentNode The node to get the scope of + * @returns {eslint-scope.Scope} The scope information for this node + * @throws {TypeError} If the `currentNode` argument is missing. + */ + getScope(currentNode) { + + if (!currentNode) { + throw new TypeError("Missing required argument: node."); + } + + // check cache first + const cache = this[caches].get("scopes"); + const cachedScope = cache.get(currentNode); + + if (cachedScope) { + return cachedScope; + } + + // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope. + const inner = currentNode.type !== "Program"; + + for (let node = currentNode; node; node = node.parent) { + const scope = this.scopeManager.acquire(node, inner); + + if (scope) { + if (scope.type === "function-expression-name") { + cache.set(currentNode, scope.childScopes[0]); + return scope.childScopes[0]; + } + + cache.set(currentNode, scope); + return scope; + } + } + + cache.set(currentNode, this.scopeManager.scopes[0]); + return this.scopeManager.scopes[0]; + } + } module.exports = SourceCode; diff --git a/tests/fixtures/testers/rule-tester/no-test-global.js b/tests/fixtures/testers/rule-tester/no-test-global.js index 6703cc62100..96c4c0790be 100644 --- a/tests/fixtures/testers/rule-tester/no-test-global.js +++ b/tests/fixtures/testers/rule-tester/no-test-global.js @@ -15,9 +15,12 @@ module.exports = { schema: [], }, create(context) { + + const sourceCode = context.getSourceCode(); + return { - "Program": function(node) { - var globals = context.getScope().variables.map(function (variable) { + "Program"(node) { + var globals = sourceCode.getScope(node).variables.map(function (variable) { return variable.name; }); diff --git a/tests/lib/source-code/source-code.js b/tests/lib/source-code/source-code.js index 7bfbd5ea09f..d65045ad384 100644 --- a/tests/lib/source-code/source-code.js +++ b/tests/lib/source-code/source-code.js @@ -2009,7 +2009,6 @@ describe("SourceCode", () => { }); - describe("getNodeByRangeIndex()", () => { let sourceCode; @@ -3011,4 +3010,277 @@ describe("SourceCode", () => { assert.strictEqual(sourceCode.getIndexFromLoc({ line: 8, column: 0 }), CODE.length); }); }); + + describe("getScope()", () => { + + it("should throw an error when argument is missing", () => { + + linter.defineRule("get-scope", { + create: context => ({ + Program() { + context.getSourceCode().getScope(); + } + }) + }); + + assert.throws(() => { + linter.verify( + "foo", + { + rules: { "get-scope": 2 } + } + ); + }, /Missing required argument: node/u); + + }); + + /** + * Get the scope on the node `astSelector` specified. + * @param {string} code The source code to verify. + * @param {string} astSelector The AST selector to get scope. + * @param {number} [ecmaVersion=5] The ECMAScript version. + * @returns {{node: ASTNode, scope: escope.Scope}} Gotten scope. + */ + function getScope(code, astSelector, ecmaVersion = 5) { + let node, scope; + + linter.defineRule("get-scope", { + create: context => ({ + [astSelector](node0) { + node = node0; + scope = context.getSourceCode().getScope(node); + } + }) + }); + linter.verify( + code, + { + parserOptions: { ecmaVersion }, + rules: { "get-scope": 2 } + } + ); + + return { node, scope }; + } + + it("should return 'function' scope on FunctionDeclaration (ES5)", () => { + const { node, scope } = getScope("function f() {}", "FunctionDeclaration"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node); + }); + + it("should return 'function' scope on FunctionExpression (ES5)", () => { + const { node, scope } = getScope("!function f() {}", "FunctionExpression"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node); + }); + + it("should return 'function' scope on the body of FunctionDeclaration (ES5)", () => { + const { node, scope } = getScope("function f() {}", "BlockStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent); + }); + + it("should return 'function' scope on the body of FunctionDeclaration (ES2015)", () => { + const { node, scope } = getScope("function f() {}", "BlockStatement", 2015); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent); + }); + + it("should return 'function' scope on BlockStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { { var b; } }", "BlockStatement > BlockStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); + }); + + it("should return 'block' scope on BlockStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { { let a; var b; } }", "BlockStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "function"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]); + assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "b"]); + }); + + it("should return 'block' scope on nested BlockStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { { let a; { let b; var c; } } }", "BlockStatement > BlockStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "block"); + assert.strictEqual(scope.upper.upper.type, "function"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); + assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["a"]); + assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "c"]); + }); + + it("should return 'function' scope on SwitchStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); + }); + + it("should return 'switch' scope on SwitchStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchStatement", 2015); + + assert.strictEqual(scope.type, "switch"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); + }); + + it("should return 'function' scope on SwitchCase in functions (ES5)", () => { + const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchCase"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); + }); + + it("should return 'switch' scope on SwitchCase in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchCase", 2015); + + assert.strictEqual(scope.type, "switch"); + assert.strictEqual(scope.block, node.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); + }); + + it("should return 'catch' scope on CatchClause in functions (ES5)", () => { + const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause"); + + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); + }); + + it("should return 'catch' scope on CatchClause in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause", 2015); + + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); + }); + + it("should return 'catch' scope on the block of CatchClause in functions (ES5)", () => { + const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause > BlockStatement"); + + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block, node.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); + }); + + it("should return 'block' scope on the block of CatchClause in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]); + }); + + it("should return 'function' scope on ForStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]); + }); + + it("should return 'for' scope on ForStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement", 2015); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["i"]); + }); + + it("should return 'function' scope on the block body of ForStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]); + }); + + it("should return 'block' scope on the block body of ForStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), []); + assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["i"]); + }); + + it("should return 'function' scope on ForInStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]); + }); + + it("should return 'for' scope on ForInStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement", 2015); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["key"]); + }); + + it("should return 'function' scope on the block body of ForInStatement in functions (ES5)", () => { + const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement > BlockStatement"); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent.parent); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]); + }); + + it("should return 'block' scope on the block body of ForInStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), []); + assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["key"]); + }); + + it("should return 'for' scope on ForOfStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement", 2015); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), ["x"]); + }); + + it("should return 'block' scope on the block body of ForOfStatement in functions (ES2015)", () => { + const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement > BlockStatement", 2015); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual(scope.variables.map(v => v.name), []); + assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["x"]); + }); + + it("should shadow the same name variable by the iteration variable.", () => { + const { node, scope } = getScope("let x; for (let x of x) {}", "ForOfStatement", 2015); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.upper.type, "global"); + assert.strictEqual(scope.block, node); + assert.strictEqual(scope.upper.variables[0].references.length, 0); + assert.strictEqual(scope.references[0].identifier, node.left.declarations[0].id); + assert.strictEqual(scope.references[1].identifier, node.right); + assert.strictEqual(scope.references[1].resolved, scope.variables[0]); + }); + }); }); From ad9dd6a933fd098a0d99c6a9aa059850535c23ee Mon Sep 17 00:00:00 2001 From: Strek Date: Sat, 25 Mar 2023 07:37:56 +0530 Subject: [PATCH 30/48] chore: remove duplicate scss, (#17005) Co-authored-by: Nick Schonning --- docs/.stylelintrc.json | 1 - docs/src/assets/scss/components/alert.scss | 19 +++---------------- .../scss/components/theme-switcher.scss | 6 ------ docs/src/assets/scss/components/toc.scss | 8 ++------ docs/src/assets/scss/foundations.scss | 10 +--------- docs/src/assets/scss/tokens/spacing.scss | 8 -------- docs/src/assets/scss/tokens/typography.scss | 16 ++++++---------- 7 files changed, 12 insertions(+), 56 deletions(-) diff --git a/docs/.stylelintrc.json b/docs/.stylelintrc.json index 18a2e077b08..ab3b3fd039d 100644 --- a/docs/.stylelintrc.json +++ b/docs/.stylelintrc.json @@ -14,7 +14,6 @@ "indentation": 4, "max-line-length": null, "no-descending-specificity": null, - "no-duplicate-selectors": null, "number-leading-zero": null, "number-no-trailing-zeros": null, "selector-class-pattern": null, diff --git a/docs/src/assets/scss/components/alert.scss b/docs/src/assets/scss/components/alert.scss index fb3b5d62d01..8235c1429c9 100644 --- a/docs/src/assets/scss/components/alert.scss +++ b/docs/src/assets/scss/components/alert.scss @@ -16,6 +16,7 @@ color: var(--color-rose-600); [data-theme="dark"] & { + border: 1px solid var(--color-rose-300); color: var(--color-rose-300); background-color: var(--color-rose-900); } @@ -27,6 +28,7 @@ [data-theme="dark"] & { color: var(--color-warning-300); + border: 1px solid var(--color-warning-300); background-color: var(--color-warning-900); } } @@ -37,23 +39,8 @@ [data-theme="dark"] & { color: var(--color-success-300); - background-color: var(--color-success-900); - } - } -} - -[data-theme="dark"] { - .alert { - &.alert--warning { - border: 1px solid var(--color-rose-300); - } - - &.alert--important { - border: 1px solid var(--color-warning-300); - } - - &.alert--tip { border: 1px solid var(--color-success-300); + background-color: var(--color-success-900); } } } diff --git a/docs/src/assets/scss/components/theme-switcher.scss b/docs/src/assets/scss/components/theme-switcher.scss index e10f9e6d6e1..d44aa9009d9 100644 --- a/docs/src/assets/scss/components/theme-switcher.scss +++ b/docs/src/assets/scss/components/theme-switcher.scss @@ -75,9 +75,3 @@ } } } - -.theme-switcher__button:hover { - .theme-switcher__icon { - color: var(--link-color); - } -} diff --git a/docs/src/assets/scss/components/toc.scss b/docs/src/assets/scss/components/toc.scss index aca19cf99b3..96647b4c70f 100644 --- a/docs/src/assets/scss/components/toc.scss +++ b/docs/src/assets/scss/components/toc.scss @@ -1,17 +1,13 @@ .docs-toc { margin: 2rem 0; -} - -.docs-toc { - .docs-aside & { - display: none; - } @media all and (min-width: 1400px) { display: none; } .docs-aside & { + display: none; + @media all and (min-width: 1400px) { display: block; } diff --git a/docs/src/assets/scss/foundations.scss b/docs/src/assets/scss/foundations.scss index c370aaeabb7..68e44651f4f 100644 --- a/docs/src/assets/scss/foundations.scss +++ b/docs/src/assets/scss/foundations.scss @@ -62,6 +62,7 @@ html { } body { + font-size: var(--step-0); position: relative; margin: 0 auto; line-height: 1.5; @@ -171,11 +172,6 @@ code { } } -p:empty { - display: none; - margin: 0; -} - .c-icon { color: var(--icon-color); flex: none; @@ -346,10 +342,6 @@ nav { } /* typography */ -body { - font-size: var(--step-0); - line-height: 1.5; -} .eyebrow { color: var(--link-color); diff --git a/docs/src/assets/scss/tokens/spacing.scss b/docs/src/assets/scss/tokens/spacing.scss index 1f5549b8e70..2bc542459b5 100644 --- a/docs/src/assets/scss/tokens/spacing.scss +++ b/docs/src/assets/scss/tokens/spacing.scss @@ -6,15 +6,7 @@ --fluid-screen: 100vw; --fluid-bp: calc((var(--fluid-screen) - var(--fluid-min-width) / 16 * 1rem) / (var(--fluid-max-width) - var(--fluid-min-width))); -} - -@media screen and (min-width: 1024px) { - :root { - --fluid-screen: calc(var(--fluid-max-width) * 1px); - } -} -:root { --fc-3xs-min: (var(--fc-s-min) * 0.25); --fc-3xs-max: (var(--fc-s-max) * 0.25); diff --git a/docs/src/assets/scss/tokens/typography.scss b/docs/src/assets/scss/tokens/typography.scss index bdf4792bb90..a9e935b2a01 100644 --- a/docs/src/assets/scss/tokens/typography.scss +++ b/docs/src/assets/scss/tokens/typography.scss @@ -1,13 +1,5 @@ /* @link https://utopia.fyi/type/calculator?c=320,16,1.125,1280,16,1.25,6,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l */ -:root { - --fluid-min-width: 320; - --fluid-max-width: 1280; - - --fluid-screen: 100vw; - --fluid-bp: calc((var(--fluid-screen) - var(--fluid-min-width) / 16 * 1rem) / (var(--fluid-max-width) - var(--fluid-min-width))); -} - @media screen and (min-width: 1280px) { :root { --fluid-screen: calc(var(--fluid-max-width) * 1px); @@ -15,6 +7,12 @@ } :root { + --fluid-min-width: 320; + --fluid-max-width: 1280; + + --fluid-screen: 100vw; + --fluid-bp: calc((var(--fluid-screen) - var(--fluid-min-width) / 16 * 1rem) / (var(--fluid-max-width) - var(--fluid-min-width))); + --f--2-min: 12.64; --f--2-max: 10.24; --step--2: calc(((var(--f--2-min) / 16) * 1rem) + (var(--f--2-max) - var(--f--2-min)) * var(--fluid-bp)); @@ -50,9 +48,7 @@ --f-6-min: 32.44; --f-6-max: 61.04; --step-6: calc(((var(--f-6-min) / 16) * 1rem) + (var(--f-6-max) - var(--f-6-min)) * var(--fluid-bp)); -} -:root { --mono-font: "Mono Punctuators", "Space Mono", monospace; --text-font: "Inter", From e39f28d8578a00f4da8d4ddad559547950128a0d Mon Sep 17 00:00:00 2001 From: Tanuj Kanti <86398394+Tanujkanti4441@users.noreply.github.com> Date: Sun, 26 Mar 2023 19:30:49 +0530 Subject: [PATCH 31/48] docs: add back to top button (#16979) * docs: add back to top button * fix: spacing errors of previous commit * docs: update css of scrollup button * docs: add css comments for color * fix: spacing issues of last commit * docs: set the scroll btn to work without js * docs: update previous code * docs: update previous code --- docs/src/_includes/layouts/base.html | 3 ++- docs/src/_includes/layouts/doc.html | 3 +++ docs/src/assets/js/scroll-up-btn.js | 13 +++++++++++ docs/src/assets/scss/docs.scss | 34 ++++++++++++++++++++++++---- docs/src/assets/scss/print.scss | 4 ++++ 5 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 docs/src/assets/js/scroll-up-btn.js diff --git a/docs/src/_includes/layouts/base.html b/docs/src/_includes/layouts/base.html index 13779e5dd86..49bdf8522c8 100644 --- a/docs/src/_includes/layouts/base.html +++ b/docs/src/_includes/layouts/base.html @@ -1,5 +1,5 @@ - + @@ -154,6 +154,7 @@ + {% include 'partials/analytics.html' %} {%- if hook == "component-library" -%} diff --git a/docs/src/_includes/layouts/doc.html b/docs/src/_includes/layouts/doc.html index 70888cd6b81..4050a901063 100644 --- a/docs/src/_includes/layouts/doc.html +++ b/docs/src/_includes/layouts/doc.html @@ -102,6 +102,9 @@

{{ title }}

{% include "partials/docs-footer.html" %} + + + diff --git a/docs/src/assets/js/scroll-up-btn.js b/docs/src/assets/js/scroll-up-btn.js new file mode 100644 index 00000000000..cb77af1bcbe --- /dev/null +++ b/docs/src/assets/js/scroll-up-btn.js @@ -0,0 +1,13 @@ +(function () { + const scrollUpBtn = document.getElementById("scroll-up-btn"); + + if(window.innerWidth < 1400) { + window.addEventListener("scroll", function () { + if(document.body.scrollTop > 500 || document.documentElement.scrollTop > 500) { + scrollUpBtn.style.display = "flex"; + } else { + scrollUpBtn.style.display = "none"; + } + }); + } +})(); \ No newline at end of file diff --git a/docs/src/assets/scss/docs.scss b/docs/src/assets/scss/docs.scss index 4aa106546c1..ee40123891d 100644 --- a/docs/src/assets/scss/docs.scss +++ b/docs/src/assets/scss/docs.scss @@ -30,7 +30,7 @@ html { grid-row: 1 / 2; padding-top: var(--space-l-xl); padding-block-start: var(--space-l-xl); - font-size: .875rem; + font-size: 0.875rem; display: grid; grid-auto-rows: max-content; align-items: start; @@ -142,10 +142,10 @@ pre[class*="language-"] { .c-btn.c-btn--playground { position: absolute; font-size: var(--step--1); - bottom: .5rem; - right: .5rem; - offset-block-end: .5rem; - offset-inline-end: .5rem; + bottom: 0.5rem; + right: 0.5rem; + offset-block-end: 0.5rem; + offset-inline-end: 0.5rem; @media all and (max-width: 768px) { display: none; @@ -157,3 +157,27 @@ pre[class*="language-"] { opacity: 1; } } + +#scroll-up-btn { + width: 50px; + height: 50px; + display: none; + position: fixed; + right: 50px; + bottom: 35px; + font-size: 1.5rem; + border-radius: 50%; + color: var(--body-background-color); + text-decoration: none; + justify-content: center; + align-items: center; + background-color: var(--link-color); + + @media (max-width: 800px) { + right: 35px; + } + + @media (max-width: 600px) { + right: 25px; + } +} diff --git a/docs/src/assets/scss/print.scss b/docs/src/assets/scss/print.scss index 68d4146240e..39dcc9470cd 100644 --- a/docs/src/assets/scss/print.scss +++ b/docs/src/assets/scss/print.scss @@ -207,3 +207,7 @@ ul { margin: 1cm; } } + +#scroll-up-btn { + display: none; +} From ec2d8307850dd039e118c001416606e1e0342bc8 Mon Sep 17 00:00:00 2001 From: Andrii Lundiak Date: Mon, 27 Mar 2023 18:48:06 +0200 Subject: [PATCH 32/48] docs: Fix typos in the `semi` rule docs (#17012) --- docs/src/rules/semi.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/rules/semi.md b/docs/src/rules/semi.md index 86c4e5d84fe..63f2070d218 100644 --- a/docs/src/rules/semi.md +++ b/docs/src/rules/semi.md @@ -76,7 +76,7 @@ This rule has two options, a string option and an object option. String option: * `"always"` (default) requires semicolons at the end of statements -* `"never"` disallows semicolons as the end of statements (except to disambiguate statements beginning with `[`, `(`, `/`, `+`, or `-`) +* `"never"` disallows semicolons at the end of statements (except to disambiguate statements beginning with `[`, `(`, `/`, `+`, or `-`) Object option (when `"always"`): @@ -86,7 +86,7 @@ Object option (when `"never"`): * `"beforeStatementContinuationChars": "any"` (default) ignores semicolons (or lacking semicolon) at the end of statements if the next line starts with `[`, `(`, `/`, `+`, or `-`. * `"beforeStatementContinuationChars": "always"` requires semicolons at the end of statements if the next line starts with `[`, `(`, `/`, `+`, or `-`. -* `"beforeStatementContinuationChars": "never"` disallows semicolons as the end of statements if it doesn't make ASI hazard even if the next line starts with `[`, `(`, `/`, `+`, or `-`. +* `"beforeStatementContinuationChars": "never"` disallows semicolons at the end of statements if it doesn't make ASI hazard even if the next line starts with `[`, `(`, `/`, `+`, or `-`. **Note:** `beforeStatementContinuationChars` does not apply to class fields because class fields are not statements. From 619f3fd17324c7b71bf17e02047d0c6dc7e5109e Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Tue, 28 Mar 2023 22:07:17 +1030 Subject: [PATCH 33/48] fix: correctly handle `null` default config in `RuleTester` (#17023) * fix: correctly handle `null` default config in `RuleTester` * fix: update flatruletester --- lib/rule-tester/flat-rule-tester.js | 2 +- lib/rule-tester/rule-tester.js | 2 +- tests/lib/rule-tester/flat-rule-tester.js | 14 ++++++++------ tests/lib/rule-tester/rule-tester.js | 14 ++++++++------ 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/rule-tester/flat-rule-tester.js b/lib/rule-tester/flat-rule-tester.js index 510cbc688ce..97055d104f4 100644 --- a/lib/rule-tester/flat-rule-tester.js +++ b/lib/rule-tester/flat-rule-tester.js @@ -345,7 +345,7 @@ class FlatRuleTester { * @returns {void} */ static setDefaultConfig(config) { - if (typeof config !== "object") { + if (typeof config !== "object" || config === null) { throw new TypeError("FlatRuleTester.setDefaultConfig: config must be an object"); } sharedDefaultConfig = config; diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index 48df3b79b94..8518299d0b0 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -412,7 +412,7 @@ class RuleTester { * @returns {void} */ static setDefaultConfig(config) { - if (typeof config !== "object") { + if (typeof config !== "object" || config === null) { throw new TypeError("RuleTester.setDefaultConfig: config must be an object"); } defaultConfig = config; diff --git a/tests/lib/rule-tester/flat-rule-tester.js b/tests/lib/rule-tester/flat-rule-tester.js index bdc196e1653..c73099d1d54 100644 --- a/tests/lib/rule-tester/flat-rule-tester.js +++ b/tests/lib/rule-tester/flat-rule-tester.js @@ -142,12 +142,14 @@ describe("FlatRuleTester", () => { FlatRuleTester.setDefaultConfig(config); }; } - assert.throw(setConfig()); - assert.throw(setConfig(1)); - assert.throw(setConfig(3.14)); - assert.throw(setConfig("foo")); - assert.throw(setConfig(null)); - assert.throw(setConfig(true)); + const errorMessage = "FlatRuleTester.setDefaultConfig: config must be an object"; + + assert.throw(setConfig(), errorMessage); + assert.throw(setConfig(1), errorMessage); + assert.throw(setConfig(3.14), errorMessage); + assert.throw(setConfig("foo"), errorMessage); + assert.throw(setConfig(null), errorMessage); + assert.throw(setConfig(true), errorMessage); }); it("should pass-through the globals config to the tester then to the to rule", () => { diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js index e961dd9a516..096e639e95f 100644 --- a/tests/lib/rule-tester/rule-tester.js +++ b/tests/lib/rule-tester/rule-tester.js @@ -1412,12 +1412,14 @@ describe("RuleTester", () => { RuleTester.setDefaultConfig(config); }; } - assert.throw(setConfig()); - assert.throw(setConfig(1)); - assert.throw(setConfig(3.14)); - assert.throw(setConfig("foo")); - assert.throw(setConfig(null)); - assert.throw(setConfig(true)); + const errorMessage = "RuleTester.setDefaultConfig: config must be an object"; + + assert.throw(setConfig(), errorMessage); + assert.throw(setConfig(1), errorMessage); + assert.throw(setConfig(3.14), errorMessage); + assert.throw(setConfig("foo"), errorMessage); + assert.throw(setConfig(null), errorMessage); + assert.throw(setConfig(true), errorMessage); }); it("should pass-through the globals config to the tester then to the to rule", () => { From 4dd8d524e0fc9e8e2019df13f8b968021600e85c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Mar 2023 14:02:29 +0200 Subject: [PATCH 34/48] ci: bump actions/stale from 7 to 8 (#17026) Bumps [actions/stale](https://github.com/actions/stale) from 7 to 8. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index cffc79faf88..9e7355d34b9 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -18,7 +18,7 @@ jobs: pull-requests: write steps: - - uses: actions/stale@v7 + - uses: actions/stale@v8 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-issue-stale: 30 From b6ab8b2a2ca8807baca121407f5bfb0a0a839427 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Tue, 28 Mar 2023 15:21:25 -0400 Subject: [PATCH 35/48] feat: `require-unicode-regexp` add suggestions (#17007) * feat: `require-unicode-regexp` add suggestions * Review fixups: invalid patterns; sequence expressions * I promise I know how CJS modules work * Handled non-string literals * Update lib/rules/require-unicode-regexp.js Co-authored-by: Milos Djermanovic * Don't concatenate + 'u' * Test the new cases * Touch up regular-expressions.js templating * Add myself as author to regular-expressions.js --------- Co-authored-by: Milos Djermanovic --- lib/rules/no-misleading-character-class.js | 37 +--- lib/rules/prefer-regex-literals.js | 3 +- lib/rules/require-unicode-regexp.js | 59 +++++- lib/rules/utils/regular-expressions.js | 42 +++++ tests/lib/rules/require-unicode-regexp.js | 207 +++++++++++++++++++-- 5 files changed, 297 insertions(+), 51 deletions(-) create mode 100644 lib/rules/utils/regular-expressions.js diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index ef6e1418002..ddbcaefd549 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -4,16 +4,15 @@ "use strict"; const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils"); -const { RegExpValidator, RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); +const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode"); const astUtils = require("./utils/ast-utils.js"); +const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const REGEXPP_LATEST_ECMA_VERSION = 2022; - /** * Iterate character sequences of a given nodes. * @@ -185,38 +184,10 @@ module.exports = { } } - /** - * Checks if the given regular expression pattern would be valid with the `u` flag. - * @param {string} pattern The regular expression pattern to verify. - * @returns {boolean} `true` if the pattern would be valid with the `u` flag. - * `false` if the pattern would be invalid with the `u` flag or the configured - * ecmaVersion doesn't support the `u` flag. - */ - function isValidWithUnicodeFlag(pattern) { - const { ecmaVersion } = context.languageOptions; - - // ecmaVersion <= 5 doesn't support the 'u' flag - if (ecmaVersion <= 5) { - return false; - } - - const validator = new RegExpValidator({ - ecmaVersion: Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION) - }); - - try { - validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true); - } catch { - return false; - } - - return true; - } - return { "Literal[regex]"(node) { verify(node, node.regex.pattern, node.regex.flags, fixer => { - if (!isValidWithUnicodeFlag(node.regex.pattern)) { + if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)) { return null; } @@ -242,7 +213,7 @@ module.exports = { if (typeof pattern === "string") { verify(refNode, pattern, flags || "", fixer => { - if (!isValidWithUnicodeFlag(pattern)) { + if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)) { return null; } diff --git a/lib/rules/prefer-regex-literals.js b/lib/rules/prefer-regex-literals.js index b264050611c..94b52155ee2 100644 --- a/lib/rules/prefer-regex-literals.js +++ b/lib/rules/prefer-regex-literals.js @@ -13,13 +13,12 @@ const astUtils = require("./utils/ast-utils"); const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("@eslint-community/eslint-utils"); const { RegExpValidator, visitRegExpAST, RegExpParser } = require("@eslint-community/regexpp"); const { canTokensBeAdjacent } = require("./utils/ast-utils"); +const { REGEXPP_LATEST_ECMA_VERSION } = require("./utils/regular-expressions"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const REGEXPP_LATEST_ECMA_VERSION = 2022; - /** * Determines whether the given node is a string literal. * @param {ASTNode} node Node to check. diff --git a/lib/rules/require-unicode-regexp.js b/lib/rules/require-unicode-regexp.js index acedb956e75..2fe1539cfcc 100644 --- a/lib/rules/require-unicode-regexp.js +++ b/lib/rules/require-unicode-regexp.js @@ -15,6 +15,8 @@ const { ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils"); +const astUtils = require("./utils/ast-utils.js"); +const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); //------------------------------------------------------------------------------ // Rule Definition @@ -31,7 +33,10 @@ module.exports = { url: "https://eslint.org/docs/rules/require-unicode-regexp" }, + hasSuggestions: true, + messages: { + addUFlag: "Add the 'u' flag.", requireUFlag: "Use the 'u' flag." }, @@ -47,7 +52,20 @@ module.exports = { const flags = node.regex.flags || ""; if (!flags.includes("u")) { - context.report({ node, messageId: "requireUFlag" }); + context.report({ + messageId: "requireUFlag", + node, + suggest: isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern) + ? [ + { + fix(fixer) { + return fixer.insertTextAfter(node, "u"); + }, + messageId: "addUFlag" + } + ] + : null + }); } }, @@ -59,11 +77,46 @@ module.exports = { }; for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { - const flagsNode = refNode.arguments[1]; + const [patternNode, flagsNode] = refNode.arguments; + const pattern = getStringIfConstant(patternNode, scope); const flags = getStringIfConstant(flagsNode, scope); if (!flagsNode || (typeof flags === "string" && !flags.includes("u"))) { - context.report({ node: refNode, messageId: "requireUFlag" }); + context.report({ + messageId: "requireUFlag", + node: refNode, + suggest: typeof pattern === "string" && isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern) + ? [ + { + fix(fixer) { + if (flagsNode) { + if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") { + const flagsNodeText = sourceCode.getText(flagsNode); + + return fixer.replaceText(flagsNode, [ + flagsNodeText.slice(0, flagsNodeText.length - 1), + flagsNodeText.slice(flagsNodeText.length - 1) + ].join("u")); + } + + // We intentionally don't suggest concatenating + "u" to non-literals + return null; + } + + const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis + + return fixer.insertTextAfter( + penultimateToken, + astUtils.isCommaToken(penultimateToken) + ? ' "u",' + : ', "u"' + ); + }, + messageId: "addUFlag" + } + ] + : null + }); } } } diff --git a/lib/rules/utils/regular-expressions.js b/lib/rules/utils/regular-expressions.js new file mode 100644 index 00000000000..234a1cb8b11 --- /dev/null +++ b/lib/rules/utils/regular-expressions.js @@ -0,0 +1,42 @@ +/** + * @fileoverview Common utils for regular expressions. + * @author Josh Goldberg + * @author Toru Nagashima + */ + +"use strict"; + +const { RegExpValidator } = require("@eslint-community/regexpp"); + +const REGEXPP_LATEST_ECMA_VERSION = 2022; + +/** + * Checks if the given regular expression pattern would be valid with the `u` flag. + * @param {number} ecmaVersion ECMAScript version to parse in. + * @param {string} pattern The regular expression pattern to verify. + * @returns {boolean} `true` if the pattern would be valid with the `u` flag. + * `false` if the pattern would be invalid with the `u` flag or the configured + * ecmaVersion doesn't support the `u` flag. + */ +function isValidWithUnicodeFlag(ecmaVersion, pattern) { + if (ecmaVersion <= 5) { // ecmaVersion <= 5 doesn't support the 'u' flag + return false; + } + + const validator = new RegExpValidator({ + ecmaVersion: Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION) + }); + + try { + validator.validatePattern(pattern, void 0, void 0, /* uFlag = */ true); + } catch { + return false; + } + + return true; +} + +module.exports = { + isValidWithUnicodeFlag, + REGEXPP_LATEST_ECMA_VERSION +}; diff --git a/tests/lib/rules/require-unicode-regexp.js b/tests/lib/rules/require-unicode-regexp.js index 16b6be4ff11..22ada7aacf6 100644 --- a/tests/lib/rules/require-unicode-regexp.js +++ b/tests/lib/rules/require-unicode-regexp.js @@ -25,8 +25,10 @@ ruleTester.run("require-unicode-regexp", rule, { "/foo/u", "/foo/gimuy", "RegExp('', 'u')", + "RegExp('', `u`)", "new RegExp('', 'u')", "RegExp('', 'gimuy')", + "RegExp('', `gimuy`)", "new RegExp('', 'gimuy')", "const flags = 'u'; new RegExp('', flags)", "const flags = 'g'; new RegExp('', flags + 'u')", @@ -44,60 +46,239 @@ ruleTester.run("require-unicode-regexp", rule, { { code: "class C { #RegExp; foo() { new globalThis.#RegExp('foo') } }", parserOptions: { ecmaVersion: 2022 }, env: { es2020: true } } ], invalid: [ + { + code: "/\\a/", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, { code: "/foo/", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "/foo/u" + } + ] + }] }, { code: "/foo/gimy", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "/foo/gimyu" + } + ] + }] }, { code: "RegExp('foo')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', \"u\")" + } + ] + }] + }, + { + code: "RegExp('\\\\a')", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] }, { code: "RegExp('foo', '')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', 'u')" + } + ] + }] }, { code: "RegExp('foo', 'gimy')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', 'gimyu')" + } + ] + }] + }, + { + code: "RegExp('foo', `gimy`)", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "RegExp('foo', `gimyu`)" + } + ] + }] }, { code: "new RegExp('foo')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp('foo', \"u\")" + } + ] + }] + }, + { + code: "new RegExp('foo', false)", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, + { + code: "new RegExp('foo', 1)", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] }, { code: "new RegExp('foo', '')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp('foo', 'u')" + } + ] + }] }, { code: "new RegExp('foo', 'gimy')", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp('foo', 'gimyu')" + } + ] + }] + }, + { + code: "new RegExp(('foo'))", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp(('foo'), \"u\")" + } + ] + }] + }, + { + code: "new RegExp(('unrelated', 'foo'))", + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new RegExp(('unrelated', 'foo'), \"u\")" + } + ] + }] }, { code: "const flags = 'gi'; new RegExp('foo', flags)", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, + { + code: "const flags = 'gi'; new RegExp('foo', ('unrelated', flags))", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, + { + code: "let flags; new RegExp('foo', flags = 'g')", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, + { + code: "const flags = `gi`; new RegExp(`foo`, (`unrelated`, flags))", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] }, { code: "const flags = 'gimu'; new RegExp('foo', flags[0])", - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] }, { code: "new window.RegExp('foo')", env: { browser: true }, - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new window.RegExp('foo', \"u\")" + } + ] + }] }, { code: "new global.RegExp('foo')", env: { node: true }, - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new global.RegExp('foo', \"u\")" + } + ] + }] }, { code: "new globalThis.RegExp('foo')", env: { es2020: true }, - errors: [{ messageId: "requireUFlag" }] + errors: [{ + messageId: "requireUFlag", + suggestions: [ + { + messageId: "addUFlag", + output: "new globalThis.RegExp('foo', \"u\")" + } + ] + }] } ] }); From 75339df99418df4d7e05a77e42ed7e22eabcc9e0 Mon Sep 17 00:00:00 2001 From: Ed Lucas <1424108+edlucas@users.noreply.github.com> Date: Tue, 28 Mar 2023 20:57:25 +0000 Subject: [PATCH 36/48] docs: fix typos and missing info in id-match docs (#17029) --- docs/src/rules/id-match.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/rules/id-match.md b/docs/src/rules/id-match.md index 468078d2d54..08d5ff3ba9c 100644 --- a/docs/src/rules/id-match.md +++ b/docs/src/rules/id-match.md @@ -91,10 +91,10 @@ This rule has an object option: * `"properties": false` (default) does not check object properties * `"properties": true` requires object literal properties and member expression assignment properties to match the specified regular expression -* `"classFields": false` (default) does not class field names +* `"classFields": false` (default) does not check class field names * `"classFields": true` requires class field names to match the specified regular expression * `"onlyDeclarations": false` (default) requires all variable names to match the specified regular expression -* `"onlyDeclarations": true` requires only `var`, `function`, and `class` declarations to match the specified regular expression +* `"onlyDeclarations": true` requires only `var`, `const`, `let`, `function`, and `class` declarations to match the specified regular expression * `"ignoreDestructuring": false` (default) enforces `id-match` for destructured identifiers * `"ignoreDestructuring": true` does not check destructured identifiers From f5f9a88c79b32222c0331a9bac1c02571d953b69 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 28 Mar 2023 23:16:01 +0200 Subject: [PATCH 37/48] chore: upgrade eslint-visitor-keys@3.4.0 (#17030) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c8c7f5fbbb8..84a1da254de 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", + "eslint-visitor-keys": "^3.4.0", "espree": "^9.5.0", "esquery": "^1.4.2", "esutils": "^2.0.2", From 522431e5206bac2fcb41c0d6dc98a84929203bee Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 28 Mar 2023 23:36:40 +0200 Subject: [PATCH 38/48] chore: upgrade espree@9.5.1 (#17031) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 84a1da254de..d976e0e074a 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.1.1", "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.0", + "espree": "^9.5.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", From dddb47528816cd7e2e737bfde108ed4d62e6a219 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Tue, 28 Mar 2023 23:54:34 +0200 Subject: [PATCH 39/48] chore: upgrade @eslint/eslintrc@2.0.2 (#17032) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d976e0e074a..d90d1bea38c 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.1", + "@eslint/eslintrc": "^2.0.2", "@eslint/js": "8.36.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", From ee9ddbd63e262aed0052853760866c7a054af561 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Tue, 28 Mar 2023 18:07:36 -0400 Subject: [PATCH 40/48] chore: package.json update for @eslint/js release --- packages/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js/package.json b/packages/js/package.json index 7dfa6130e8d..27e4f0056a1 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "8.36.0", + "version": "8.37.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, From c67f2992a743de4765bb6f11c12622e3651324b9 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Wed, 29 Mar 2023 00:26:22 +0200 Subject: [PATCH 41/48] chore: upgrade @eslint/js@8.37.0 (#17033) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d90d1bea38c..dab4da10637 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.36.0", + "@eslint/js": "8.37.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", From 3413d34acf198c1e65b0abb228d67b0ff3feda6d Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Tue, 28 Mar 2023 18:46:37 -0400 Subject: [PATCH 42/48] Build: changelog update for 8.37.0 --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a68f37a80b7..1ee070df357 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +v8.37.0 - March 28, 2023 + +* [`c67f299`](https://github.com/eslint/eslint/commit/c67f2992a743de4765bb6f11c12622e3651324b9) chore: upgrade @eslint/js@8.37.0 (#17033) (Milos Djermanovic) +* [`ee9ddbd`](https://github.com/eslint/eslint/commit/ee9ddbd63e262aed0052853760866c7a054af561) chore: package.json update for @eslint/js release (ESLint Jenkins) +* [`dddb475`](https://github.com/eslint/eslint/commit/dddb47528816cd7e2e737bfde108ed4d62e6a219) chore: upgrade @eslint/eslintrc@2.0.2 (#17032) (Milos Djermanovic) +* [`522431e`](https://github.com/eslint/eslint/commit/522431e5206bac2fcb41c0d6dc98a84929203bee) chore: upgrade espree@9.5.1 (#17031) (Milos Djermanovic) +* [`f5f9a88`](https://github.com/eslint/eslint/commit/f5f9a88c79b32222c0331a9bac1c02571d953b69) chore: upgrade eslint-visitor-keys@3.4.0 (#17030) (Milos Djermanovic) +* [`75339df`](https://github.com/eslint/eslint/commit/75339df99418df4d7e05a77e42ed7e22eabcc9e0) docs: fix typos and missing info in id-match docs (#17029) (Ed Lucas) +* [`b6ab8b2`](https://github.com/eslint/eslint/commit/b6ab8b2a2ca8807baca121407f5bfb0a0a839427) feat: `require-unicode-regexp` add suggestions (#17007) (Josh Goldberg) +* [`4dd8d52`](https://github.com/eslint/eslint/commit/4dd8d524e0fc9e8e2019df13f8b968021600e85c) ci: bump actions/stale from 7 to 8 (#17026) (dependabot[bot]) +* [`619f3fd`](https://github.com/eslint/eslint/commit/619f3fd17324c7b71bf17e02047d0c6dc7e5109e) fix: correctly handle `null` default config in `RuleTester` (#17023) (Brad Zacher) +* [`ec2d830`](https://github.com/eslint/eslint/commit/ec2d8307850dd039e118c001416606e1e0342bc8) docs: Fix typos in the `semi` rule docs (#17012) (Andrii Lundiak) +* [`e39f28d`](https://github.com/eslint/eslint/commit/e39f28d8578a00f4da8d4ddad559547950128a0d) docs: add back to top button (#16979) (Tanuj Kanti) +* [`ad9dd6a`](https://github.com/eslint/eslint/commit/ad9dd6a933fd098a0d99c6a9aa059850535c23ee) chore: remove duplicate scss, (#17005) (Strek) +* [`10022b1`](https://github.com/eslint/eslint/commit/10022b1f4bda1ad89193512ecf18c2ee61db8202) feat: Copy getScope() to SourceCode (#17004) (Nicholas C. Zakas) +* [`1665c02`](https://github.com/eslint/eslint/commit/1665c029acb92bf8812267f1647ad1a7054cbcb4) feat: Use plugin metadata for flat config serialization (#16992) (Nicholas C. Zakas) +* [`b3634f6`](https://github.com/eslint/eslint/commit/b3634f695ddab6a82c0a9b1d8695e62b60d23366) feat: docs license (#17010) (Samuel Roldan) +* [`721c717`](https://github.com/eslint/eslint/commit/721c71782a7c11025689a1500e7690fb3794fcce) docs: Custom Processors cleanup and expansion (#16838) (Ben Perlmutter) +* [`1fbf118`](https://github.com/eslint/eslint/commit/1fbf1184fed57df02640aad4659afb54dc26a2e9) fix: `getFirstToken`/`getLastToken` on comment-only node (#16889) (Francesco Trotta) +* [`129e252`](https://github.com/eslint/eslint/commit/129e252132c7c476d7de17f40b54a333ddb2e6bb) fix: Fix typo in `logical-assignment-operators` rule description (#17000) (Francesco Trotta) +* [`892e6e5`](https://github.com/eslint/eslint/commit/892e6e58c5a07a549d3104de3b6b5879797dc97f) feat: languageOptions.parser must be an object. (#16985) (Nicholas C. Zakas) +* [`ada6a3e`](https://github.com/eslint/eslint/commit/ada6a3e6e3607523958f35e1260537630ec0e976) ci: unpin Node 19 (#16993) (Milos Djermanovic) +* [`c3da975`](https://github.com/eslint/eslint/commit/c3da975e69fde46f35338ce48528841a8dc1ffd2) chore: Remove triage label from template (#16990) (Nicholas C. Zakas) +* [`d049f97`](https://github.com/eslint/eslint/commit/d049f974103e530ef76ede25af701635caf1f405) docs: 'How ESLint is Maintained' page (#16961) (Ben Perlmutter) +* [`5251a92`](https://github.com/eslint/eslint/commit/5251a921866e8d3b380dfe8db8a6e6ab97773d5e) docs: Describe guard options for guard-for-in (#16986) (alope107) +* [`69bc0e2`](https://github.com/eslint/eslint/commit/69bc0e2f4412998f9384600a100d7882ea4dd3f3) ci: pin Node 19 to 19.7.0 (#16987) (Milos Djermanovic) +* [`6157d81`](https://github.com/eslint/eslint/commit/6157d813e19b80481a46f8cbdf9eae18a55e5619) docs: Add example to guard-for-in docs. (#16983) (alope107) +* [`fd47998`](https://github.com/eslint/eslint/commit/fd47998af6efadcdf5ba93e0bd1f4c02d97d22b3) docs: update `Array.prototype.toSorted` specification link (#16982) (Milos Djermanovic) +* [`3e1cf6b`](https://github.com/eslint/eslint/commit/3e1cf6bfc5ebc29314ddbe462d6cb580e9ab085c) docs: Copy edits on Maintain ESLint docs (#16939) (Ben Perlmutter) + v8.36.0 - March 10, 2023 * [`602b111`](https://github.com/eslint/eslint/commit/602b11121910a97ab2bc4a95a46dd0ccd0a89309) chore: upgrade @eslint/js@8.36.0 (#16978) (Milos Djermanovic) From 4c46fb3d861ca12e86f868af19778ce988238da7 Mon Sep 17 00:00:00 2001 From: ESLint Jenkins Date: Tue, 28 Mar 2023 18:46:38 -0400 Subject: [PATCH 43/48] 8.37.0 --- docs/package.json | 2 +- docs/src/_data/rules.json | 4 ++-- docs/src/_data/rules_meta.json | 5 +++-- docs/src/use/formatters/html-formatter-example.html | 2 +- package.json | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/package.json b/docs/package.json index 64125d3954b..ab5bb049536 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.36.0", + "version": "8.37.0", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 27280e44935..2046ccd1450 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -588,7 +588,7 @@ }, { "name": "logical-assignment-operators", - "description": "Require or disallow logical assignment logical operator shorthand", + "description": "Require or disallow logical assignment operator shorthand", "recommended": false, "fixable": true, "hasSuggestions": true @@ -1382,7 +1382,7 @@ "description": "Enforce the use of `u` flag on RegExp", "recommended": false, "fixable": false, - "hasSuggestions": false + "hasSuggestions": true }, { "name": "require-yield", diff --git a/docs/src/_data/rules_meta.json b/docs/src/_data/rules_meta.json index 8791b0048f8..4242852b68d 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -534,7 +534,7 @@ "logical-assignment-operators": { "type": "suggestion", "docs": { - "description": "Require or disallow logical assignment logical operator shorthand", + "description": "Require or disallow logical assignment operator shorthand", "recommended": false, "url": "https://eslint.org/docs/rules/logical-assignment-operators" }, @@ -2261,7 +2261,8 @@ "description": "Enforce the use of `u` flag on RegExp", "recommended": false, "url": "https://eslint.org/docs/rules/require-unicode-regexp" - } + }, + "hasSuggestions": true }, "require-yield": { "type": "suggestion", diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index 0e3b6f25cce..2bd483aa90f 100644 --- a/docs/src/use/formatters/html-formatter-example.html +++ b/docs/src/use/formatters/html-formatter-example.html @@ -118,7 +118,7 @@

ESLint Report

- 9 problems (5 errors, 4 warnings) - Generated on Fri Mar 10 2023 17:15:51 GMT-0500 (Eastern Standard Time) + 9 problems (5 errors, 4 warnings) - Generated on Tue Mar 28 2023 18:46:38 GMT-0400 (Eastern Daylight Time)
diff --git a/package.json b/package.json index dab4da10637..e593df9971c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "8.36.0", + "version": "8.37.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { From 1c1ece26d1da61e523b83dda25353ec9379eb6c9 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 31 Mar 2023 05:18:28 +0200 Subject: [PATCH 44/48] fix: do not report on `RegExp(...args)` in `require-unicode-regexp` (#17037) * fix: do not report on `RegExp(...args)` in `require-unicode-regexp` * Add valid test `RegExp(...patternAndFlags)` --- lib/rules/require-unicode-regexp.js | 4 ++++ tests/lib/rules/require-unicode-regexp.js | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/lib/rules/require-unicode-regexp.js b/lib/rules/require-unicode-regexp.js index 2fe1539cfcc..943137cb5d6 100644 --- a/lib/rules/require-unicode-regexp.js +++ b/lib/rules/require-unicode-regexp.js @@ -78,6 +78,10 @@ module.exports = { for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { const [patternNode, flagsNode] = refNode.arguments; + + if (patternNode && patternNode.type === "SpreadElement") { + continue; + } const pattern = getStringIfConstant(patternNode, scope); const flags = getStringIfConstant(flagsNode, scope); diff --git a/tests/lib/rules/require-unicode-regexp.js b/tests/lib/rules/require-unicode-regexp.js index 22ada7aacf6..a75f6863168 100644 --- a/tests/lib/rules/require-unicode-regexp.js +++ b/tests/lib/rules/require-unicode-regexp.js @@ -29,6 +29,7 @@ ruleTester.run("require-unicode-regexp", rule, { "new RegExp('', 'u')", "RegExp('', 'gimuy')", "RegExp('', `gimuy`)", + "RegExp(...patternAndFlags)", "new RegExp('', 'gimuy')", "const flags = 'u'; new RegExp('', flags)", "const flags = 'g'; new RegExp('', flags + 'u')", @@ -36,6 +37,7 @@ ruleTester.run("require-unicode-regexp", rule, { "new RegExp('', flags)", "function f(flags) { return new RegExp('', flags) }", "function f(RegExp) { return new RegExp('foo') }", + "function f(patternAndFlags) { return new RegExp(...patternAndFlags) }", { code: "new globalThis.RegExp('foo')", env: { es6: true } }, { code: "new globalThis.RegExp('foo')", env: { es2017: true } }, { code: "new globalThis.RegExp('foo', 'u')", env: { es2020: true } }, @@ -77,6 +79,13 @@ ruleTester.run("require-unicode-regexp", rule, { ] }] }, + { + code: "RegExp()", + errors: [{ + messageId: "requireUFlag", + suggestions: null + }] + }, { code: "RegExp('foo')", errors: [{ From 24206c49a138d4390f815ae122ee12f564bc604b Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Sat, 1 Apr 2023 08:05:58 +0000 Subject: [PATCH 45/48] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 040654c1424..f0df03f0239 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

Chrome Frameworks Fund Automattic

Gold Sponsors

RIDI Salesforce Airbnb

Silver Sponsors

-

Sentry Liftoff American Express

Bronze Sponsors

+

Sentry American Express

Bronze Sponsors

PayDay Say ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition HeroCoders QuickBooks Tool hub

From 518130ae79a16d7bf4d752c211ae88152cc5a6f0 Mon Sep 17 00:00:00 2001 From: Percy Ma Date: Sun, 2 Apr 2023 07:11:01 +0800 Subject: [PATCH 46/48] docs: switch language based on current path (#16687) * docs: switch language based on current path ref https://github.com/eslint/eslint.org/pull/380 * fix: redirect to latest when version isn't latest * Update docs/src/_includes/components/language-switcher.html Co-authored-by: Milos Djermanovic --------- Co-authored-by: Milos Djermanovic --- docs/src/_data/sites/en.yml | 1 + docs/src/_data/sites/zh-hans.yml | 1 + docs/src/_includes/components/language-switcher.html | 8 ++++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/src/_data/sites/en.yml b/docs/src/_data/sites/en.yml index 7c7de804bde..ccd87ad37bd 100644 --- a/docs/src/_data/sites/en.yml +++ b/docs/src/_data/sites/en.yml @@ -85,6 +85,7 @@ footer: description: Selecting a language will take you to the ESLint website in that language. change_language: Change Language language: Language + latest: Latest copyright: > © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. links: diff --git a/docs/src/_data/sites/zh-hans.yml b/docs/src/_data/sites/zh-hans.yml index 0bff9291937..efa9474b89d 100644 --- a/docs/src/_data/sites/zh-hans.yml +++ b/docs/src/_data/sites/zh-hans.yml @@ -83,6 +83,7 @@ footer: description: 切换到你所选择语言版本对应的 ESLint 网站。 change_language: 更改语言 language: 语言 + latest: 最新 copyright: > © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. links: diff --git a/docs/src/_includes/components/language-switcher.html b/docs/src/_includes/components/language-switcher.html index aef1c2aa956..629b9dd022b 100644 --- a/docs/src/_includes/components/language-switcher.html +++ b/docs/src/_includes/components/language-switcher.html @@ -14,9 +14,13 @@ From da8d52a9d4edd9b2016cd4a15cd78f1ddadf20c7 Mon Sep 17 00:00:00 2001 From: Ahmadou Waly NDIAYE Date: Sun, 2 Apr 2023 05:35:02 +0000 Subject: [PATCH 47/48] docs: Update the second object instance for the "no-new" rule (#17020) * docs: Removed the second object instance for the "no-new" rule * docs: re-define the function name --- docs/src/rules/no-new.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/rules/no-new.md b/docs/src/rules/no-new.md index 9eeda70095a..c8cea29cb0f 100644 --- a/docs/src/rules/no-new.md +++ b/docs/src/rules/no-new.md @@ -43,7 +43,7 @@ Examples of **correct** code for this rule: var thing = new Thing(); -Thing(); +Foo(); ``` ::: From b0f11cf977a4180bf7c3042e7faeaaa067ffafd0 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Sun, 2 Apr 2023 08:06:12 +0000 Subject: [PATCH 48/48] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f0df03f0239..040654c1424 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

Chrome Frameworks Fund Automattic

Gold Sponsors

RIDI Salesforce Airbnb

Silver Sponsors

-

Sentry American Express

Bronze Sponsors

+

Sentry Liftoff American Express

Bronze Sponsors

PayDay Say ThemeIsle Nx (by Nrwl) Anagram Solver Icons8: free icons, photos, illustrations, and music Discord Transloadit Ignition HeroCoders QuickBooks Tool hub