profile
viewpoint

humanwhocodes/computer-science-in-javascript 6976

Collection of classic computer science paradigms, algorithms, and approaches written in JavaScript.

nzakas/cssembed 396

A tool for embedding data URIs in CSS files.

nzakas/cssurl 185

A Node.js utility for safely rewriting URLs in CSS code

billtrik/karma-fixture 77

A plugin for the Karma test runner that loads .html and .json fixtures

nzakas/buildr 47

A project to create macros for common web-related utilities

humanwhocodes/momoa 38

A JSON parser, tokenizer, traverser, and printer.

nzakas/combiner 31

A tool for combining JavaScript and CSS files based on embedded comments.

nzakas/debug-errorpage 27

An error page for use while developing Express apps

nzakas/jstools 23

General collection of useful JavaScript tools

nzakas/datauri 16

A utility for generating data URIs from existing files.

issue commenteslint/eslint

Support for "message" with "patterns" in `no-restricted-imports`

Folks, no need to leave a comment to express interest. There’s definitely enough interest. What we need now is someone to figure out if it’s possible to create a rule schema to validate such options while maintaining backwards compatibility.

mplis

comment created time in 4 days

issue commentestree/estree

Standardize Comment Types

While I think the idea of standardizing this has merit, in practice existing parsers won’t be able to make any changes due to compatibility issues. ESLint, for example, couldn’t make a change to the nodes we use because there is already a large ecosystem depending on what we already produce. (Not to mention compatible parsers like babel-eslint that would also need to change.)

GuyLewin

comment created time in 4 days

issue commenteslint/eslint

[no-duplicate-imports] Treat namespace imports as different

I agree, this seems like a bug.

G-Rath

comment created time in 4 days

issue openedeslint/eslint

Update eslint:recommended

<!-- ESLint adheres to the JS Foundation Code of Conduct.

This template is for requesting a change that is not a bug fix, rule change, or new rule. If you are here for another reason, please see below:

1. To report a bug: https://eslint.org/docs/developer-guide/contributing/reporting-bugs
2. To request a rule change: https://eslint.org/docs/developer-guide/contributing/rule-changes
3. To propose a new rule: https://eslint.org/docs/developer-guide/contributing/new-rules
4. If you have any questions, please stop by our chatroom: https://gitter.im/eslint/eslint

Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.

-->

For v7.0.0, we'd like to enable these rules for eslint:recommended:

  • no-import-assign
  • no-dupe-if
  • no-setter-return

created time in 4 days

CommitCommentEvent

Pull request review commenteslint/espree

Update: add latestEcmaVersion() & supportedEcmaVersions()

+/**+ * @fileoverview A collection of methods for processing Espree's options.+ * @author Kai Cataldo+ */++"use strict";++//------------------------------------------------------------------------------+// Helpers+//------------------------------------------------------------------------------++const DEFAULT_ECMA_VERSION = 5;+const SUPPORTED_VERSIONS = [+    3,+    5,+    6,+    7,+    8,+    9,+    10,+    11+];++/**+ * Normalize ECMAScript version from the initial config+ * @param {number} ecmaVersion ECMAScript version from the initial config+ * @throws {Error} throws an error if the ecmaVersion is invalid.+ * @returns {number} normalized ECMAScript version+ */+function normalizeEcmaVersion(ecmaVersion = DEFAULT_ECMA_VERSION) {+    if (typeof ecmaVersion !== "number") {+        throw new Error(`ecmaVersion must be a number. Received value of type ${typeof ecmaVersion} instead.`);+    }++    let version = ecmaVersion;++    // Calculate ECMAScript edition number from official year version starting with+    // ES2015, which corresponds with ES6 (or a difference of 2009).+    if (version >= 2015) {+        version -= 2009;+    }++    if (!SUPPORTED_VERSIONS.includes(version)) {+        throw new Error("Invalid ecmaVersion.");+    }++    return version;+}++/**+ * Normalize sourceType from the initial config+ * @param {string} sourceType to normalize+ * @throws {Error} throw an error if sourceType is invalid+ * @returns {string} normalized sourceType+ */+function normalizeSourceType(sourceType = "script") {+    if (sourceType === "script" || sourceType === "module") {+        return sourceType;+    }+    throw new Error("Invalid sourceType.");+}++/**+ * Normalize parserOptions+ * @param {Object} options the parser options to normalize+ * @throws {Error} throw an error if found invalid option.+ * @returns {Object} normalized options+ */+function normalizeOptions(options) {+    const ecmaVersion = normalizeEcmaVersion(options.ecmaVersion);+    const sourceType = normalizeSourceType(options.sourceType);+    const ranges = options.range === true;+    const locations = options.loc === true;++    if (sourceType === "module" && ecmaVersion < 6) {+        throw new Error("sourceType 'module' is not supported when ecmaVersion < 2015. Consider adding `{ ecmaVersion: 2015 }` to the parser options.");+    }+    return Object.assign({}, options, { ecmaVersion, sourceType, ranges, locations });+}++/**+ * Get the latest ECMAScript version supported by Espree.+ * @returns {number} The latest ECMAScript version.+ */+function latestEcmaVersion() {+    return Math.max(...SUPPORTED_VERSIONS);

You can avoid a calculation here by using SUPPORTED_VERSIONS[SUPPORTED_VERSIONS.length - 1]

kaicataldo

comment created time in 5 days

Pull request review commenteslint/espree

Update: add latestEcmaVersion() & supportedEcmaVersions()

+/**+ * @fileoverview A collection of methods for processing Espree's options.+ * @author Kai Cataldo+ */++"use strict";++//------------------------------------------------------------------------------+// Helpers+//------------------------------------------------------------------------------++const DEFAULT_ECMA_VERSION = 5;+const SUPPORTED_VERSIONS = [+    3,+    5,+    6,+    7,+    8,+    9,+    10,+    11+];++/**+ * Normalize ECMAScript version from the initial config+ * @param {number} ecmaVersion ECMAScript version from the initial config+ * @throws {Error} throws an error if the ecmaVersion is invalid.+ * @returns {number} normalized ECMAScript version+ */+function normalizeEcmaVersion(ecmaVersion = DEFAULT_ECMA_VERSION) {+    if (typeof ecmaVersion !== "number") {+        throw new Error(`ecmaVersion must be a number. Received value of type ${typeof ecmaVersion} instead.`);+    }++    let version = ecmaVersion;++    // Calculate ECMAScript edition number from official year version starting with+    // ES2015, which corresponds with ES6 (or a difference of 2009).+    if (version >= 2015) {+        version -= 2009;+    }++    if (!SUPPORTED_VERSIONS.includes(version)) {+        throw new Error("Invalid ecmaVersion.");+    }++    return version;+}++/**+ * Normalize sourceType from the initial config+ * @param {string} sourceType to normalize+ * @throws {Error} throw an error if sourceType is invalid+ * @returns {string} normalized sourceType+ */+function normalizeSourceType(sourceType = "script") {+    if (sourceType === "script" || sourceType === "module") {+        return sourceType;+    }+    throw new Error("Invalid sourceType.");+}++/**+ * Normalize parserOptions+ * @param {Object} options the parser options to normalize+ * @throws {Error} throw an error if found invalid option.+ * @returns {Object} normalized options+ */+function normalizeOptions(options) {+    const ecmaVersion = normalizeEcmaVersion(options.ecmaVersion);+    const sourceType = normalizeSourceType(options.sourceType);+    const ranges = options.range === true;+    const locations = options.loc === true;++    if (sourceType === "module" && ecmaVersion < 6) {+        throw new Error("sourceType 'module' is not supported when ecmaVersion < 2015. Consider adding `{ ecmaVersion: 2015 }` to the parser options.");+    }+    return Object.assign({}, options, { ecmaVersion, sourceType, ranges, locations });+}++/**+ * Get the latest ECMAScript version supported by Espree.+ * @returns {number} The latest ECMAScript version.+ */+function latestEcmaVersion() {+    return Math.max(...SUPPORTED_VERSIONS);+}++/**+ * Get the list of ECMAScript versions supported by Espree.+ * @returns {number[]} An array containing the supported ECMAScript versions.+ */+function supportedEcmaVersions() {

Similarly, getSupportedEcmaVersions.

kaicataldo

comment created time in 5 days

Pull request review commenteslint/espree

Update: add latestEcmaVersion() & supportedEcmaVersions()

+/**+ * @fileoverview A collection of methods for processing Espree's options.+ * @author Kai Cataldo+ */++"use strict";++//------------------------------------------------------------------------------+// Helpers+//------------------------------------------------------------------------------++const DEFAULT_ECMA_VERSION = 5;+const SUPPORTED_VERSIONS = [+    3,+    5,+    6,+    7,+    8,+    9,+    10,+    11+];++/**+ * Normalize ECMAScript version from the initial config+ * @param {number} ecmaVersion ECMAScript version from the initial config+ * @throws {Error} throws an error if the ecmaVersion is invalid.+ * @returns {number} normalized ECMAScript version+ */+function normalizeEcmaVersion(ecmaVersion = DEFAULT_ECMA_VERSION) {+    if (typeof ecmaVersion !== "number") {+        throw new Error(`ecmaVersion must be a number. Received value of type ${typeof ecmaVersion} instead.`);+    }++    let version = ecmaVersion;++    // Calculate ECMAScript edition number from official year version starting with+    // ES2015, which corresponds with ES6 (or a difference of 2009).+    if (version >= 2015) {+        version -= 2009;+    }++    if (!SUPPORTED_VERSIONS.includes(version)) {+        throw new Error("Invalid ecmaVersion.");+    }++    return version;+}++/**+ * Normalize sourceType from the initial config+ * @param {string} sourceType to normalize+ * @throws {Error} throw an error if sourceType is invalid+ * @returns {string} normalized sourceType+ */+function normalizeSourceType(sourceType = "script") {+    if (sourceType === "script" || sourceType === "module") {+        return sourceType;+    }+    throw new Error("Invalid sourceType.");+}++/**+ * Normalize parserOptions+ * @param {Object} options the parser options to normalize+ * @throws {Error} throw an error if found invalid option.+ * @returns {Object} normalized options+ */+function normalizeOptions(options) {+    const ecmaVersion = normalizeEcmaVersion(options.ecmaVersion);+    const sourceType = normalizeSourceType(options.sourceType);+    const ranges = options.range === true;+    const locations = options.loc === true;++    if (sourceType === "module" && ecmaVersion < 6) {+        throw new Error("sourceType 'module' is not supported when ecmaVersion < 2015. Consider adding `{ ecmaVersion: 2015 }` to the parser options.");+    }+    return Object.assign({}, options, { ecmaVersion, sourceType, ranges, locations });+}++/**+ * Get the latest ECMAScript version supported by Espree.+ * @returns {number} The latest ECMAScript version.+ */+function latestEcmaVersion() {

I’d suggest calling this getLatestEcmaVersion to indicate a value is being retrieved (verb as opposed to noun).

kaicataldo

comment created time in 5 days

issue commenteslint/tsc-meetings

TSC meeting 13-February-2020

Awesome! Please be sure to add any issues/PRs to the 7.0.0 board that you’d like to propose to be included in the release. Our goal is to lock down the feature set in this meeting.

eslint[bot]

comment created time in 5 days

pull request commenteslint/eslint

Update: Check for unknown error object keys in RuleTester

There hasn’t been any activity on this for two months. Are we still planning on doing this?

mdjermanovic

comment created time in 6 days

Pull request review commenteslint/eslint

Fix: `--no-ignore` does not work with `--stdin-filename` (fixes #12745)

 class CLIEngine {     /**      * Checks if a given path is ignored by ESLint.      * @param {string} filePath The path of the file to check.+     * @param {boolean} isDot is the file a dot file.      * @returns {boolean} Whether or not the given path is ignored.      */-    isPathIgnored(filePath) {+    isPathIgnored(filePath, isDot) {

This is part of our published API so it would be preferable not to change the function signature.

karthikkp

comment created time in 6 days

Pull request review commenteslint/eslint

Fix: `--no-ignore` does not work with `--stdin-filename` (fixes #12745)

 class CLIEngine {         // Clear the last used config arrays.         lastConfigArrays.length = 0; -        if (resolvedFilename && this.isPathIgnored(resolvedFilename)) {+        const dotfilesPattern = /(?:(?:^\.)|(?:[/\\]\.))[^/\\.].*/u;

Is there a reason this logic can’t go into isPathIgnored()?

karthikkp

comment created time in 6 days

issue commenteslint/tsc-meetings

TSC meeting 13-February-2020

I’ll be there as well.

eslint[bot]

comment created time in 6 days

issue commenteslint/website

A cookie associated with a resource at http://www.netlify.com/ was set with `SameSite=None` but without `Secure`

Hmm. On my phone right now, but maybe the Netlify badge at the bottom is the culprit?

aladdin-add

comment created time in 7 days

PR closed eslint/eslint

New: add prefer-async-await rule accepted feature rule

Hey all! My first PR to eslint. Thanks so far for this amazing piece of work :raised_hands:

Taking over PR #11033. All changes mentioned by @not-an-aardvark are added.

Fixes #9649

What is the purpose of this pull request? (put an "X" next to item)

[x] New rule

Please describe what the rule should do:

The rule should suggest to replace promise-based (then) solutions with async/await

What category of rule is this? (place an "X" next to just one item)

[x] Enforces code style [x] Suggests an alternate way of doing something

Why should this rule be included in ESLint (instead of a plugin)?

Preferring async/await over promises is a major step for writing modern JavaScript (in a ES2017+ or transpiled environment). Thus, the rule should be part of the core.

What changes did you make? (Give an overview)

I've adopted the content from #11033 and applied change requests from there.

Is there anything you'd like reviewers to focus on?

Potential edge cases, suggestions for further coverage

+157 -0

14 comments

5 changed files

manniL

pr closed time in 10 days

pull request commenteslint/eslint

New: add prefer-async-await rule

It seems that there’s a consensus to not include the rule, so closing. While we wish we'd be able to accommodate everyone's requests, we do need to prioritize. This doesn't mean the idea isn't interesting or useful, just that it's not something the team can commit to. Thanks!

manniL

comment created time in 10 days

issue commenteslint/website

A cookie associated with a resource at http://www.netlify.com/ was set with `SameSite=None` but without `Secure`

Can you explain more about this? Were you viewing eslint.org?

Any steps to reproduce would be helpful.

aladdin-add

comment created time in 10 days

pull request commenteslint/eslint

Automatically remove `eslint-plugin-` from ruleId's in linter.js

As this would require an RFC and there hasn't been any response five months, I'm closing this pull request. If anyone is interested in taking up this change, the next step would be to create an RFC.

Validark

comment created time in 10 days

PR closed eslint/eslint

Automatically remove `eslint-plugin-` from ruleId's in linter.js core enhancement evaluating needs design

<!-- ESLint adheres to the JS Foundation Code of Conduct. -->

What is the purpose of this pull request? (put an "X" next to item)

[ ] Documentation update [ ] Bug fix (template) [ ] New rule (template) [ ] Changes an existing rule (template) [x] Add autofixing to a rule [ ] Add a CLI option [ ] Add something to the core [ ] Other, please explain:

<!-- If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.) -->

<!-- Please ensure your pull request is ready:

- Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
- Include tests for this change
- Update documentation for this change (if appropriate)

-->

<!-- The following is required for all pull requests: -->

What changes did you make? (Give an overview) I made it so if a person defines a rule and forgets to remove "eslint-plugin-", it will still work.

If this PR is decided against, I request that a special error message be added so that people who make this mistake will have explicit instructions telling them how to fix their code.

+3 -1

7 comments

1 changed file

Validark

pr closed time in 10 days

pull request commenteslint/eslint

New: Add switch-final-break rule

@eranshabi are you still working on this?

eranshabi

comment created time in 10 days

pull request commenteslint/eslint

Update: handle undefined as ambiguous in consistent-return

I'd also like some clarification as I'm not entirely sure what the goal is here. I understand a desire to treat return, return void foo, and return undefined as different, but I'm having trouble understanding why.

I think the easiest way to get some clarity here is to add some tests for this PR so we can see the patterns that catches and how it differs from what's already in the rule.

skeggse

comment created time in 10 days

pull request commenteslint/eslint

Fix: no-new-wrappers reports only wrappers

I'd recommend we merge this PR and update no-obj-calls to cover Math and JSON.

mysticatea

comment created time in 10 days

pull request commenteslint/eslint

Fix: Optionally allow underscores in member names

@eaviles Just following up to see if you're still planning on updating this?

eaviles

comment created time in 10 days

pull request commenteslint/eslint

New: add prefer-async-await rule

While I can see why some might want a rule like this, I don't think it rises to level of importance to be included in ESLint by default. I'd recommend publishing the rule in a plugin instead.

manniL

comment created time in 10 days

pull request commenteslint/eslint

Fix: Change error message logic for implicit file ignore (fixes #12873)

@kaicataldo can you be more specific with your feedback? Are you requesting a change in this PR or just asking for an opinion?

snhardin

comment created time in 10 days

push eventeslint/eslint

Kai Cataldo

commit sha 1907e57362f7d5f7a02a5a78f24ac3347f868e93

Chore: add Twitter and Open Collective badge (#12877) * Chore: add Open Collective badge * Add Twitter badge * Split Open Collective badges into backers and sponsors * Update README.md * Reorder once more * Fix typo

view details

push time in 11 days

delete branch eslint/eslint

delete branch : kaicataldo-oc-badge

delete time in 11 days

PR merged eslint/eslint

Chore: add Twitter and Open Collective badge chore

<!-- Thank you for contributing!

ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).

-->

Prerequisites checklist

  • [x] I have read the contributing guidelines.
  • [x] The team has reached consensus on the changes proposed in this pull request. If not, I understand that the evaluation process will begin with this pull request and won't be merged until the team has reached consensus.

What is the purpose of this pull request? (put an "X" next to an item)

[ ] Documentation update [ ] Bug fix (template) [ ] New rule (template) [ ] Changes an existing rule (template) [ ] Add autofixing to a rule [ ] Add a CLI option [ ] Add something to the core [x] Other, please explain:

<!-- If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.) --> Chore <!-- Please ensure your pull request is ready:

- Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
- Include tests for this change
- Update documentation for this change (if appropriate)

-->

<!-- The following is required for all pull requests: -->

What changes did you make? (Give an overview)

This PR adds an Open Collective badge and reorders the badges in groupings that I think make more sense.

Is there anything you'd like reviewers to focus on?

  1. Does this make sense?
  2. Do we need the bounty source badge? I've never actually seen a bounty paid out, and it's adding noise to the line of current more-relevant badges.
+7 -10

0 comment

1 changed file

kaicataldo

pr closed time in 11 days

PR closed eslint/eslint

Disable `functions` option if used object option, and `ecmaVersion: 5` enhancement evaluating rule

This change leads to document behavior:

functions should only be enabled when linting ECMAScript 2017 or higher.

<!-- ESLint adheres to the JS Foundation Code of Conduct. -->

What is the purpose of this pull request? (put an "X" next to item)

[ ] Documentation update [x] Bug fix (template) [ ] New rule (template) [ ] Changes an existing rule (template) [ ] Add autofixing to a rule [ ] Add a CLI option [ ] Add something to the core [ ] Other, please explain: Tell us about your environment

  • ESLint Version: 6.6.0
  • Node Version: 12.13.0
  • npm Version: 6.12.0
  • yarn Version: 1.19.1

What parser (default, Babel-ESLint, etc.) are you using? Default with "ecmaVersion": 5 Please show your full configuration:

<details> <summary>Configuration</summary>

module.exports = {
    'parserOptions': {
        'ecmaVersion': 5,
    },
    rules: {
        'comma-dangle': ['error', {// actually used from airbnb config
            arrays: 'always-multiline',
            objects: 'always-multiline',
            imports: 'always-multiline',
            exports: 'always-multiline',
            functions: 'always-multiline',
        }],
    },
};

</details>

What did you do? Please include the actual source code causing the issue.

function foo(a, b) {
    console.log(a, b);
}
foo(
    1,
    2// dont need coma at here, because `'ecmaVersion': 5,`
);

What did you expect to happen? Dont trigger error

What actually happened? Please include the actual, raw output from ESLint.

C:\Projects\github.com\forks\test-code\1.js
  7:6  error  Missing trailing comma  comma-dangle

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

What changes did you make? (Give an overview) Pre-cast options to the object format Then fixed functions for the case 'ecmaVersion': < 8, This did not occur previously if the option was specified in the object format.

Is there anything you'd like reviewers to focus on?

+21 -12

6 comments

2 changed files

Gvozd

pr closed time in 12 days

pull request commenteslint/eslint

Disable `functions` option if used object option, and `ecmaVersion: 5`

I agree. This is a pattern we’ve tried to avoid for all the reasons already discussed.

Thanks for the suggestion and discussion.

Gvozd

comment created time in 12 days

push eventeslint/eslint

Milos Djermanovic

commit sha 95e0586c95e6953d11983d1d11891ed30318109a

Fix: id-blacklist false positives on renamed imports (#12831)

view details

push time in 12 days

delete branch eslint/eslint

delete branch : idblacklist-renamedimports

delete time in 12 days

PR merged eslint/eslint

Fix: id-blacklist false positives on renamed imports accepted bug rule

<!-- Thank you for contributing!

ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).

-->

Prerequisites checklist

  • [X] I have read the contributing guidelines.
  • [X] The team has reached consensus on the changes proposed in this pull request. If not, I understand that the evaluation process will begin with this pull request and won't be merged until the team has reached consensus.

What is the purpose of this pull request? (put an "X" next to item)

[X] Bug fix

<!-- If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.) -->

Tell us about your environment

  • ESLint Version: 7.0.0-alpha.0
  • Node Version: v12.14.0
  • npm Version: v6.13.4

What parser (default, Babel-ESLint, etc.) are you using?

default

Please show your full configuration:

<details> <summary>Configuration</summary>

<!-- Paste your configuration below: -->

module.exports = {
    parserOptions: {
        ecmaVersion: 2015,
        sourceType: "module"
    },
};

</details>

What did you do? Please include the actual source code causing the issue.

/* eslint id-blacklist: ["error", "foo"] */

import { foo as bar } from "mod";
export { foo as bar } from "mod";

import { foo } from "mod";

Online Demo Link - the actual demo is v6.8.0, but it's the same behavior regarding this issue.

What did you expect to happen?

Not to report foo in import { foo as bar } and export { foo as bar }.

This is a stylistic rule and shouldn't restrict imports of existing external names (and thus basically their use, that's the responsibility of the no-restricted-imports rule).

On the other hand, the rule should report import { foo } and enforce renaming, but it shouldn't report the same identifier twice.

What actually happened? Please include the actual, raw output from ESLint.

  3:10  error  Identifier 'foo' is blacklisted  id-blacklist
  4:10  error  Identifier 'foo' is blacklisted  id-blacklist
  6:10  error  Identifier 'foo' is blacklisted  id-blacklist
  6:10  error  Identifier 'foo' is blacklisted  id-blacklist

These are two different bugs:

  • The first two errors are false positives.
  • One of the other two errors is a duplicate.

<!-- Please ensure your pull request is ready:

- Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
- Include tests for this change
- Update documentation for this change (if appropriate)

-->

<!-- The following is required for all pull requests: -->

What changes did you make? (Give an overview)

  • Fixed the rule to not report renamed imports/re-exports.
  • Fixed the rule to avoid reporting same nodes multiple times.
  • Refactoring
    • if (node.parent.type === "Property") branch was same as the next branch, so it's removed.
    • effectiveParent was different than node.parent just for member expressions, so it's moved to that branch to simplify the rest of the code.

Is there anything you'd like reviewers to focus on?

  • Refactoring, did I miss something.
  • Local names in exports (not re-exports) will be still reported:
/* eslint id-blacklist: ["error", "foo"] */

var foo; // error

export { foo as bar }; // also error, although it's renamed here

That seemed consistent with how this rule works: it reports all references, not just declarations.

+317 -29

0 comment

2 changed files

mdjermanovic

pr closed time in 12 days

Pull request review commenteslint/eslint

Chore: add Twitter and Open Collective badge

 ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J 5. [Frequently Asked Questions](#faq) 6. [Releases](#releases) 7. [Semantic Versioning Policy](#semantic-versioning-policy)-8. [License](#license)+8. [License](#license

Oops!

kaicataldo

comment created time in 12 days

issue commenteslint/tsc-meetings

TSC meeting 13-February-2020

Can everyone please confirm if you are going to attend before the day of the meeting? Just leave a comment on this issue saying you're planning to attend. Thanks!

eslint[bot]

comment created time in 12 days

push eventeslint/eslint

Arthur Denner

commit sha 824d23585c205f2993716585cb6f55dfbe4a33f0

Docs: add errorOnUnmatchedPattern option to CLIEngine (#12834)

view details

push time in 12 days

PR merged eslint/eslint

Docs: add errorOnUnmatchedPattern option to CLIEngine documentation

<!-- Thank you for contributing!

ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).

-->

Prerequisites checklist

  • [x] I have read the contributing guidelines.
  • [ ] The team has reached consensus on the changes proposed in this pull request. If not, I understand that the evaluation process will begin with this pull request and won't be merged until the team has reached consensus.

What is the purpose of this pull request? (put an "X" next to item)

  • [x] Documentation update
  • [ ] Bug fix (template)
  • [ ] New rule (template)
  • [ ] Changes an existing rule (template)
  • [ ] Add autofixing to a rule
  • [ ] Add a CLI option
  • [ ] Add something to the core
  • [ ] Other, please explain:

<!-- If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.) -->

<!-- Please ensure your pull request is ready:

- Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
- Include tests for this change
- Update documentation for this change (if appropriate)

-->

<!-- The following is required for all pull requests: -->

What changes did you make? (Give an overview)

Documented the new errorOnUnmatchedPattern option on CLIEngine.

Is there anything you'd like reviewers to focus on?

Nope.

+1 -0

0 comment

1 changed file

arthurdenner

pr closed time in 12 days

PR opened eslint/website

Fix: Show platinum sponsors on homepage accepted bug

This PR adds platinum sponsor logos to the homepage. This wasn't working before due to several missing pieces.

+8 -2

0 comment

3 changed files

pr created time in 12 days

create barncheslint/website

branch : issue691

created branch time in 12 days

issue commenteslint/eslint

`yoda` overlooks template literal

There's consensus on this change, so marking as accepted.

yeonjuan

comment created time in 12 days

issue openedeslint/website

Platinum sponsor logos not shown on homepage

It looks like we missed adding new platinum sponsors to the homepage. We are pulling data about platinum sponsors but we aren't actually doing anything with it.

created time in 12 days

issue commenteslint/website

Node.js API anchor links are broken

This was fixed in https://github.com/eslint/eslint/commit/aea172998ec4e2af1d9186b6767c3f34428945f4

reduckted

comment created time in 12 days

issue closedeslint/website

Node.js API anchor links are broken

On the Node.JS API page, the links in the table of contents for the sub-sections are broken. For example, the link in the table of contents to the SourceCode.splitLines() method is:

https://eslint.org/docs/developer-guide/nodejs-api#sourcecodesplitlines

But the id of the element is sourcecode-splitlines. Clicking the "anchor" link next to the heading produces the correct URL.

Interestingly, the links work correctly when viewing the markdown file on GitHub. For example, this link goes to the correct section:

https://github.com/eslint/website/blob/master/docs/developer-guide/nodejs-api.md#sourcecodesplitlines

closed time in 12 days

reduckted

issue commenteslint/website

Can we bundle ESLint and Espree directly in this repository on build?

@kaicataldo has this been addressed? It looks like there were some PRs merged related to this, so just checking.

kaicataldo

comment created time in 12 days

issue closedeslint/website

`no-param-reassign`: `arguments` object is mutated anyhow

I was testing ESLint for the first time and I decided to use Airbnb style guide.

Let's use an example from the page:

function foo(bar) {
  var baz = bar;
}

It passes ESLint check.

But the main point for Disallowing Reassignment of Function Parameters is that we don't mutate arguments object unintentionally.

But I think that's only true if the bar is of primitive type. Because every time I pass a value of a complex type as the argument, arguments is mutated after var baz = bar;:

function foo(bar) {
  var baz = bar;
  baz.property1 = 5;
  return arguments[0];
}

foo({ property0: 2 }); // returns { property0: 2, property1: 5 }

I just got more curious when I read a comment with lots of points regarding the same issue, but for DOM. I tried the DOM objects too. The result is the same; arguments[0] is mutated.

Am I missing something? If it's true only for primitive types, then that'd be great to mention something about it on the docs. If it's a general thing, what about the behavior I'm experiencing? What's the point?

closed time in 12 days

mrmowji

issue commenteslint/website

`no-param-reassign`: `arguments` object is mutated anyhow

It looks like the conversation is stalled here. As this is a question rather than an action item, I'm closing the issue. If you still need help, please send a message to our mailing list or chatroom. Thanks!

mrmowji

comment created time in 12 days

issue closedeslint/website

Build failing

It looks like eslint.org hasn't successfully deployed in a couple of weeks. Here's the log:

<details> <summary>Netlify Build Log</summary> <pre> 7:10:47 AM: Build ready to start 7:10:49 AM: build-image version: 9cade8af58c2cf3a17a1e9433d2e979149488837 7:10:49 AM: build-image tag: v3.3.5 7:10:49 AM: buildbot version: 036f37945d6de439a17a554b3ae02e2f8f0f1fb0 7:10:49 AM: Fetching cached dependencies 7:10:49 AM: Starting to download cache of 153.7MB 7:10:51 AM: Finished downloading cache in 2.268544724s 7:10:51 AM: Starting to extract cache 7:10:59 AM: Finished extracting cache in 7.101883019s 7:10:59 AM: Finished fetching cache in 9.584823588s 7:10:59 AM: Starting to prepare the repo for build 7:10:59 AM: Preparing Git Reference refs/heads/master 7:11:00 AM: Starting build script 7:11:00 AM: Installing dependencies 7:11:01 AM: Started restoring cached node version 7:11:05 AM: Finished restoring cached node version 7:11:06 AM: v12.14.1 is already installed. 7:11:07 AM: Now using node v12.14.1 (npm v6.13.4) 7:11:07 AM: Attempting ruby version 2.6.2, read from environment 7:11:09 AM: Using ruby version 2.6.2 7:11:09 AM: Using PHP version 5.6 7:11:09 AM: Started restoring cached node modules 7:11:09 AM: Finished restoring cached node modules 7:11:10 AM: Started restoring cached go cache 7:11:10 AM: Finished restoring cached go cache 7:11:10 AM: unset GOOS; 7:11:10 AM: unset GOARCH; 7:11:10 AM: export GOROOT='/opt/buildhome/.gimme/versions/go1.12.linux.amd64'; 7:11:10 AM: export PATH="/opt/buildhome/.gimme/versions/go1.12.linux.amd64/bin:${PATH}"; 7:11:10 AM: go version >&2; 7:11:10 AM: export GIMME_ENV='/opt/buildhome/.gimme/env/go1.12.linux.amd64.env'; 7:11:10 AM: go version go1.12 linux/amd64 7:11:10 AM: Installing missing commands 7:11:10 AM: Verify run directory 7:11:10 AM: Executing user command: npm run build 7:11:11 AM: > eslint-website@1.0.0 build /opt/build/repo 7:11:11 AM: > npm run build:eleventy && npm run build:less && npm run build:webpack 7:11:11 AM: > eslint-website@1.0.0 build:eleventy /opt/build/repo 7:11:11 AM: > eleventy --quiet 7:11:56 AM: Copied 3 items / Wrote 3413 files in 44.08 seconds (12.9ms each, v0.10.0) 7:11:57 AM: > eslint-website@1.0.0 build:less /opt/build/repo 7:11:57 AM: > lessc src/styles/main.less _site/assets/styles/main.css --clean-css --source-map=_site/assets/styles/main.css.map 7:12:00 AM: > eslint-website@1.0.0 build:webpack /opt/build/repo 7:12:00 AM: > webpack --mode=production 7:12:12 AM: ERROR in ./src/js/demo/components/App.jsx 7:12:12 AM: Module not found: Error: Can't resolve '../node_modules/eslint/lib/linter/linter' in '/opt/build/repo/src/js/demo/components' 7:12:12 AM: @ ./src/js/demo/components/App.jsx 43:0-68 63:17-29 7:12:12 AM: @ ./src/js/demo/index.js 7:12:12 AM: ERROR in chunk demo [entry] 7:12:12 AM: demo.js 7:12:12 AM: /opt/build/repo/node_modules/babel-loader/lib/index.js??ref--4!/opt/build/repo/src/js/demo/index.js 8d9c08418e3c67681b9089441452bb1e 7:12:12 AM: Unexpected token (64:17) 7:12:12 AM: | }(); 7:12:12 AM: | 7:12:12 AM: | var linter = new !(function webpackMissingModule() { var e = new Error("Cannot find module '../node_modules/eslint/lib/linter/linter'"); e.code = 'MODULE_NOT_FOUND'; throw e; }()).Linter(); 7:12:12 AM: | var rules = linter.getRules(); 7:12:12 AM: | var ruleNames = Array.from(rules.keys()); 7:12:13 AM: npm 7:12:13 AM: ERR! code ELIFECYCLE 7:12:13 AM: npm 7:12:13 AM: ERR! 7:12:13 AM: errno 2 7:12:13 AM: npm 7:12:13 AM: ERR! eslint-website@1.0.0 build:webpack: webpack --mode=production 7:12:13 AM: npm 7:12:13 AM: ERR! Exit status 2 7:12:13 AM: npm 7:12:13 AM: ERR! 7:12:13 AM: npm ERR! 7:12:13 AM: Failed at the eslint-website@1.0.0 build:webpack script. 7:12:13 AM: npm 7:12:13 AM: ERR! This is probably not a problem with npm. There is likely additional logging output above. 7:12:13 AM: npm 7:12:13 AM: ERR! A complete log of this run can be found in: 7:12:13 AM: npm ERR! 7:12:13 AM: /opt/buildhome/.npm/_logs/2020-02-03T15_12_13_275Z-debug.log 7:12:13 AM: failed during stage 'building site': Build script returned non-zero exit code: 2 7:12:13 AM: npm 7:12:13 AM: ERR! 7:12:13 AM: code ELIFECYCLE 7:12:13 AM: npm 7:12:13 AM: ERR! errno 2 7:12:13 AM: Shutting down logging, 29 messages pending </pre> </details>

closed time in 12 days

nzakas

issue commenteslint/website

Build failing

closed by #690

nzakas

comment created time in 12 days

issue openedeslint/website

Build failing

It looks like eslint.org hasn't successfully deployed in a couple of weeks. Here's the log:

<details> <summary>Netlify Build Log</summary> <pre> 7:10:47 AM: Build ready to start 7:10:49 AM: build-image version: 9cade8af58c2cf3a17a1e9433d2e979149488837 7:10:49 AM: build-image tag: v3.3.5 7:10:49 AM: buildbot version: 036f37945d6de439a17a554b3ae02e2f8f0f1fb0 7:10:49 AM: Fetching cached dependencies 7:10:49 AM: Starting to download cache of 153.7MB 7:10:51 AM: Finished downloading cache in 2.268544724s 7:10:51 AM: Starting to extract cache 7:10:59 AM: Finished extracting cache in 7.101883019s 7:10:59 AM: Finished fetching cache in 9.584823588s 7:10:59 AM: Starting to prepare the repo for build 7:10:59 AM: Preparing Git Reference refs/heads/master 7:11:00 AM: Starting build script 7:11:00 AM: Installing dependencies 7:11:01 AM: Started restoring cached node version 7:11:05 AM: Finished restoring cached node version 7:11:06 AM: v12.14.1 is already installed. 7:11:07 AM: Now using node v12.14.1 (npm v6.13.4) 7:11:07 AM: Attempting ruby version 2.6.2, read from environment 7:11:09 AM: Using ruby version 2.6.2 7:11:09 AM: Using PHP version 5.6 7:11:09 AM: Started restoring cached node modules 7:11:09 AM: Finished restoring cached node modules 7:11:10 AM: Started restoring cached go cache 7:11:10 AM: Finished restoring cached go cache 7:11:10 AM: unset GOOS; 7:11:10 AM: unset GOARCH; 7:11:10 AM: export GOROOT='/opt/buildhome/.gimme/versions/go1.12.linux.amd64'; 7:11:10 AM: export PATH="/opt/buildhome/.gimme/versions/go1.12.linux.amd64/bin:${PATH}"; 7:11:10 AM: go version >&2; 7:11:10 AM: export GIMME_ENV='/opt/buildhome/.gimme/env/go1.12.linux.amd64.env'; 7:11:10 AM: go version go1.12 linux/amd64 7:11:10 AM: Installing missing commands 7:11:10 AM: Verify run directory 7:11:10 AM: Executing user command: npm run build 7:11:11 AM: > eslint-website@1.0.0 build /opt/build/repo 7:11:11 AM: > npm run build:eleventy && npm run build:less && npm run build:webpack 7:11:11 AM: > eslint-website@1.0.0 build:eleventy /opt/build/repo 7:11:11 AM: > eleventy --quiet 7:11:56 AM: Copied 3 items / Wrote 3413 files in 44.08 seconds (12.9ms each, v0.10.0) 7:11:57 AM: > eslint-website@1.0.0 build:less /opt/build/repo 7:11:57 AM: > lessc src/styles/main.less _site/assets/styles/main.css --clean-css --source-map=_site/assets/styles/main.css.map 7:12:00 AM: > eslint-website@1.0.0 build:webpack /opt/build/repo 7:12:00 AM: > webpack --mode=production 7:12:12 AM: ERROR in ./src/js/demo/components/App.jsx 7:12:12 AM: Module not found: Error: Can't resolve '../node_modules/eslint/lib/linter/linter' in '/opt/build/repo/src/js/demo/components' 7:12:12 AM: @ ./src/js/demo/components/App.jsx 43:0-68 63:17-29 7:12:12 AM: @ ./src/js/demo/index.js 7:12:12 AM: ERROR in chunk demo [entry] 7:12:12 AM: demo.js 7:12:12 AM: /opt/build/repo/node_modules/babel-loader/lib/index.js??ref--4!/opt/build/repo/src/js/demo/index.js 8d9c08418e3c67681b9089441452bb1e 7:12:12 AM: Unexpected token (64:17) 7:12:12 AM: | }(); 7:12:12 AM: | 7:12:12 AM: | var linter = new !(function webpackMissingModule() { var e = new Error("Cannot find module '../node_modules/eslint/lib/linter/linter'"); e.code = 'MODULE_NOT_FOUND'; throw e; }()).Linter(); 7:12:12 AM: | var rules = linter.getRules(); 7:12:12 AM: | var ruleNames = Array.from(rules.keys()); 7:12:13 AM: npm 7:12:13 AM: ERR! code ELIFECYCLE 7:12:13 AM: npm 7:12:13 AM: ERR! 7:12:13 AM: errno 2 7:12:13 AM: npm 7:12:13 AM: ERR! eslint-website@1.0.0 build:webpack: webpack --mode=production 7:12:13 AM: npm 7:12:13 AM: ERR! Exit status 2 7:12:13 AM: npm 7:12:13 AM: ERR! 7:12:13 AM: npm ERR! 7:12:13 AM: Failed at the eslint-website@1.0.0 build:webpack script. 7:12:13 AM: npm 7:12:13 AM: ERR! This is probably not a problem with npm. There is likely additional logging output above. 7:12:13 AM: npm 7:12:13 AM: ERR! A complete log of this run can be found in: 7:12:13 AM: npm ERR! 7:12:13 AM: /opt/buildhome/.npm/_logs/2020-02-03T15_12_13_275Z-debug.log 7:12:13 AM: failed during stage 'building site': Build script returned non-zero exit code: 2 7:12:13 AM: npm 7:12:13 AM: ERR! 7:12:13 AM: code ELIFECYCLE 7:12:13 AM: npm 7:12:13 AM: ERR! errno 2 7:12:13 AM: Shutting down logging, 29 messages pending </pre> </details>

created time in 14 days

push eventeslint/rfcs

Nicholas C. Zakas

commit sha ba647ed8076344b1605e9e7b33b0550c0159400f

Cleanup description of parsers and processors

view details

push time in 3 months

pull request commenteslint/rfcs

New: Config File Simplification

After pondering things over lunch, it seems like one of the big concerns is that this proposal basically eliminates the concept of a plugin as a separate entity, instead splitting it into its individual parts. As @mysticatea points out, that would make doing things like plugin hooks more difficult and confusing. Given that, I think it's reasonable to replace ruleNamespaces with plugins so users can define an entire plugin (this was actually where the proposal began before I submitted it for comment, ironically). So we could do this:

const react = require("eslint-plugin-react");

module.exports = [
    {
        defs: {
            plugins: {
                react,
                foo: require("./plugins/local-file.js")
            }
        }
    },
    {
        files: ["*.md"],

        // look up in the plugins
        processor: "foo/mdprocessor",
    },
    {
         files: ["*.js"],
         
         // also allowed to pass in an object directly
         processor: require("some-processor")
];

We can't escape the need for users to provide a plugin name, but at least with this approach there's the possibility of using shorthand syntax in common cases so it doesn't feel so weird. The idea would be to load the plugin as it is loaded today and string identifiers later in the config (such as with processor) could be looked up inside of a plugin or could be assigned an object. That matches a lot more closely to the eslintrc model so hopefully will be less confusing and more familiar.

So, I went ahead and updated the RFC with my original ideas about plugins to better represent my current thinking. (Still not sure if it should be defs.plugins or just plugins, it seems like having a defs area where we could add more definition types later would be helpful but I'm not 100% sure.)

nzakas

comment created time in 3 months

push eventeslint/rfcs

Nicholas C. Zakas

commit sha 579d466532a1e13550ffa947d6eeba802907ce2f

Add back @eslint/config, explain back compat features

view details

Nicholas C. Zakas

commit sha 919a43d0b43333305b8e90c070088e6269aa05a1

ruleNamespaces -> plugins

view details

push time in 3 months

pull request commenteslint/rfcs

New: First-Class Support For `.eslintrc.ts`

@G-Rath I completely understand your point, but I think you took my comments a bit too literally. :) To me, the only path forward is to come up with some way that different config file formats can be supported from outside of the ESLint core. I thought implementing #9 could make doing such a thing easier because it limits the number of places configuration can take place, but it's probably not a prerequisite

@mysticatea I don't know. Maybe it would have to be a different type of plugin for ESLint than we currently have. I really didn't think it through. My main point is that this isn't something that should be included in the core, but I'd support having a way for third-parties to create ways to do similar things.

G-Rath

comment created time in 3 months

pull request commenteslint/rfcs

New: Config File Simplification

Thanks for the additional feedback, everyone. For the sake of everyone's sanity, I'm not going to re-debate points that I already addressed in earlier rounds of feedback. There are some points on which it's clear that @not-an-aardvark and I do not agree (i.e. the robustness argument) and where @mysticatea and I will not agree, and I think all of those debates are already well-encapsulated in previous discussions on this RFC.

@not-an-aardvark

Replacing plugins with ruleNamespaces: It's not clear to me that this is a net win. It seems like requiring end users to know the internal structure of a plugin (namely, the fact that it has a rules key) is a more complicated experience in the common case than with today's configs, which basically work with "just add the plugin and the rules will be there".

This is fair. I would agree that ruleNamespaces introduces an additional step for config users. Unfortunately, there's not a good way around doing something like because once we no longer know the name of a package, we can't automatically namespace rules. However, plugins could certainly expose something that makes it easier for users. For example, what if a plugin exposed a baseConfig property that already had that stuff specified? Then you could do something like this:

module.exports = [
    require("eslint-plugin-react").baseConfig,
   { /* my config */ }
];

So, I think there are a variety of ways we could address this aspect of the design.

I feel the same way about removing env in favor of using object spread for globals. Previously, a user would just need to know something like "Use env: { browser: true } to avoid spurious errors when running in a browser". Now, they need to know the specifics of how globals are specified to ESLint, as well as how to use a separate globals package.

This is true, but I think this is something we can manage with documentation and tooling. When someone calls --init, we could automatically put the globals in there, as I'm sure a lot of shared configs would. Based on how tools like Webpack, Rollup, and Babel are configured, I think we can assume our users are savvy enough to figure this out.

I'm also not sure array configs are an improvement in the common case over extends and overrides. Array configs might seem intuitive to us because we're familiar with the internals, but for new users it might not be obvious that overrides blocks can be implemented as a series of configs that extend each other. This seems like it could be frustrating if someone wants to use the equivalent of overrides and has to learn a whole system of config merging.

I disagree on this point. I think the model of a single array is much easier to understand that what we have in eslintrc with two arrays and one object, and then you need to figure out the order in which they are applied.

If our goal is to make it easier for users to fully understand how configs work, then this RFC is a big improvement. If our goal is to make the most common use case easier, then I think this RFC is counterproductive.

Just to make sure I understand your point here, what is the common case you're referring to?

@mysticatea

In short, some plugins have requested the lintStarted-like hook to analyze the whole codebase and to share the analysis result with workers. In this proposal, it looks hard to package rules and such a hook.

This proposal was made before that one, so it didn't take that use case into account, but that doesn't mean that it can't. For instance, maybe a plugin wanting a pre-analysis has to define a config function and there's a method on context that allows for hooks. I don't know, I'd need to dig in and understand that use case more.

This proposal is confusing about the existing excludedFiles and ignorePatterns properties. The former configures if each config (overrides) entry should be applied or not. The latter configures if ESLint should lint or not. How do we configure the former with this proposal?

It seems like this is covered in the RFC, but to reiterate:

  • an object config that has files without ignores will always be applied when the filename matches files
  • an object config that has files with ignores will first match the filename but will not apply the config if the filename matches whats in ignores (the excludedFiles equivalent)
  • an object config with ignores and without files specifies files that will not be linted (replacement for .eslintignore/ignorePatterns)

Rule context has parserPath property and some popular plugins depend on that. How does this proposal support that?

Yeah, that's something we'll need to move away from eventually, probably replace it with context.parser instead. In the short term, we could do something like create a unique ID for each loaded parser (@eslint/parser-1) and insert it into require.cache so context.parserPath still works. In the long term we probably need to move to passing in the actual parser more and more code is written in ESM so parserPath might not necessarily work.


Some points I want to be clear on:

  1. I do not think incremental changes are a good idea. The eslintrc config system has grown in complexity so much that every additional change not only endangers backwards compatibility, but makes it harder to understand what's going on. Incremental breaking changes introduce pain every time we make a change and I don't think that's fair or useful to our users. By creating a completely new approach that people can gradually adopt, we avoid a lot of pain for our users.
  2. I'm not going to make any further changes to this RFC until we decide whether or not to continue in this direction. I spent months incorporating feedback earlier this year, and I don't have the energy to continue doing that if we're not aligned on moving in this direction.
  3. I'm not saying this RFC is complete. There have been a lot of changes since I made this proposal originally and those would have to be factored in. What I'm looking for is a decision by the team whether or not we want to move in this direction, which means that those with concerns are willing to work to improve the proposal to address those concerns. If the team does not want to move in this direction, then I'll close the RFC and we can move on to other matters.
nzakas

comment created time in 3 months

pull request commenteslint/rfcs

New: Config File Simplification

As a note to everyone, I did build a generic utility for this, published here: https://www.npmjs.com/package/@humanwhocodes/config-array

nzakas

comment created time in 3 months

Pull request review commenteslint/rfcs

New: Config File Simplification

+- Start Date: 2019-01-20+- RFC PR: https://github.com/eslint/rfcs/pull/9+- Authors: Nicholas C. Zakas (@nzakas)+- Contributors: Teddy Katz (@not-an-ardvaark), Toru Nagashima (@mysticatea)++# Config File Simplification++## Summary++This proposal provides a way to simplify configuration of ESLint through a new configuration file format. The configuration file format is written in JavaScript and removes several existing configuration keys in favor of allowing the user to manually create them.++## Motivation++The ESLint configuration file format (`.eslintrc`) hasn't changed much since ESLint was first released. As we have continued to add new features, the configuration file format has continued to be extended and evolve into a state where it is the most complicated part of ESLint. The `.eslintrc` way of doing things means a variety of different merging strategies (cascading, extending, overriding), loading strategies (for plugins, processors, parsers), and naming conventions (`eslint-config-*`, `eslint-plugin-*`).++Although `.eslintrc` has proven to be a very robust format, it is also limited and unable to easily accommodate feature requests that the community favors, such as:++1. [bundling plugins with configs](https://github.com/eslint/eslint/issues/3458)+1. [specifying ignore patterns in the config](https://github.com/eslint/eslint/issues/10891)+1. [specifying `--ext` information in the config](https://github.com/eslint/eslint/issues/11223)+1. [using `extends` in `overrides`](https://github.com/eslint/eslint/issues/8813) +1. [Customize merging of config options](https://github.com/eslint/eslint/issues/9192)+++The only reason that these requests are difficult to implement in ESLint is because of how complex the `.eslintrc` configuration format is. Any changes made to any part of `.eslintrc` processing end up affecting millions of ESLint installations, so we have ended up stuck. The complicated parts of `.eslintrc` include:++1. Resolution behavior of modules (`plugins`, `parser`, `extends`)+1. Merging of cascading config files in a directory structure+1. Merging of config files via `extends`+1. Overriding of configuration via `overrides`++All of these complexities arise from `.eslintrc` format of describing what should happen rather than how it should happen. We keep trying to anticipate how users want things to happen and guessing wrong has led to increased complexity.++This RFC proposes a fresh start for configuration in ESLint that takes into account all of the requests we've received over the years. By simplifying and stripping configuration we can actually make a configuration system that is flexible enough to accomodate changes in the future and easy enough to implement that we won't be afraid to do so.++## Detailed Design++Design Summary:++1. Introduce a new `eslint.config.js` configuration file format+1. Searching for `eslint.config.js` starts from the current working directory and continues up+1. All `eslint.config.js` files are treated as if they have `root: true`+1. There is no automatic merging of config files+1. The `eslint.config.js` configuration is not serializable and objects such as functions and plugins may be added directly into configuration+1. Remove the concept of environments (`env`)+1. Remove `.eslintignore` and `--ignore-file` (no longer necessary)+1. Remove `--rulesdir`+1. Remove `--ext`++### The `eslint.config.js` File++The `eslint.config.js` file is a JavaScript file (there is no JSON or YAML equivalent) that exports a `config` object: ++```js+exports.config = {+    name: "name",+    files: ["*.js"],+    ignores: ["*.test.js"],+    globals: {},+    settings: {},+    processor: object,+    parser: object,+    parserOptions: {},+    defs: {+        ruleNamespaces: {}+    },+    rules: {}+};+```++The following keys are new to the `eslint.config.js` format:++* `name` - Specifies the name of the config object. This is helpful for printing out debugging information and, while not required, is recommended for that reason.+* `files` - **Required.** Determines the glob file patterns that this configuration applies to.+* `ignores` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`.+* `defs` - Contains definitions that are used in the config.+  * `ruleNamespaces` - Contains definitions for rule namespaces grouped by a specific name. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option.++The following keys are specified the same as in `.eslintrc` files:++* `globals`+* `settings`+* `rules`+* `parserOptions`++The following keys are specified differently than in `.eslintrc` files:++* `parser` - an object in `eslint.config.js` files (a string in `.eslintrc`)+* `processor` - an object in `eslint.config.js` files (a string in `.eslintrc`)++Each of these keys used to require one or more strings specifying module(s) to load in `.eslintrc`. In `eslint.config.js`, these are all objects, requiring users to manually specify the objects to use.++The following keys are invalid in `eslint.config.js`:++* `extends` - replaced by config arrays+* `env` - responsibility of the user+* `overrides` - responsibility of the user+* `plugins` - replaced by `ruledefs`+* `root` - always considered `true`++Each of these keys represent different ways of augmenting how configuration is calculated and all of that responsibility now falls on the user.++#### Extending Another Config++Extending another config is accomplished by returning an array as the value of `exports.config`. Configs that come later in the array are merged with configs that come earlier in the array. For example:++```js+exports.config = [+    require("eslint-config-standard"),+    {+        files: ["*.js"],+        rules: {+            semi: ["error", "always"]+        }+    }+];+```++This config extends `eslint-config-standard` because that package is included first in the array. You can add multiple configs into the array to extend from multiple configs, such as:++```js+exports.config = [+    require("eslint-config-standard"),+    require("@me/eslint-config"),+    {+        files: ["*.js"],+        rules: {+            semi: ["error", "always"]+        }+    }+];+```++Each item in a config array can be a config array. For example, this is a valid config array and equivalent to the previous example:++```js+exports.config = [+    [+        require("eslint-config-standard"),+        require("@me/eslint-config")+    ],+    {+        files: ["*.js"],+        rules: {+            semi: ["error", "always"]+        }+    }+];+```++A config array is always flattened before being evaluated, so even though this example is a two-dimensional config array, it will be evaluated as if it were a one-dimensional config array.++When using a config array, only one config object must have a `files` key (config arrays where no objects contain `files` will result in an error). If a config in the config array does not contain `files` or `ignores`, then that config is merged into every config with a `files` pattern. For example:++```js+exports.config = [+    {+        globals: {+            Foo: true+        }+    },+    {+        files: ["*.js"],+        rules: {+            semi: ["error", "always"]+        }+    },+    {+        files: ["*.mjs"],+        rules: {+            semi: ["error", "never"]+        }+    }+];+```++In this example, the first config in the array defines a global variable of `Foo`. That global variable is merged into the other two configs in the array automatically because there is no `files` or `ignores` specifying when it should be used. The first config matches zero files on its own and would be invalid if it was the only config in the config array.++#### Extending From `eslint:recommended` and `eslint:all`++Both `eslint:recommended` and `eslint:all` can be represented as strings in a config array. For example:++```js+exports.config = [+    "eslint:recommended",+    require("eslint-config-standard"),+    require("@me/eslint-config"),+    {+        files: ["*.js"],+        rules: {+            semi: ["error", "always"]+        }+    }+];+```++This config first extends `eslint:recommended` and then continues on to extend other configs.++#### Setting the Name of Shareable Configs++For shareable configs, specifying a `name` property for each config they export helps ESLint to output more useful error messages if there is a problem. The `name` property is a string that will be used to identify configs to help users resolve problems. For example, if you are creating `eslint-config-example`, then you can specify a `name` property to reflect that:++```js+module.exports = {+    name: "eslint-config-example",++    // other info here+};+```++It's recommended that the shareable config provide a unique name for each config that is exported.++#### Disambiguating Shareable Configs With Common Dependencies++Today, shareable configs that depend on plugin rules must specify the plugin as a peer dependency and then either provide a script to install those dependencies or ask the user to install them manually.++With this design, shareable configs can specify plugins as direct dependencies that will be automatically installed with the shareable config, improving the user experience of complex shareable configs. This means it's possible for multiple shareable configs to depend on the same plugin and, in theory, depend on different versions of the same plugin. In general, npm will handle this directly, installing the correct plugin version at the correct level for each shareable config to `require()`. For example, suppose there are two shareable configs, `eslint-config-a` and `eslint-config-b` that both rely on the `eslint-plugin-example` plugin, but the former relies on 1.0.0 and the latter relies on 2.0.0. npm will install those plugins like this:++```+your-project+├── eslint.config.js+└── node_modules+  ├── eslint+  ├── eslint-config-a+  | └── node_modules+  |   └── eslint-plugin-example@1.0.0+  └── eslint-config-b+    └── node_modules+      └── eslint-plugin-example@2.0.0+```++The problem comes when the shareable configs try to use the default namespace of `eslint-plugin-example` for its rules, such as:++```js+module.exports = {+    defs: {+        ruleNamespaces: {+            example: require("eslint-plugin-example").rules+        }+    }+};+```++If both shareable configs do this, and the user tries to use both shareable configs, an error will be thrown when the configs are normalized because the rule namespace `example` can only be assigned once.++To work around this problem, the end user can create a separate namespace for the same plugin so that it doesn't conflict with an existing rule namespace from a shareable config. For example, suppose you want to use `eslint-config-first`, and that has an `example` rule namespace defined. You'd also like to use `eslint-config-second`, which also has an `example` rule namespace defined. Trying to use both shareable configs will throw an error because a rule namespace cannot be defined twice. You can still use both shareable configs by creating a new config from `eslint-config-second` that uses a different namespace. For example:++```js+// get the config you want to extend+const configToExtend = require("eslint-config-second");++// create a new copy (NOTE: probably best to do this with @eslint/config somehow)+const compatConfig = Object.create(configToExtend);+compatConfig.defs.ruleNamespaces = Object.assign({}, configToExtend.defs.ruleNamespaces);+compatConfig.defs.ruleNamespaces["compat::example"] = require("eslint-plugin-example").rules;+delete compatConfig.defs.ruleNamespaces.example;++// include in config+exports.config = [++    require("eslint-config-first");+    compatConfig,+    {+        // overrides here+    }+];+```+++#### Referencing Plugin Rules++The `plugins` key in `.eslintrc` was an array of strings indicating the plugins to load, allowing you to specify processors, rules, etc., by referencing the name of the plugin. It's no longer necessary to indicate the plugins to load because that is done directly in the `eslint.config.js` file. For example, consider this `.eslintrc` file:++```yaml+plugins:+  - react++rules:+  react/jsx-uses-react: error+```++This file tells ESLint to load `eslint-plugin-react` and then configure a rule from that plugin. The `react/` is automatically preprended to the rule by ESLint for easy reference.++In `eslint.config.js`, the same configuration is achieved using a `ruledefs` key:++```js+const reactPlugin = require("eslint-plugin-react");++exports.config = {+    files: ["*.js"],+    defs: {+        ruleNamespaces: {+            react: reactPlugin.rules+        }+    },+    rules: {+        "react/jsx-uses-react": "error"+    }+};+```++Here, it is the `defs.ruleNamespaces` that assigns the name `react` to the rules from `eslint-plugin-react`. The reference to `react/` in a rule will always look up that value in the `defs.ruleNamespaces` key.++**Note:** If a config is merged with another config that already has the same `defs.ruleNamespaces` namespace defined, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent.++#### Plugins Specifying Their Own Namespaces++Rules imported from a plugin must be assigned a namespace using `defs.ruleNamespaces`, which puts the responsibility for that namespace on the config file user. Plugins can define their own namespace for rules in two ways.++First, a plugin can export a recommended configuration to place in a config array. For example, a plugin called `eslint-plugin-example`, might define a config that looks like this:++```js+exports.configs = {+    recommended: {+        defs: {+            ruleNamespaces: {+                example: {+                    rule1: require("./rules/rule1")+                }+            }+        }+    }+};+```++Then, inside of a user config, the plugin's recommended config can be loaded:++```js+exports.config = [+    require("eslint-plugin-example").configs.recommended,+    {+        rules: {+            "example/rule1": "error"+        }+    }+];+```++The user config in this example now inherits the `defs.ruleNamespaces` from the plugin's recommended config, automatically adding in the rules with their preferred namespace. (Note that the user config can't have another `defs.ruleNamespaces` namespace called `example` without an error being thrown.)++The second way for plugins to specify their preferred namespace is to export a `ruleNamespaces` key directly that users can include their own config. This is what it would look like in the plugin:++```js+exports.ruleNamespaces = {+    example: {+        rule1: require("./rules/rule1")+    }+};+```++Then, inside of a user config, the plugin's `defs.ruleNamespaces` can be included directly`:++```js+exports.config = {+    defs: {+        ruleNamespaces: {+            ...require("eslint-plugin-example").ruleNamespaces+        },+    }+    rules: {+        "example/rule1": "error"+    }+};+```++This example imports the `ruleNamespaces` from a plugin directly into the same section in the user config.++#### Referencing Parsers and Processors++In `.eslintrc`, the `parser` and `processor` keys required strings to be specified, such as:++```yaml+plugins: ["markdown"]+parser: "babel-eslint"+processor: "markdown/markdown"+```++In `eslint.config.js`, you would need to pass the references directly, such as:++```js+exports.config = {+    files: ["*.js"],+    parser: require("babel-eslint"),+    processor: require("eslint-plugin-markdown").processors.markdown+};+```++In both cases, users now must pass a direct object reference. This has the benefit of using the builtin Node.js module resolution system or allowing users to specify their own. There is never a question of where the modules will be resolved from.++#### Applying an Environment++Unlike with `.eslintrc` files, there is no `env` key in `eslint.config.js`. Users can mimic the behavior of `env` by assigning directly to the `globals` key:++```js+const globals = require("globals");++exports.config = {+    files: ["*.js"],+    globals: {+        MyGlobal: true,+        ...globals.browser+    }+};+```++This effectively duplicates the use of `env: { browser: true }` in ESLint.++**Note:** This would allow us to stop shipping environments in ESLint. We could just tell people to use `globals` in their config and allow them to specify which version of `globals` they want to use.++#### Overriding Configuration Based on File Patterns++Whereas `.eslintrc` had an `overrides` key that made a hierarchical structure, the `eslint.config.js` file does not have any such hierarchy. Instead, users can return an array of configs that should be used. For example, consider this `.eslintrc` config:++```yaml+plugins: ["react"]+rules:+    react/jsx-uses-react: error+    semi: error++overrides:+    - files: "*.md"+      plugins: ["markdown"],+      processor: "markdown/markdown"+```++This can be written in `eslint.config.js` as an array of two configs:++```js+exports.config = [+    {+        files: "*.js",+        defs: {+            ruleNamespaces: {+                react: require("eslint-plugin-react").rules,+            },+        }+        rules: {+            "react/jsx-uses-react": "error",+            semi: "error"+        }+    },+    {+        files: "*.md",+        processor: require("eslint-plugin-markdown").processors.markdown+    }+];+```++When ESLint uses this config, it will check each `files` pattern to determine which configs apply. Any config with a `files` pattern matching the file to lint will be extracted and used (if multiple configs match, then those configs are merged to determine the final config to use). In this way, returning an array acts exactly the same as the array in `overrides`.++#### Replacing `.eslintignore`++Because there is only one `eslint.config.js` file to consider, ESLint doesn't have to first search directories to determine its location. That allows `eslint.config.js` to specify files to ignore directly instead of relying on `.eslintignore`. For backwards compatibility, users could create a config like this:++```js+const fs = require("fs");++exports.config = {+    files: ["*.js"],+    ignores: fs.readFileSync(".eslintignore", "utf8").split("\n")+};+```++### Replacing `--ext`++The `--ext` flag is currently used to pass in one or more file extensions that ESLint should search for when a directory without a glob pattern is passed on the command line, such as:++```bash+eslint src/ --ext .js,.jsx+```++This curently searches all subdirectories of `src/` for files with extensions matching `.js` or `.jsx`.++This proposal removes `--ext` by allowing the same information to be passed in a config. For example, the following config achieves the same result:++```js+const fs = require("fs");++exports.config = {+    files: ["*.js", "*.jsx"],+};+```++ESLint could then be run with this command:++```bash+eslint src/+```++When evaluating the `files` array in the config, ESLint will end up searching for `src/**/*.js` and `src/**/*.jsx`. (More information about file resolution is included later this proposal.)++### Replacing `--rulesdir`++In order to recreate the functionality of `--rulesdir`, a user would need to create a new entry in `defs.ruleNamespaces` and then specify the rules from a directory. This can be accomplished using the [`requireindex`](https://npmjs.com/package/requireindex) npm package:++```js+const requireIndex = require("requireindex");++exports.config = {+    defs: {+        ruleNamespaces: {+            custom: requireIndex("./custom-rules")+        },+    }+    rules: {+        "custom/my-rule": "error"+    }+};+```++The `requireIndex()` method returns an object where the keys are the rule IDs (based on the filenames found in the directory) and the values are the rule objects. Unlike today, rules loaded from a local directory must have a namespace just like plugin rules (`custom` in this example).++### Function Configs++Some users may need information from ESLint to determine the correct configuration to use. To allow for that, `exports.config` may also be a function that returns an object, such as:++```js+exports.config = (context) => {++    // do something++    return {+        files: ["*.js"],+        rules: {+            "semi": ["error", "always"]+        }+    };++};+```++The `context` object has the following members:++* `core` - information about the ESLint core that is using the config+    * `version` - the version of ESLint being used+    * `hasRule(ruleId)` - determine if the given rule is in the core+* `cwd` - the current working directory for ESLint (might be different than `process.cwd()` but always matches `CLIEngine.options.cwd`, see https://github.com/eslint/eslint/issues/11218)++This information allows users to make logical decisions about how the config should be constructed.++A configuration function may return an object or an array of objects. An error is thrown if any other type of value is returned.++#### Checking for Rule Existence++One of the problems with shareable configs today is when a new rule is added to the ESLint core, shareable configs using that rule are not valid for older versions of ESLint (because ESLint validates that configured rules are present). With advanced configs, a shareable config could detect if a new rule is present before deciding to include it, for example:++```js+const requireIndex = require("requireindex");++exports.config = (context) => {+    const myConfig = {+        files: ["*.js"],+        defs: {+            ruleNamespaces: {+                custom: requireIndex("./custom-rules")+            },+        }+        rules: {+            "custom/my-rule": "error"+        }+    };++    if (context.hasRule("some-new-rule")) {

I feel like we won't really know what helper functions are useful until we get a base implementation out. It'll be easy to see what's missing once we get to see the feedback, whereas we can never remove anything if we add something that isn't needed.

nzakas

comment created time in 3 months

Pull request review commenteslint/rfcs

New: Config File Simplification

+- Start Date: 2019-01-20+- RFC PR: https://github.com/eslint/rfcs/pull/9+- Authors: Nicholas C. Zakas (@nzakas)+- Contributors: Teddy Katz (@not-an-ardvaark), Toru Nagashima (@mysticatea)++# Config File Simplification++## Summary++This proposal provides a way to simplify configuration of ESLint through a new configuration file format. The configuration file format is written in JavaScript and removes several existing configuration keys in favor of allowing the user to manually create them.++## Motivation++The ESLint configuration file format (`.eslintrc`) hasn't changed much since ESLint was first released. As we have continued to add new features, the configuration file format has continued to be extended and evolve into a state where it is the most complicated part of ESLint. The `.eslintrc` way of doing things means a variety of different merging strategies (cascading, extending, overriding), loading strategies (for plugins, processors, parsers), and naming conventions (`eslint-config-*`, `eslint-plugin-*`).++Although `.eslintrc` has proven to be a very robust format, it is also limited and unable to easily accommodate feature requests that the community favors, such as:++1. [bundling plugins with configs](https://github.com/eslint/eslint/issues/3458)+1. [specifying ignore patterns in the config](https://github.com/eslint/eslint/issues/10891)+1. [specifying `--ext` information in the config](https://github.com/eslint/eslint/issues/11223)+1. [using `extends` in `overrides`](https://github.com/eslint/eslint/issues/8813) +1. [Customize merging of config options](https://github.com/eslint/eslint/issues/9192)+++The only reason that these requests are difficult to implement in ESLint is because of how complex the `.eslintrc` configuration format is. Any changes made to any part of `.eslintrc` processing end up affecting millions of ESLint installations, so we have ended up stuck. The complicated parts of `.eslintrc` include:++1. Resolution behavior of modules (`plugins`, `parser`, `extends`)+1. Merging of cascading config files in a directory structure+1. Merging of config files via `extends`+1. Overriding of configuration via `overrides`++All of these complexities arise from `.eslintrc` format of describing what should happen rather than how it should happen. We keep trying to anticipate how users want things to happen and guessing wrong has led to increased complexity.++This RFC proposes a fresh start for configuration in ESLint that takes into account all of the requests we've received over the years. By simplifying and stripping configuration we can actually make a configuration system that is flexible enough to accomodate changes in the future and easy enough to implement that we won't be afraid to do so.++## Detailed Design++Design Summary:++1. Introduce a new `eslint.config.js` configuration file format+1. Searching for `eslint.config.js` starts from the current working directory and continues up+1. All `eslint.config.js` files are treated as if they have `root: true`+1. There is no automatic merging of config files+1. The `eslint.config.js` configuration is not serializable and objects such as functions and plugins may be added directly into configuration+1. Remove the concept of environments (`env`)+1. Remove `.eslintignore` and `--ignore-file` (no longer necessary)+1. Remove `--rulesdir`+1. Remove `--ext`++### The `eslint.config.js` File++The `eslint.config.js` file is a JavaScript file (there is no JSON or YAML equivalent) that exports a `config` object: ++```js+exports.config = {+    name: "name",+    files: ["*.js"],+    ignores: ["*.test.js"],+    globals: {},+    settings: {},+    processor: object,+    parser: object,+    parserOptions: {},+    defs: {+        ruleNamespaces: {}+    },+    rules: {}+};+```++The following keys are new to the `eslint.config.js` format:++* `name` - Specifies the name of the config object. This is helpful for printing out debugging information and, while not required, is recommended for that reason.+* `files` - **Required.** Determines the glob file patterns that this configuration applies to.+* `ignores` - Determines the files that should not be linted using ESLint. This can be used in place of the `.eslintignore` file. The files specified by this array of glob patterns are subtracted from the files specified in `files`.+* `defs` - Contains definitions that are used in the config.+  * `ruleNamespaces` - Contains definitions for rule namespaces grouped by a specific name. This replaces the `plugins` key in `.eslintrc` files and the `--rulesdir` option.++The following keys are specified the same as in `.eslintrc` files:++* `globals`+* `settings`+* `rules`+* `parserOptions`++The following keys are specified differently than in `.eslintrc` files:++* `parser` - an object in `eslint.config.js` files (a string in `.eslintrc`)+* `processor` - an object in `eslint.config.js` files (a string in `.eslintrc`)++Each of these keys used to require one or more strings specifying module(s) to load in `.eslintrc`. In `eslint.config.js`, these are all objects, requiring users to manually specify the objects to use.++The following keys are invalid in `eslint.config.js`:++* `extends` - replaced by config arrays+* `env` - responsibility of the user+* `overrides` - responsibility of the user+* `plugins` - replaced by `ruledefs`+* `root` - always considered `true`++Each of these keys represent different ways of augmenting how configuration is calculated and all of that responsibility now falls on the user.++#### Extending Another Config++Extending another config is accomplished by returning an array as the value of `exports.config`. Configs that come later in the array are merged with configs that come earlier in the array. For example:++```js+exports.config = [+    require("eslint-config-standard"),+    {+        files: ["*.js"],+        rules: {+            semi: ["error", "always"]+        }+    }+];+```++This config extends `eslint-config-standard` because that package is included first in the array. You can add multiple configs into the array to extend from multiple configs, such as:++```js+exports.config = [+    require("eslint-config-standard"),+    require("@me/eslint-config"),+    {+        files: ["*.js"],+        rules: {+            semi: ["error", "always"]+        }+    }+];+```++Each item in a config array can be a config array. For example, this is a valid config array and equivalent to the previous example:++```js+exports.config = [+    [+        require("eslint-config-standard"),+        require("@me/eslint-config")+    ],+    {+        files: ["*.js"],+        rules: {+            semi: ["error", "always"]+        }+    }+];+```++A config array is always flattened before being evaluated, so even though this example is a two-dimensional config array, it will be evaluated as if it were a one-dimensional config array.++When using a config array, only one config object must have a `files` key (config arrays where no objects contain `files` will result in an error). If a config in the config array does not contain `files` or `ignores`, then that config is merged into every config with a `files` pattern. For example:++```js+exports.config = [+    {+        globals: {+            Foo: true+        }+    },+    {+        files: ["*.js"],+        rules: {+            semi: ["error", "always"]+        }+    },+    {+        files: ["*.mjs"],+        rules: {+            semi: ["error", "never"]+        }+    }+];+```++In this example, the first config in the array defines a global variable of `Foo`. That global variable is merged into the other two configs in the array automatically because there is no `files` or `ignores` specifying when it should be used. The first config matches zero files on its own and would be invalid if it was the only config in the config array.++#### Extending From `eslint:recommended` and `eslint:all`++Both `eslint:recommended` and `eslint:all` can be represented as strings in a config array. For example:++```js+exports.config = [+    "eslint:recommended",+    require("eslint-config-standard"),+    require("@me/eslint-config"),+    {+        files: ["*.js"],+        rules: {+            semi: ["error", "always"]+        }+    }+];+```++This config first extends `eslint:recommended` and then continues on to extend other configs.++#### Setting the Name of Shareable Configs++For shareable configs, specifying a `name` property for each config they export helps ESLint to output more useful error messages if there is a problem. The `name` property is a string that will be used to identify configs to help users resolve problems. For example, if you are creating `eslint-config-example`, then you can specify a `name` property to reflect that:++```js+module.exports = {+    name: "eslint-config-example",++    // other info here+};+```++It's recommended that the shareable config provide a unique name for each config that is exported.++#### Disambiguating Shareable Configs With Common Dependencies++Today, shareable configs that depend on plugin rules must specify the plugin as a peer dependency and then either provide a script to install those dependencies or ask the user to install them manually.++With this design, shareable configs can specify plugins as direct dependencies that will be automatically installed with the shareable config, improving the user experience of complex shareable configs. This means it's possible for multiple shareable configs to depend on the same plugin and, in theory, depend on different versions of the same plugin. In general, npm will handle this directly, installing the correct plugin version at the correct level for each shareable config to `require()`. For example, suppose there are two shareable configs, `eslint-config-a` and `eslint-config-b` that both rely on the `eslint-plugin-example` plugin, but the former relies on 1.0.0 and the latter relies on 2.0.0. npm will install those plugins like this:++```+your-project+├── eslint.config.js+└── node_modules+  ├── eslint+  ├── eslint-config-a+  | └── node_modules+  |   └── eslint-plugin-example@1.0.0+  └── eslint-config-b+    └── node_modules+      └── eslint-plugin-example@2.0.0+```++The problem comes when the shareable configs try to use the default namespace of `eslint-plugin-example` for its rules, such as:++```js+module.exports = {+    defs: {+        ruleNamespaces: {+            example: require("eslint-plugin-example").rules+        }+    }+};+```++If both shareable configs do this, and the user tries to use both shareable configs, an error will be thrown when the configs are normalized because the rule namespace `example` can only be assigned once.++To work around this problem, the end user can create a separate namespace for the same plugin so that it doesn't conflict with an existing rule namespace from a shareable config. For example, suppose you want to use `eslint-config-first`, and that has an `example` rule namespace defined. You'd also like to use `eslint-config-second`, which also has an `example` rule namespace defined. Trying to use both shareable configs will throw an error because a rule namespace cannot be defined twice. You can still use both shareable configs by creating a new config from `eslint-config-second` that uses a different namespace. For example:++```js+// get the config you want to extend+const configToExtend = require("eslint-config-second");++// create a new copy (NOTE: probably best to do this with @eslint/config somehow)+const compatConfig = Object.create(configToExtend);+compatConfig.defs.ruleNamespaces = Object.assign({}, configToExtend.defs.ruleNamespaces);+compatConfig.defs.ruleNamespaces["compat::example"] = require("eslint-plugin-example").rules;+delete compatConfig.defs.ruleNamespaces.example;++// include in config+exports.config = [++    require("eslint-config-first");+    compatConfig,+    {+        // overrides here+    }+];+```+++#### Referencing Plugin Rules++The `plugins` key in `.eslintrc` was an array of strings indicating the plugins to load, allowing you to specify processors, rules, etc., by referencing the name of the plugin. It's no longer necessary to indicate the plugins to load because that is done directly in the `eslint.config.js` file. For example, consider this `.eslintrc` file:++```yaml+plugins:+  - react++rules:+  react/jsx-uses-react: error+```++This file tells ESLint to load `eslint-plugin-react` and then configure a rule from that plugin. The `react/` is automatically preprended to the rule by ESLint for easy reference.++In `eslint.config.js`, the same configuration is achieved using a `ruledefs` key:++```js+const reactPlugin = require("eslint-plugin-react");++exports.config = {+    files: ["*.js"],+    defs: {+        ruleNamespaces: {+            react: reactPlugin.rules+        }+    },+    rules: {+        "react/jsx-uses-react": "error"+    }+};+```++Here, it is the `defs.ruleNamespaces` that assigns the name `react` to the rules from `eslint-plugin-react`. The reference to `react/` in a rule will always look up that value in the `defs.ruleNamespaces` key.++**Note:** If a config is merged with another config that already has the same `defs.ruleNamespaces` namespace defined, then an error is thrown. In this case, if a config already has a `react` namespace, then attempting to combine with another config that has a `react` namespace will throw an error. This is to ensure the meaning of `namespace/rule` remains consistent.++#### Plugins Specifying Their Own Namespaces++Rules imported from a plugin must be assigned a namespace using `defs.ruleNamespaces`, which puts the responsibility for that namespace on the config file user. Plugins can define their own namespace for rules in two ways.++First, a plugin can export a recommended configuration to place in a config array. For example, a plugin called `eslint-plugin-example`, might define a config that looks like this:++```js+exports.configs = {+    recommended: {+        defs: {+            ruleNamespaces: {+                example: {+                    rule1: require("./rules/rule1")+                }+            }+        }+    }+};+```++Then, inside of a user config, the plugin's recommended config can be loaded:++```js+exports.config = [+    require("eslint-plugin-example").configs.recommended,+    {+        rules: {+            "example/rule1": "error"+        }+    }+];+```++The user config in this example now inherits the `defs.ruleNamespaces` from the plugin's recommended config, automatically adding in the rules with their preferred namespace. (Note that the user config can't have another `defs.ruleNamespaces` namespace called `example` without an error being thrown.)++The second way for plugins to specify their preferred namespace is to export a `ruleNamespaces` key directly that users can include their own config. This is what it would look like in the plugin:++```js+exports.ruleNamespaces = {+    example: {+        rule1: require("./rules/rule1")+    }+};+```++Then, inside of a user config, the plugin's `defs.ruleNamespaces` can be included directly`:++```js+exports.config = {+    defs: {+        ruleNamespaces: {+            ...require("eslint-plugin-example").ruleNamespaces+        },+    }+    rules: {+        "example/rule1": "error"+    }+};+```++This example imports the `ruleNamespaces` from a plugin directly into the same section in the user config.++#### Referencing Parsers and Processors++In `.eslintrc`, the `parser` and `processor` keys required strings to be specified, such as:++```yaml+plugins: ["markdown"]+parser: "babel-eslint"+processor: "markdown/markdown"+```++In `eslint.config.js`, you would need to pass the references directly, such as:++```js+exports.config = {+    files: ["*.js"],+    parser: require("babel-eslint"),+    processor: require("eslint-plugin-markdown").processors.markdown+};+```++In both cases, users now must pass a direct object reference. This has the benefit of using the builtin Node.js module resolution system or allowing users to specify their own. There is never a question of where the modules will be resolved from.++#### Applying an Environment++Unlike with `.eslintrc` files, there is no `env` key in `eslint.config.js`. Users can mimic the behavior of `env` by assigning directly to the `globals` key:++```js+const globals = require("globals");++exports.config = {+    files: ["*.js"],+    globals: {+        MyGlobal: true,+        ...globals.browser+    }+};+```++This effectively duplicates the use of `env: { browser: true }` in ESLint.++**Note:** This would allow us to stop shipping environments in ESLint. We could just tell people to use `globals` in their config and allow them to specify which version of `globals` they want to use.++#### Overriding Configuration Based on File Patterns++Whereas `.eslintrc` had an `overrides` key that made a hierarchical structure, the `eslint.config.js` file does not have any such hierarchy. Instead, users can return an array of configs that should be used. For example, consider this `.eslintrc` config:++```yaml+plugins: ["react"]+rules:+    react/jsx-uses-react: error+    semi: error++overrides:+    - files: "*.md"+      plugins: ["markdown"],+      processor: "markdown/markdown"+```++This can be written in `eslint.config.js` as an array of two configs:++```js+exports.config = [+    {+        files: "*.js",+        defs: {+            ruleNamespaces: {+                react: require("eslint-plugin-react").rules,+            },+        }+        rules: {+            "react/jsx-uses-react": "error",+            semi: "error"+        }+    },+    {+        files: "*.md",+        processor: require("eslint-plugin-markdown").processors.markdown+    }+];+```++When ESLint uses this config, it will check each `files` pattern to determine which configs apply. Any config with a `files` pattern matching the file to lint will be extracted and used (if multiple configs match, then those configs are merged to determine the final config to use). In this way, returning an array acts exactly the same as the array in `overrides`.

In general, I think most shared configs will either a) not specify files or b) if they do specify files, indicate that so in their README.

So yes, I think we'd want to provide guidance to config developers to make sure they specify which files their configs will apply to by default.

nzakas

comment created time in 3 months

pull request commenteslint/rfcs

Docs: update rfc lifecycle

Agree with @kaicataldo. It’s best to have a consistent way to track RFC progress.

That said, we might need to add repo information into the RFC so we know which repo to create the issue in. If an RFC is for Espree, we don’t want to automatically create an issue in the ESLint repo.

aladdin-add

comment created time in 3 months

pull request commenteslint/website

Chore: switch from Jekyll to Eleventy

It might be easier to create a script that converts YAML data files into JSON before the website is built. That way, we can decouple making this change from changes in the main repo.

At that point, I’d say make sure the instructions to run in dev work correctly and feel free to merge.

kaicataldo

comment created time in 3 months

created taghumanwhocodes/config-array

tagv0.1.0

A glob-based configuration array utility

created time in 3 months

release humanwhocodes/config-array

v0.1.0

released time in 3 months

push eventhumanwhocodes/config-array

Nicholas C. Zakas

commit sha aed6b83b465e591acc2525b9275012e5a3699583

v0.1.0

view details

push time in 3 months

create barnchhumanwhocodes/config-array

branch : master

created branch time in 3 months

created repositoryhumanwhocodes/config-array

A glob-based configuration array utility

created time in 3 months

pull request commenteslint/rfcs

Docs: update rfc lifecycle

@kaicataldo That sounds like a great idea. :+1: If we came up with a standard format for that issue, we could probably also use the bot to comment back on the RFC PR when it has been implemented.

aladdin-add

comment created time in 3 months

pull request commenteslint/rfcs

New: First-Class Support For `.eslintrc.ts`

@G-Rath Sure thing. Please keep in mind that we, on the core team, really do need to think long-term even if it means some short-term inconvenience. Things change rapidly in the JS ecosystem and it's a big job to keep ESLint stable enough to continue to function in that kind of environment.

I've been interested in creating some kind of a pluggable config system for a while now (built off of #9), so this could be a good reason to move forward on those discussions.

G-Rath

comment created time in 3 months

pull request commenteslint/rfcs

New: Config File Simplification

I'd like to take the temperature of the room on this RFC again. I still feel strongly that this proposal solves a lot of problems that are currently taking several different RFCs to address while maintaining backwards compatibility (as evidenced by the prototype).

nzakas

comment created time in 3 months

Pull request review commenteslint/rfcs

New: `ESLint` Class Replacing `CLIEngine`

+- Start Date: 2019-09-28+- RFC PR: https://github.com/eslint/rfcs/pull/40+- Authors: Toru Nagashima ([@mysticatea](https://github.com/mysticatea))++# `ESLint` Class Replacing `CLIEngine`++## Summary++This RFC adds a new class `ESLint` that provides asynchronous API and deprecates `CLIEngine`.++## Motivation++- We have functionality that cannot be supported with the current synchronous API. For example, ESLint verifying files in parallel, formatters printing progress state, formatters printing results in streams etc. A move to an asynchronous API would be beneficial and a new `ESLint` class can be created with an async API in mind from the start.+- Node.js will support [ES modules](https://nodejs.org/api/esm.html) stably on `13.0.0` at last. Node.js doesn't provide any way that loads ES modules synchronously from CJS. This means that ESLint (CJS) cannot load configs/plugins that are written as ES modules synchronously. Migrating to asynchronous API opens up doors to support those.+- The name of `CLIEngine`, our primary API, has caused confusion in the community and is sub-optimal. We have a lot of issues that say "please use `CLIEngine` instead.". A new class, `ESLint`, while fixing other issues, will also make our primary API more clear.++## Detailed Design++### Add new `ESLint` class++This RFC adds a new class `ESLint`. It has almost the same methods as `CLIEngine`, but the return value of some methods are different.++- [constructor()](#-constructor)+- [executeOnFiles()](#-the-executeonfiles-method)+- [executeOnText()](#-the-executeontext-method)+- [getFormatter()](#-the-getformatter-method)+- [static outputFixesInIteration()](#-the-outputfixesiniteration-method) (rename)+- [static extractErrorResults()](#-the-extracterrorresults-method) (rename)+- [getConfigForFile()](#-the-other-methods)+- [getRules()](#-the-other-methods)+- [isPathIgnored()](#-the-other-methods)+- ~~addPlugin()~~ (move to a constructor option)+- ~~resolveFileGlobPatterns()~~ (delete)+- [static collectResults()](#-new-methods) (new)+- [static compareResultsByFilePath()](#-new-methods) (new)++Initially the `ESLint` class will be a wrapper around `CLIEngine`, modifying return types. Later it can take on a more independent shape as `CLIEngine` gets more deprecated.++#### ● Constructor++The constructor has mostly the same options as `CLIEngine`, but with some differences:++- It throws fatal errors if the options contain unknown properties or an option is invalid type ([eslint/eslint#10272](https://github.com/eslint/eslint/issues/10272)).+- It disallows the deprecated `cacheFile` option.+- It has a new `pluginImplementations` option as the successor of `addPlugin()` method. This is an object that keys are plugin IDs and each value is the plugin object. See also "[The other methods](#-the-other-methods)" section.++<details>+<summary>A rough sketch of the constructor.</summary>++```js+class ESLint {+  constructor({+    allowInlineConfig = true,+    baseConfig = null,+    cache = false,+    cacheLocation = ".eslintcache",+    configFile = null,+    cwd = process.cwd(),+    envs = [],+    extensions = [".js"],+    fix = false,+    fixTypes = ["problem", "suggestion", "layout"],+    globInputPaths = true,+    globals = [],+    ignore = true,+    ignorePath = null,+    ignorePattern = [],+    parser = "espree",+    parserOptions = null,+    pluginImplementations = null,+    plugins = [],+    reportUnusedDisableDirectives = false,+    resolvePluginsRelativeTo = cwd,+    rulePaths = [],+    rules = null,+    useEslintrc = true,+    ...unknownOptions+  } = {}) {+    // Throws on unknown options+    if (Object.keys(unknownOptions).length >= 1) {+      //...+    }+    // Throws on the invalid value of options+    if (typeof allowInlineConfig !== "boolean") {+      // ...+    }+    if (typeof baseConfig !== "object") {+      // ...+    }+    // and other options...++    // Initialize CLIEngine because this is a tiny wrapper.+    const engine = (this._cliEngine = new CLIEngine({+      allowInlineConfig,+      baseConfig,+      cache,+      cacheLocation,+      configFile,+      cwd,+      envs,+      extensions,+      fix,+      fixTypes,+      globInputPaths,+      globals,+      ignore,+      ignorePath,+      ignorePattern,+      parser,+      parserOptions,+      plugins,+      reportUnusedDisableDirectives,+      resolvePluginsRelativeTo,+      rulePaths,+      rules,+      useEslintrc,+    }))++    // Apply `pluginImplementations` option.+    if (pluginImplementations) {+      for (const [id, definition] of Object.entries(pluginImplementations)) {+        engine.addPlugin(id, definition)+      }+    }+  }+}+```++</details>++#### ● The `executeOnFiles()` method++This method returns an object that implements [AsyncIterable] and [AsyncIterator], as similar to async generators. Therefore we can use the returned object with `for-await-of` statement.

I think it makes sense if the formatter handles all outputs because people need to have varied progress displays

I think it's a mistake to bundle that requirement with this proposal, as I think it's clouding the issue of the correct interface to use to replace CLIEngine. We can easily provide a default progress indicator that doesn't require any changes to how we do formatters. If, at some later point, we start getting requests to customize the progress indicator, we can always design something for that.

I predict that API users will request to implement AsyncIterable interface and Thennable interface to the session object to handle the filecomplete events by for-await-of loops and to handle the complete and error events by await expressions. (Similarly, the Node.js streams have been implemented AsyncIterable interface.)

I just don't think either async iteration or streams makes sense for this API. I'm having a hard time imagining why something like Standard or Visual Studio Code would want anything less than all of the lint results. The only thing you've suggested as a use case for partial results is a progress indicator, which again, I just don't think is important enough to design the API around.

mysticatea

comment created time in 3 months

Pull request review commenteslint/rfcs

New: `ESLint` Class Replacing `CLIEngine`

+- Start Date: 2019-09-28+- RFC PR: https://github.com/eslint/rfcs/pull/40+- Authors: Toru Nagashima ([@mysticatea](https://github.com/mysticatea))++# `ESLint` Class Replacing `CLIEngine`++## Summary++This RFC adds a new class `ESLint` that provides asynchronous API and deprecates `CLIEngine`.++## Motivation++- We have functionality that cannot be supported with the current synchronous API. For example, ESLint verifying files in parallel, formatters printing progress state, formatters printing results in streams etc. A move to an asynchronous API would be beneficial and a new `ESLint` class can be created with an async API in mind from the start.

I don't think we should rename Linter as part of this. That's just introducing more breaking changes without much benefit.

I have remembered that we don't support ESLint in browsers officially. Doesn't your concern conflict with our stance?

No. I think it's reasonable to assume that a class named ESLint should work in a browser, just like it's reasonable to assume that Linter would work in the browser (even if we don't formally support it, people still use it). Again, I purposely named the class CLIEngine to make it clear that this was to be used for creating CLI applications. I think whatever replaces it should be named equally clearly.

I can live with LinterShell.

mysticatea

comment created time in 3 months

pull request commenteslint/rfcs

Docs: update rfc lifecycle

I'm not in favor of leaving the PR open until it has been implemented -- this would lead to a very large list of open PRs. I'd rather have open PRs represent RFCs that still need to be reviewed.

That said, I've thought about this, as well. We could definitely use labels for this purpose. Another option would be to have separate folders for "accepted" and "implemented" in the repo, though that has a heavier touch than labels.

aladdin-add

comment created time in 3 months

delete branch eslint/website

delete branch : githubsponsors

delete time in 3 months

push eventeslint/website

Nicholas C. Zakas

commit sha da280c90bd93fe857a2585a00baad089015d2bea

Update: Pull GitHub Sponsors donation data (fixes #647) (#651) * Update: Pull GitHub Sponsors donation data (fixes #647) * Add updated sponsors file

view details

push time in 3 months

issue closedeslint/website

Update scripts to account for Github supporters

Currently our scripts are only checking OpenCollective for supporters to send twitter messages about and adding them to the site/readme. Now that ESLint org can be supported through Github, we need to update those scripts to use Github API for the same tasks as well.

closed time in 3 months

ilyavolodin

PR merged eslint/website

Update: Pull GitHub Sponsors donation data (fixes #647) chore

This updates the fetch-sponsors.js script to include information from GitHub Sponsors. It normalizes the GitHub data to look the same as the Open Collective data (though without total donations, because that isn't available yet from GitHub).

+519 -181

0 comment

4 changed files

nzakas

pr closed time in 3 months

push eventeslint/website

Nicholas C. Zakas

commit sha 7272d79e59f1d55b1d618a664bce9362916b7e2a

Add updated sponsors file

view details

push time in 3 months

PR opened eslint/website

Update: Pull GitHub Sponsors donation data (fixes #647) chore

This updates the fetch-sponsors.js script to include information from GitHub Sponsors. It normalizes the GitHub data to look the same as the Open Collective data (though without total donations, because that isn't available yet from GitHub).

+219 -63

0 comment

3 changed files

pr created time in 3 months

create barncheslint/website

branch : githubsponsors

created branch time in 3 months

issue commenteslint/website

Update scripts to account for Github supporters

Starting to work on this.

ilyavolodin

comment created time in 3 months

issue commenteslint/website

Discussion: do we want to continue using Jekyll?

I generally agree with your assessment. I looked at a bunch of options and have felt like they were either too complicated (Gatsby, Docusaurus) or not complete enough (Vuepress). It seems like Eleventy is the simplest and most straightforward port of what we have now.

I don’t think we should tie a platform change to a redesign. I’d still like to investigate hiring a designer to do a proper redesign, and I don’t see a reason to create a dependency on that for moving off of Jekyll.

kaicataldo

comment created time in 3 months

delete branch eslint/website

delete branch : monthlydonations

delete time in 3 months

push eventeslint/website

Nicholas C. Zakas

commit sha ce6d954fe7b1b94dd4a3f46396f908236ba820cd

Update: Add sponsor count and donation totals to homepage (#648)

view details

push time in 3 months

PR merged eslint/website

Update: Add sponsor count and donation totals to homepage enhancement

This adds a count of our total contributors and monthly donations to the homepage. I think this is useful because Open Collective, for some reason, doesn't have this info easily available (I end up calculating it by hand every time). Screen Shot 2019-11-14 at 10 24 22

+13 -1

4 comments

1 changed file

nzakas

pr closed time in 3 months

issue commenteslint/website

Update scripts to account for Github supporters

Nice! I can try updating the script next week but anyone should feel free to take this on in the meantime if the spirit moves you.

ilyavolodin

comment created time in 3 months

issue commenteslint/website

Update scripts to account for Github supporters

I took a quick look at this, and it seems like the GraphQL API isn't (yet?) returning data for sponsors. I tried using a modified version of the script I'm using to pull my personal sponsors list (which does work).

<details> <summary>GraphQL query</summary>

query{
    organization(login:"eslint") {
      sponsorsListing {
        tiers(first: 100) {
          nodes {
            name
            monthlyPriceInDollars
            adminInfo {
              sponsorships(first: 100) {
                nodes {
                  sponsor {
                    avatarUrl
                    name
                    url
                  }
                }
              }
            }
          }
        }
      }
    }
  }

</details>

The list of sponsors should be in the adminInfo key, but it comes back as null. Do you have a contact for reporting problems?

ilyavolodin

comment created time in 3 months

pull request commenteslint/website

Update: Add sponsor count and donation totals to homepage

Sounds good.

nzakas

comment created time in 3 months

pull request commenteslint/website

Update: Add sponsor count and donation totals to homepage

It could be, but then I'd need to change the format of the sponsors file. Right now, we assume that every top-level property represents a tier. If I were to add another key, then we'd need to change the logic that processes the data both for the website and the ESLint readme. I'm not sure it's worth that much code churn just to output these numbers.

nzakas

comment created time in 3 months

PR opened eslint/website

Update: Add sponsor count and donation totals to homepage enhancement

This adds a count of our total contributors and monthly donations to the homepage. I think this is useful because Open Collective, for some reason, doesn't have this info easily available (I end up calculating it by hand every time). Screen Shot 2019-11-14 at 10 24 22

+13 -1

0 comment

1 changed file

pr created time in 3 months

create barncheslint/website

branch : monthlydonations

created branch time in 3 months

Pull request review commenteslint/rfcs

New: `ESLint` Class Replacing `CLIEngine`

+- Start Date: 2019-09-28+- RFC PR: https://github.com/eslint/rfcs/pull/40+- Authors: Toru Nagashima ([@mysticatea](https://github.com/mysticatea))++# `ESLint` Class Replacing `CLIEngine`++## Summary++This RFC adds a new class `ESLint` that provides asynchronous API and deprecates `CLIEngine`.++## Motivation++- We have functionality that cannot be supported with the current synchronous API. For example, ESLint verifying files in parallel, formatters printing progress state, formatters printing results in streams etc. A move to an asynchronous API would be beneficial and a new `ESLint` class can be created with an async API in mind from the start.+- Node.js will support [ES modules](https://nodejs.org/api/esm.html) stably on `13.0.0` at last. Node.js doesn't provide any way that loads ES modules synchronously from CJS. This means that ESLint (CJS) cannot load configs/plugins that are written as ES modules synchronously. Migrating to asynchronous API opens up doors to support those.+- The name of `CLIEngine`, our primary API, has caused confusion in the community and is sub-optimal. We have a lot of issues that say "please use `CLIEngine` instead.". A new class, `ESLint`, while fixing other issues, will also make our primary API more clear.++## Detailed Design++### Add new `ESLint` class++This RFC adds a new class `ESLint`. It has almost the same methods as `CLIEngine`, but the return value of some methods are different.++- [constructor()](#-constructor)+- [executeOnFiles()](#-the-executeonfiles-method)+- [executeOnText()](#-the-executeontext-method)+- [getFormatter()](#-the-getformatter-method)+- [static outputFixesInIteration()](#-the-outputfixesiniteration-method) (rename)+- [static extractErrorResults()](#-the-extracterrorresults-method) (rename)+- [getConfigForFile()](#-the-other-methods)+- [getRules()](#-the-other-methods)+- [isPathIgnored()](#-the-other-methods)+- ~~addPlugin()~~ (move to a constructor option)+- ~~resolveFileGlobPatterns()~~ (delete)+- [static collectResults()](#-new-methods) (new)+- [static compareResultsByFilePath()](#-new-methods) (new)++Initially the `ESLint` class will be a wrapper around `CLIEngine`, modifying return types. Later it can take on a more independent shape as `CLIEngine` gets more deprecated.++#### ● Constructor++The constructor has mostly the same options as `CLIEngine`, but with some differences:++- It throws fatal errors if the options contain unknown properties or an option is invalid type ([eslint/eslint#10272](https://github.com/eslint/eslint/issues/10272)).+- It disallows the deprecated `cacheFile` option.+- It has a new `pluginImplementations` option as the successor of `addPlugin()` method. This is an object that keys are plugin IDs and each value is the plugin object. See also "[The other methods](#-the-other-methods)" section.++<details>+<summary>A rough sketch of the constructor.</summary>++```js+class ESLint {+  constructor({+    allowInlineConfig = true,+    baseConfig = null,+    cache = false,+    cacheLocation = ".eslintcache",+    configFile = null,+    cwd = process.cwd(),+    envs = [],+    extensions = [".js"],+    fix = false,+    fixTypes = ["problem", "suggestion", "layout"],+    globInputPaths = true,+    globals = [],+    ignore = true,+    ignorePath = null,+    ignorePattern = [],+    parser = "espree",+    parserOptions = null,+    pluginImplementations = null,+    plugins = [],+    reportUnusedDisableDirectives = false,+    resolvePluginsRelativeTo = cwd,+    rulePaths = [],+    rules = null,+    useEslintrc = true,+    ...unknownOptions+  } = {}) {+    // Throws on unknown options+    if (Object.keys(unknownOptions).length >= 1) {+      //...+    }+    // Throws on the invalid value of options+    if (typeof allowInlineConfig !== "boolean") {+      // ...+    }+    if (typeof baseConfig !== "object") {+      // ...+    }+    // and other options...++    // Initialize CLIEngine because this is a tiny wrapper.+    const engine = (this._cliEngine = new CLIEngine({+      allowInlineConfig,+      baseConfig,+      cache,+      cacheLocation,+      configFile,+      cwd,+      envs,+      extensions,+      fix,+      fixTypes,+      globInputPaths,+      globals,+      ignore,+      ignorePath,+      ignorePattern,+      parser,+      parserOptions,+      plugins,+      reportUnusedDisableDirectives,+      resolvePluginsRelativeTo,+      rulePaths,+      rules,+      useEslintrc,+    }))++    // Apply `pluginImplementations` option.+    if (pluginImplementations) {+      for (const [id, definition] of Object.entries(pluginImplementations)) {+        engine.addPlugin(id, definition)+      }+    }+  }+}+```++</details>++#### ● The `executeOnFiles()` method++This method returns an object that implements [AsyncIterable] and [AsyncIterator], as similar to async generators. Therefore we can use the returned object with `for-await-of` statement.

@platinumazure same wavelength :)

mysticatea

comment created time in 3 months

Pull request review commenteslint/rfcs

New: `ESLint` Class Replacing `CLIEngine`

+- Start Date: 2019-09-28+- RFC PR: https://github.com/eslint/rfcs/pull/40+- Authors: Toru Nagashima ([@mysticatea](https://github.com/mysticatea))++# `ESLint` Class Replacing `CLIEngine`++## Summary++This RFC adds a new class `ESLint` that provides asynchronous API and deprecates `CLIEngine`.++## Motivation++- We have functionality that cannot be supported with the current synchronous API. For example, ESLint verifying files in parallel, formatters printing progress state, formatters printing results in streams etc. A move to an asynchronous API would be beneficial and a new `ESLint` class can be created with an async API in mind from the start.+- Node.js will support [ES modules](https://nodejs.org/api/esm.html) stably on `13.0.0` at last. Node.js doesn't provide any way that loads ES modules synchronously from CJS. This means that ESLint (CJS) cannot load configs/plugins that are written as ES modules synchronously. Migrating to asynchronous API opens up doors to support those.+- The name of `CLIEngine`, our primary API, has caused confusion in the community and is sub-optimal. We have a lot of issues that say "please use `CLIEngine` instead.". A new class, `ESLint`, while fixing other issues, will also make our primary API more clear.++## Detailed Design++### Add new `ESLint` class++This RFC adds a new class `ESLint`. It has almost the same methods as `CLIEngine`, but the return value of some methods are different.++- [constructor()](#-constructor)+- [executeOnFiles()](#-the-executeonfiles-method)+- [executeOnText()](#-the-executeontext-method)+- [getFormatter()](#-the-getformatter-method)+- [static outputFixesInIteration()](#-the-outputfixesiniteration-method) (rename)+- [static extractErrorResults()](#-the-extracterrorresults-method) (rename)+- [getConfigForFile()](#-the-other-methods)+- [getRules()](#-the-other-methods)+- [isPathIgnored()](#-the-other-methods)+- ~~addPlugin()~~ (move to a constructor option)+- ~~resolveFileGlobPatterns()~~ (delete)+- [static collectResults()](#-new-methods) (new)+- [static compareResultsByFilePath()](#-new-methods) (new)++Initially the `ESLint` class will be a wrapper around `CLIEngine`, modifying return types. Later it can take on a more independent shape as `CLIEngine` gets more deprecated.++#### ● Constructor++The constructor has mostly the same options as `CLIEngine`, but with some differences:++- It throws fatal errors if the options contain unknown properties or an option is invalid type ([eslint/eslint#10272](https://github.com/eslint/eslint/issues/10272)).+- It disallows the deprecated `cacheFile` option.+- It has a new `pluginImplementations` option as the successor of `addPlugin()` method. This is an object that keys are plugin IDs and each value is the plugin object. See also "[The other methods](#-the-other-methods)" section.

Oops! I meant this:

{
    plugins: {
        "react": "react",
        "foo": { /* some object*/ }
    }

Although @platinumazure's approach would work too. I think trying to keep all the data in one place would be beneficial.

mysticatea

comment created time in 3 months

Pull request review commenteslint/rfcs

New: `ESLint` Class Replacing `CLIEngine`

+- Start Date: 2019-09-28+- RFC PR: https://github.com/eslint/rfcs/pull/40+- Authors: Toru Nagashima ([@mysticatea](https://github.com/mysticatea))++# `ESLint` Class Replacing `CLIEngine`++## Summary++This RFC adds a new class `ESLint` that provides asynchronous API and deprecates `CLIEngine`.++## Motivation++- We have functionality that cannot be supported with the current synchronous API. For example, ESLint verifying files in parallel, formatters printing progress state, formatters printing results in streams etc. A move to an asynchronous API would be beneficial and a new `ESLint` class can be created with an async API in mind from the start.+- Node.js will support [ES modules](https://nodejs.org/api/esm.html) stably on `13.0.0` at last. Node.js doesn't provide any way that loads ES modules synchronously from CJS. This means that ESLint (CJS) cannot load configs/plugins that are written as ES modules synchronously. Migrating to asynchronous API opens up doors to support those.+- The name of `CLIEngine`, our primary API, has caused confusion in the community and is sub-optimal. We have a lot of issues that say "please use `CLIEngine` instead.". A new class, `ESLint`, while fixing other issues, will also make our primary API more clear.++## Detailed Design++### Add new `ESLint` class++This RFC adds a new class `ESLint`. It has almost the same methods as `CLIEngine`, but the return value of some methods are different.++- [constructor()](#-constructor)+- [executeOnFiles()](#-the-executeonfiles-method)+- [executeOnText()](#-the-executeontext-method)+- [getFormatter()](#-the-getformatter-method)+- [static outputFixesInIteration()](#-the-outputfixesiniteration-method) (rename)+- [static extractErrorResults()](#-the-extracterrorresults-method) (rename)+- [getConfigForFile()](#-the-other-methods)+- [getRules()](#-the-other-methods)+- [isPathIgnored()](#-the-other-methods)+- ~~addPlugin()~~ (move to a constructor option)+- ~~resolveFileGlobPatterns()~~ (delete)+- [static collectResults()](#-new-methods) (new)+- [static compareResultsByFilePath()](#-new-methods) (new)++Initially the `ESLint` class will be a wrapper around `CLIEngine`, modifying return types. Later it can take on a more independent shape as `CLIEngine` gets more deprecated.++#### ● Constructor++The constructor has mostly the same options as `CLIEngine`, but with some differences:++- It throws fatal errors if the options contain unknown properties or an option is invalid type ([eslint/eslint#10272](https://github.com/eslint/eslint/issues/10272)).+- It disallows the deprecated `cacheFile` option.+- It has a new `pluginImplementations` option as the successor of `addPlugin()` method. This is an object that keys are plugin IDs and each value is the plugin object. See also "[The other methods](#-the-other-methods)" section.++<details>+<summary>A rough sketch of the constructor.</summary>++```js+class ESLint {+  constructor({+    allowInlineConfig = true,+    baseConfig = null,+    cache = false,+    cacheLocation = ".eslintcache",+    configFile = null,+    cwd = process.cwd(),+    envs = [],+    extensions = [".js"],+    fix = false,+    fixTypes = ["problem", "suggestion", "layout"],+    globInputPaths = true,+    globals = [],+    ignore = true,+    ignorePath = null,+    ignorePattern = [],+    parser = "espree",+    parserOptions = null,+    pluginImplementations = null,+    plugins = [],+    reportUnusedDisableDirectives = false,+    resolvePluginsRelativeTo = cwd,+    rulePaths = [],+    rules = null,+    useEslintrc = true,+    ...unknownOptions+  } = {}) {+    // Throws on unknown options+    if (Object.keys(unknownOptions).length >= 1) {+      //...+    }+    // Throws on the invalid value of options+    if (typeof allowInlineConfig !== "boolean") {+      // ...+    }+    if (typeof baseConfig !== "object") {+      // ...+    }+    // and other options...++    // Initialize CLIEngine because this is a tiny wrapper.+    const engine = (this._cliEngine = new CLIEngine({+      allowInlineConfig,+      baseConfig,+      cache,+      cacheLocation,+      configFile,+      cwd,+      envs,+      extensions,+      fix,+      fixTypes,+      globInputPaths,+      globals,+      ignore,+      ignorePath,+      ignorePattern,+      parser,+      parserOptions,+      plugins,+      reportUnusedDisableDirectives,+      resolvePluginsRelativeTo,+      rulePaths,+      rules,+      useEslintrc,+    }))++    // Apply `pluginImplementations` option.+    if (pluginImplementations) {+      for (const [id, definition] of Object.entries(pluginImplementations)) {+        engine.addPlugin(id, definition)+      }+    }+  }+}+```++</details>++#### ● The `executeOnFiles()` method++This method returns an object that implements [AsyncIterable] and [AsyncIterator], as similar to async generators. Therefore we can use the returned object with `for-await-of` statement.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++for await (const result of eslint.executeOnFiles(patterns)) {+  print(result)+}+```++The returned object yields the lint result of each file immediately when it has finished linting each file. Therefore, ESLint doesn't guarantee the order of the iteration. The order is random.++This method must not throw any errors synchronously. Errors may happen in iteration asynchronously.++<details>+<summary>A rough sketch of the `executeOnFiles()` method.</summary>++A tiny wrapper of `CLIEngine`.++```js+class ESLint {+  async *executeOnFiles(patterns) {+    yield* this._cliEngine.executeOnFiles(patterns).results+  }+}+```++But once [RFC42] is implemented, the returned object will be an instance of [`LintResultGenerator`](https://github.com/eslint/eslint/blob/836c0e48704d70bc1a5cbdbf0211368b0ada942d/lib/eslint/lint-result-generator.js#L136) class.++</details>++If you want to use the returned object with `await` expression, you can use a small utility to convert an async iterable object to an array.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++// Convert the results to an array.+const results = await ESLint.collectResults(eslint.executeOnFiles(patterns))++// Optionally you can sort the results.+results.sort(ESLint.compareResultsByFilePath)++print(results)+```++Once we got this change, we can realize the following things:++- [RFC42] We can implement linting in parallel by worker threads. It will reduce spending time of linting much.+- [RFC45] We can implement to print the results in streaming or to print progress state, without more breaking changes. Because ESLint may spend time to lint files (for example, ESLint needs about 20 seconds to lint [our codebase](https://github.com/eslint/eslint)), to print progress state will be useful.+- (no RFC yet) We can support ES modules for shareable configs, plugins, and custom parsers.++##### Iterate only one time++We can iterate the returned object of this method only one time similar to generators.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++const resultGenerator = eslint.executeOnFiles(patterns)+for await (const result of resultGenerator) {+  print(result)+}+// ↓ Throw "This generator has been consumed already"+for await (const result of resultGenerator) {+  print(result)+}+```++##### Move the `usedDeprecatedRules` property++The returned object of `CLIEngine#executeOnFiles()` has the `usedDeprecatedRules` property that includes the deprecated rule IDs which the linting used.++But the location doesn't fit asynchronous because the used deprecated rule list is not determined until the iteration finished. Therefore, this RFC moves the `usedDeprecatedRules` property to each lint result.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++for await (const result of eslint.executeOnFiles(patterns)) {+  console.log(result.usedDeprecatedRules)+}+```++As a side-effect, formatters gets the capability to print the used deprecated rules. Previously, ESLint has not passed the returned object to formatters, so the formatters could not print used deprecated rules. After this RFC, each lint result has the `usedDeprecatedRules` property and the formatters receive those.++##### Disallow execution in parallel++Because this method updates the cache file, it will break the cache file if called multiple times in parallel. To prevent that, every call of `executeOnFiles()` must wait for the previous call finishes.++##### Abort linting++The iterator interface has optional [`return()` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/return) that forces to finish the iterator. The `for-of`/`for-await-of` syntax calls the `return()` method automatically if the loop is stopped through a `braek`, `return`, or `throw`.++ESLint aborts linting when the `return()` method is called. The first `return()` method call does:++- ESLint cancels the linting of all pending files.+- ESLint updates the cache file with the current state. Therefore, the next time, ESLint can use the cache of the already linted files and lint only the canceled files.+- ESLint will terminate all workers if [RFC42] is implemented.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++for await (const result of eslint.executeOnFiles(patterns)) {+  if (Math.random() < 0.5) {+    break // abort linting.+  }+}+```++The second and later calls do nothing.++#### ● The `executeOnText()` method++This method returns the same type of an object as the `executeOnFiles()` method.++Because the returned object of `CLIEngine#executeOnText()` method is the same type as the `CLIEngine#executeOnFiles()` method. The `ESLint` class inherits that mannar.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++const [result] = await ESLint.collectResults(+  eslint.executeOnText(text, filePath),+)++print(result)+```++Example: Using along with the `executeOnFiles()` method.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++const report = useStdin+  ? eslint.executeOnText(text, filePath)+  : eslint.executeOnFiles(patterns)++for await (const result of report) {+  print(result)+}+```++#### ● The `getFormatter()` method++This method returns a `Promise<Formatter>`. The `Formatter` type is a function `(results: AsyncIterable<LintResult>) => AsyncIterable<string>`. It receives lint results then outputs the formatted text.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()+const formatter = eslint.getFormatter("stylish")++// Verify files+const results = eslint.executeOnFiles(patterns)+// Format and write the results+for await (const textPiece of formatter(results)) {+    process.stdout.write(textPiece)+}+```++This means the `getFormatter()` method wraps the current formatter to adapt the interface.++<details>+<summary>A rough sketch of the `getFormatter()` method.</summary>++```js+class ESLint {+  async getFormatter(name) {+    const format = this._cliEngine.getFormatter(name)++    // Return the wrapper.+    return async function* formatter(resultIterator) {+      // Collect and sort the results.+      const results = await ESLint.collectResults(resultIterator)+      results.sort(ESLint.compareResultsByFilePath)++      // Make `rulesMeta`.+      const rules = this._cliEngine.getRules()+      const rulesMeta = getRulesMeta(rules)++      // Format the results with the formatter of the current spec.+      yield format(results, { rulesMeta })+    }+  }+}+```++</details>++Once we got this change, we can realize the following things:++- [RFC45] We can implement to print the results in streaming or to print progress state, without more breaking changes.+- (no RFC yet) We can support ES modules for custom formatters.++#### ● The `outputFixesInIteration()` method++The original `CLIEngine.outputFixes()` static method writes the fix results to the source code files.++The goal of this method is same as the `CLIEngine.outputFixes()` method, but we cannot share async iterators with this method and formatters, so this method receives an `AsyncIterable<LintResult>` object as the first argument then return an `AsyncIterable<LintResult>` object. This method is sandwiched between `executeOnFiles()` and formatters.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()+const formatter = eslint.getFormatter("stylish")++// Verify files+let results = eslint.executeOnFiles(patterns)+// Update the files of the results if needed+if (process.argv.includes("--fix")) {+    results = ESLint.outputFixesInIteration(results)+}+// Format and write the results+for await (const textPiece of formatter(results)) {+    process.stdout.write(textPiece)+}+```++#### ● The `extractErrorResults()` method++The original `CLIEngine.getErrorResults()` static method receives an array of lint results, then extracts only the messages, which are error severity, then returns the results that contain only those.++This method just changed the arrays to async iterables because this method is sandwiched between `executeOnFiles()` and formatters.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()+const formatter = eslint.getFormatter("stylish")++// Verify files+let results = eslint.executeOnFiles(patterns)+// Extract only error results+if (process.argv.includes("--quiet")) {+    results = ESLint.extractErrorResults(results)+}+// Format and write the results+for await (const textPiece of formatter(results)) {+    process.stdout.write(textPiece)+}+```++#### ● The other methods++The following methods return `Promise` which gets fulfilled with each result. Once we got this change, we can support ES modules for shareable configs, plugins, and custom parsers without more breaking changes.++- `getConfigForFile()`+- `getRules()`+- `isPathIgnored()`++The following methods are removed because those don't fit the new API.++- `addPlugin()` ... This method has caused to confuse people. We have introduced this method to add plugin implementations and expected people to use this method to test plugins. But people have often thought that this method loads a new plugin for the following linting so they can use plugins rules without `plugins` setting. And this method is only one that mutates the state of `CLIEngine` objects and messes all caches. Therefore, this RFC moves this functionality to a constructor option. See also "[Constructor](#-constructor)" section.+- `resolveFileGlobPatterns()` ... ESLint doesn't use this logic since `v6.0.0`, but it has stayed there for backward compatibility. Once [RFC20] is implemented, what ESLint iterates and what the glob of this method iterates will be different, then it will confuse users. This is good timing to remove the legacy.++#### ● New methods++- `collectResults()` ... This method receives an async iterable object then returns an array that contains the iterated values.

In that case, I'm not sure it's worth being part of the API.

mysticatea

comment created time in 3 months

Pull request review commenteslint/rfcs

New: `ESLint` Class Replacing `CLIEngine`

+- Start Date: 2019-09-28+- RFC PR: https://github.com/eslint/rfcs/pull/40+- Authors: Toru Nagashima ([@mysticatea](https://github.com/mysticatea))++# `ESLint` Class Replacing `CLIEngine`++## Summary++This RFC adds a new class `ESLint` that provides asynchronous API and deprecates `CLIEngine`.++## Motivation++- We have functionality that cannot be supported with the current synchronous API. For example, ESLint verifying files in parallel, formatters printing progress state, formatters printing results in streams etc. A move to an asynchronous API would be beneficial and a new `ESLint` class can be created with an async API in mind from the start.+- Node.js will support [ES modules](https://nodejs.org/api/esm.html) stably on `13.0.0` at last. Node.js doesn't provide any way that loads ES modules synchronously from CJS. This means that ESLint (CJS) cannot load configs/plugins that are written as ES modules synchronously. Migrating to asynchronous API opens up doors to support those.+- The name of `CLIEngine`, our primary API, has caused confusion in the community and is sub-optimal. We have a lot of issues that say "please use `CLIEngine` instead.". A new class, `ESLint`, while fixing other issues, will also make our primary API more clear.++## Detailed Design++### Add new `ESLint` class++This RFC adds a new class `ESLint`. It has almost the same methods as `CLIEngine`, but the return value of some methods are different.++- [constructor()](#-constructor)+- [executeOnFiles()](#-the-executeonfiles-method)+- [executeOnText()](#-the-executeontext-method)

Yeah, I think the word "lint" would be better than "verify". I still regret calling the original method "verify". :(

mysticatea

comment created time in 3 months

Pull request review commenteslint/rfcs

New: `ESLint` Class Replacing `CLIEngine`

+- Start Date: 2019-09-28+- RFC PR: https://github.com/eslint/rfcs/pull/40+- Authors: Toru Nagashima ([@mysticatea](https://github.com/mysticatea))++# `ESLint` Class Replacing `CLIEngine`++## Summary++This RFC adds a new class `ESLint` that provides asynchronous API and deprecates `CLIEngine`.++## Motivation++- We have functionality that cannot be supported with the current synchronous API. For example, ESLint verifying files in parallel, formatters printing progress state, formatters printing results in streams etc. A move to an asynchronous API would be beneficial and a new `ESLint` class can be created with an async API in mind from the start.+- Node.js will support [ES modules](https://nodejs.org/api/esm.html) stably on `13.0.0` at last. Node.js doesn't provide any way that loads ES modules synchronously from CJS. This means that ESLint (CJS) cannot load configs/plugins that are written as ES modules synchronously. Migrating to asynchronous API opens up doors to support those.+- The name of `CLIEngine`, our primary API, has caused confusion in the community and is sub-optimal. We have a lot of issues that say "please use `CLIEngine` instead.". A new class, `ESLint`, while fixing other issues, will also make our primary API more clear.++## Detailed Design++### Add new `ESLint` class++This RFC adds a new class `ESLint`. It has almost the same methods as `CLIEngine`, but the return value of some methods are different.++- [constructor()](#-constructor)+- [executeOnFiles()](#-the-executeonfiles-method)+- [executeOnText()](#-the-executeontext-method)+- [getFormatter()](#-the-getformatter-method)+- [static outputFixesInIteration()](#-the-outputfixesiniteration-method) (rename)+- [static extractErrorResults()](#-the-extracterrorresults-method) (rename)+- [getConfigForFile()](#-the-other-methods)+- [getRules()](#-the-other-methods)+- [isPathIgnored()](#-the-other-methods)+- ~~addPlugin()~~ (move to a constructor option)+- ~~resolveFileGlobPatterns()~~ (delete)+- [static collectResults()](#-new-methods) (new)+- [static compareResultsByFilePath()](#-new-methods) (new)++Initially the `ESLint` class will be a wrapper around `CLIEngine`, modifying return types. Later it can take on a more independent shape as `CLIEngine` gets more deprecated.++#### ● Constructor++The constructor has mostly the same options as `CLIEngine`, but with some differences:++- It throws fatal errors if the options contain unknown properties or an option is invalid type ([eslint/eslint#10272](https://github.com/eslint/eslint/issues/10272)).+- It disallows the deprecated `cacheFile` option.+- It has a new `pluginImplementations` option as the successor of `addPlugin()` method. This is an object that keys are plugin IDs and each value is the plugin object. See also "[The other methods](#-the-other-methods)" section.++<details>+<summary>A rough sketch of the constructor.</summary>++```js+class ESLint {+  constructor({+    allowInlineConfig = true,+    baseConfig = null,+    cache = false,+    cacheLocation = ".eslintcache",+    configFile = null,+    cwd = process.cwd(),+    envs = [],+    extensions = [".js"],+    fix = false,+    fixTypes = ["problem", "suggestion", "layout"],+    globInputPaths = true,+    globals = [],+    ignore = true,+    ignorePath = null,+    ignorePattern = [],+    parser = "espree",+    parserOptions = null,+    pluginImplementations = null,+    plugins = [],+    reportUnusedDisableDirectives = false,+    resolvePluginsRelativeTo = cwd,+    rulePaths = [],+    rules = null,+    useEslintrc = true,+    ...unknownOptions+  } = {}) {+    // Throws on unknown options+    if (Object.keys(unknownOptions).length >= 1) {+      //...+    }+    // Throws on the invalid value of options+    if (typeof allowInlineConfig !== "boolean") {+      // ...+    }+    if (typeof baseConfig !== "object") {+      // ...+    }+    // and other options...++    // Initialize CLIEngine because this is a tiny wrapper.+    const engine = (this._cliEngine = new CLIEngine({+      allowInlineConfig,+      baseConfig,+      cache,+      cacheLocation,+      configFile,+      cwd,+      envs,+      extensions,+      fix,+      fixTypes,+      globInputPaths,+      globals,+      ignore,+      ignorePath,+      ignorePattern,+      parser,+      parserOptions,+      plugins,+      reportUnusedDisableDirectives,+      resolvePluginsRelativeTo,+      rulePaths,+      rules,+      useEslintrc,+    }))++    // Apply `pluginImplementations` option.+    if (pluginImplementations) {+      for (const [id, definition] of Object.entries(pluginImplementations)) {+        engine.addPlugin(id, definition)+      }+    }+  }+}+```++</details>++#### ● The `executeOnFiles()` method++This method returns an object that implements [AsyncIterable] and [AsyncIterator], as similar to async generators. Therefore we can use the returned object with `for-await-of` statement.

My point is not that progress indicators wouldn't be useful (your gif is nice!), it's that I don't see the value of embedding that functionality in formatters (making them more complicated) when that could just be handled in the CLI by us all the time.

For example, that progress indicator you showed could just be built into ESLint by default. It could run when ESLint starts, disappear when linting is complete, and then run formatters as we do today.

I just don't think there's enough evidence that this functionality needs to be built into formatters.

I believe that it makes sense if we use the standard way in the language rather than the specific way per tool.

Again, I feel like async iteration is too confusing and error-prone for API consumers. Events are a much easier to understand model for what is going on here. You do make a good point about knowing where the events came from, so we could return an event emitter from executeOnFiles():

const session = engine.executeOnFiles(filesToLint);
session.on("start", ({totalFileCount}) => {});
session.on("filestart", ({filePath}) => {});
session.on("filecomplete", ({filePath}) => {});
session.on("complete", ({fileCount}) => {});
session.on("error", (error) => {});

This would allow everything to happen asynchronously and the promise would resolve to the complete event and reject to the error event.

mysticatea

comment created time in 3 months

Pull request review commenteslint/rfcs

New: `ESLint` Class Replacing `CLIEngine`

+- Start Date: 2019-09-28+- RFC PR: https://github.com/eslint/rfcs/pull/40+- Authors: Toru Nagashima ([@mysticatea](https://github.com/mysticatea))++# `ESLint` Class Replacing `CLIEngine`++## Summary++This RFC adds a new class `ESLint` that provides asynchronous API and deprecates `CLIEngine`.++## Motivation++- We have functionality that cannot be supported with the current synchronous API. For example, ESLint verifying files in parallel, formatters printing progress state, formatters printing results in streams etc. A move to an asynchronous API would be beneficial and a new `ESLint` class can be created with an async API in mind from the start.

I understand your reasoning. The problem is that Linter was designed to be the primary object that everyone uses everywhere (including browsers and Node.js) so people are doing the right thing. However, most of the time people looking at the API are wanting to mimic the ESLint CLI in some way, and that's why we have CLIEngine. As far as I know, the most common cases are 1) tooling plugins (editors, build tools, etc.), and 2) standalone CLI wrappers (Standard, XO). Is there another case you're thinking of?

My problem with calling this ESLint is that it is completely limited to running in Node.js, and I think people trying to use the API in a browser would be confused as to why ESLint isn't available. I just don't think a class that is tied to a specific runtime should be given the name ESLint. If you're opposed to including "CLI", that's fine, I just think the class name needs to be explicit that it is not a general-purpose utility and is limited to one runtime or use case.

(A lot of this is because we are dealing with a codebase that is over six years old and the project has grown in ways I didn't anticipate when initially setting up the structure.)

mysticatea

comment created time in 3 months

Pull request review commenteslint/rfcs

New: `ESLint` Class Replacing `CLIEngine`

+- Start Date: 2019-09-28+- RFC PR: https://github.com/eslint/rfcs/pull/40+- Authors: Toru Nagashima ([@mysticatea](https://github.com/mysticatea))++# `ESLint` Class Replacing `CLIEngine`++## Summary++This RFC adds a new class `ESLint` that provides asynchronous API and deprecates `CLIEngine`.++## Motivation++- We have functionality that cannot be supported with the current synchronous API. For example, ESLint verifying files in parallel, formatters printing progress state, formatters printing results in streams etc. A move to an asynchronous API would be beneficial and a new `ESLint` class can be created with an async API in mind from the start.+- Node.js will support [ES modules](https://nodejs.org/api/esm.html) stably on `13.0.0` at last. Node.js doesn't provide any way that loads ES modules synchronously from CJS. This means that ESLint (CJS) cannot load configs/plugins that are written as ES modules synchronously. Migrating to asynchronous API opens up doors to support those.

Ah good to know, thanks.

mysticatea

comment created time in 3 months

Pull request review commenteslint/rfcs

New: `ESLint` Class Replacing `CLIEngine`

+- Start Date: 2019-09-28+- RFC PR: https://github.com/eslint/rfcs/pull/40+- Authors: Toru Nagashima ([@mysticatea](https://github.com/mysticatea))++# `ESLint` Class Replacing `CLIEngine`++## Summary++This RFC adds a new class `ESLint` that provides asynchronous API and deprecates `CLIEngine`.++## Motivation++- We have functionality that cannot be supported with the current synchronous API. For example, ESLint verifying files in parallel, formatters printing progress state, formatters printing results in streams etc. A move to an asynchronous API would be beneficial and a new `ESLint` class can be created with an async API in mind from the start.+- Node.js will support [ES modules](https://nodejs.org/api/esm.html) stably on `13.0.0` at last. Node.js doesn't provide any way that loads ES modules synchronously from CJS. This means that ESLint (CJS) cannot load configs/plugins that are written as ES modules synchronously. Migrating to asynchronous API opens up doors to support those.+- The name of `CLIEngine`, our primary API, has caused confusion in the community and is sub-optimal. We have a lot of issues that say "please use `CLIEngine` instead.". A new class, `ESLint`, while fixing other issues, will also make our primary API more clear.++## Detailed Design++### Add new `ESLint` class++This RFC adds a new class `ESLint`. It has almost the same methods as `CLIEngine`, but the return value of some methods are different.++- [constructor()](#-constructor)+- [executeOnFiles()](#-the-executeonfiles-method)+- [executeOnText()](#-the-executeontext-method)+- [getFormatter()](#-the-getformatter-method)+- [static outputFixesInIteration()](#-the-outputfixesiniteration-method) (rename)+- [static extractErrorResults()](#-the-extracterrorresults-method) (rename)+- [getConfigForFile()](#-the-other-methods)+- [getRules()](#-the-other-methods)+- [isPathIgnored()](#-the-other-methods)+- ~~addPlugin()~~ (move to a constructor option)+- ~~resolveFileGlobPatterns()~~ (delete)+- [static collectResults()](#-new-methods) (new)+- [static compareResultsByFilePath()](#-new-methods) (new)++Initially the `ESLint` class will be a wrapper around `CLIEngine`, modifying return types. Later it can take on a more independent shape as `CLIEngine` gets more deprecated.++#### ● Constructor++The constructor has mostly the same options as `CLIEngine`, but with some differences:++- It throws fatal errors if the options contain unknown properties or an option is invalid type ([eslint/eslint#10272](https://github.com/eslint/eslint/issues/10272)).+- It disallows the deprecated `cacheFile` option.+- It has a new `pluginImplementations` option as the successor of `addPlugin()` method. This is an object that keys are plugin IDs and each value is the plugin object. See also "[The other methods](#-the-other-methods)" section.++<details>+<summary>A rough sketch of the constructor.</summary>++```js+class ESLint {+  constructor({+    allowInlineConfig = true,+    baseConfig = null,+    cache = false,+    cacheLocation = ".eslintcache",+    configFile = null,+    cwd = process.cwd(),+    envs = [],+    extensions = [".js"],+    fix = false,+    fixTypes = ["problem", "suggestion", "layout"],+    globInputPaths = true,+    globals = [],+    ignore = true,+    ignorePath = null,+    ignorePattern = [],+    parser = "espree",+    parserOptions = null,+    pluginImplementations = null,+    plugins = [],+    reportUnusedDisableDirectives = false,+    resolvePluginsRelativeTo = cwd,+    rulePaths = [],+    rules = null,+    useEslintrc = true,+    ...unknownOptions+  } = {}) {+    // Throws on unknown options+    if (Object.keys(unknownOptions).length >= 1) {+      //...+    }+    // Throws on the invalid value of options+    if (typeof allowInlineConfig !== "boolean") {+      // ...+    }+    if (typeof baseConfig !== "object") {+      // ...+    }+    // and other options...++    // Initialize CLIEngine because this is a tiny wrapper.+    const engine = (this._cliEngine = new CLIEngine({+      allowInlineConfig,+      baseConfig,+      cache,+      cacheLocation,+      configFile,+      cwd,+      envs,+      extensions,+      fix,+      fixTypes,+      globInputPaths,+      globals,+      ignore,+      ignorePath,+      ignorePattern,+      parser,+      parserOptions,+      plugins,+      reportUnusedDisableDirectives,+      resolvePluginsRelativeTo,+      rulePaths,+      rules,+      useEslintrc,+    }))++    // Apply `pluginImplementations` option.+    if (pluginImplementations) {+      for (const [id, definition] of Object.entries(pluginImplementations)) {+        engine.addPlugin(id, definition)+      }+    }+  }+}+```++</details>++#### ● The `executeOnFiles()` method++This method returns an object that implements [AsyncIterable] and [AsyncIterator], as similar to async generators. Therefore we can use the returned object with `for-await-of` statement.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++for await (const result of eslint.executeOnFiles(patterns)) {+  print(result)+}+```++The returned object yields the lint result of each file immediately when it has finished linting each file. Therefore, ESLint doesn't guarantee the order of the iteration. The order is random.++This method must not throw any errors synchronously. Errors may happen in iteration asynchronously.++<details>+<summary>A rough sketch of the `executeOnFiles()` method.</summary>++A tiny wrapper of `CLIEngine`.++```js+class ESLint {+  async *executeOnFiles(patterns) {+    yield* this._cliEngine.executeOnFiles(patterns).results+  }+}+```++But once [RFC42] is implemented, the returned object will be an instance of [`LintResultGenerator`](https://github.com/eslint/eslint/blob/836c0e48704d70bc1a5cbdbf0211368b0ada942d/lib/eslint/lint-result-generator.js#L136) class.++</details>++If you want to use the returned object with `await` expression, you can use a small utility to convert an async iterable object to an array.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++// Convert the results to an array.+const results = await ESLint.collectResults(eslint.executeOnFiles(patterns))++// Optionally you can sort the results.+results.sort(ESLint.compareResultsByFilePath)++print(results)+```++Once we got this change, we can realize the following things:++- [RFC42] We can implement linting in parallel by worker threads. It will reduce spending time of linting much.+- [RFC45] We can implement to print the results in streaming or to print progress state, without more breaking changes. Because ESLint may spend time to lint files (for example, ESLint needs about 20 seconds to lint [our codebase](https://github.com/eslint/eslint)), to print progress state will be useful.+- (no RFC yet) We can support ES modules for shareable configs, plugins, and custom parsers.++##### Iterate only one time++We can iterate the returned object of this method only one time similar to generators.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++const resultGenerator = eslint.executeOnFiles(patterns)+for await (const result of resultGenerator) {+  print(result)+}+// ↓ Throw "This generator has been consumed already"+for await (const result of resultGenerator) {+  print(result)+}+```++##### Move the `usedDeprecatedRules` property++The returned object of `CLIEngine#executeOnFiles()` has the `usedDeprecatedRules` property that includes the deprecated rule IDs which the linting used.++But the location doesn't fit asynchronous because the used deprecated rule list is not determined until the iteration finished. Therefore, this RFC moves the `usedDeprecatedRules` property to each lint result.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++for await (const result of eslint.executeOnFiles(patterns)) {+  console.log(result.usedDeprecatedRules)+}+```++As a side-effect, formatters gets the capability to print the used deprecated rules. Previously, ESLint has not passed the returned object to formatters, so the formatters could not print used deprecated rules. After this RFC, each lint result has the `usedDeprecatedRules` property and the formatters receive those.++##### Disallow execution in parallel++Because this method updates the cache file, it will break the cache file if called multiple times in parallel. To prevent that, every call of `executeOnFiles()` must wait for the previous call finishes.

This seems like a pretty big constraint to place on API consumers. It seems like each run should be able to:

  1. Read data in from the cache file, store the mtime or hash
  2. Do its linting
  3. Write data back to the cache file if the mtime or hash is the same

Of course, this would only be if the run makes use of a cache file, but I don't think throwing an error when a second run starts while a first run is in process makes sense. We don't know how many instances of ESLint (or whatever the name ends up being) an application will create.

mysticatea

comment created time in 3 months

Pull request review commenteslint/rfcs

New: `ESLint` Class Replacing `CLIEngine`

+- Start Date: 2019-09-28+- RFC PR: https://github.com/eslint/rfcs/pull/40+- Authors: Toru Nagashima ([@mysticatea](https://github.com/mysticatea))++# `ESLint` Class Replacing `CLIEngine`++## Summary++This RFC adds a new class `ESLint` that provides asynchronous API and deprecates `CLIEngine`.++## Motivation++- We have functionality that cannot be supported with the current synchronous API. For example, ESLint verifying files in parallel, formatters printing progress state, formatters printing results in streams etc. A move to an asynchronous API would be beneficial and a new `ESLint` class can be created with an async API in mind from the start.+- Node.js will support [ES modules](https://nodejs.org/api/esm.html) stably on `13.0.0` at last. Node.js doesn't provide any way that loads ES modules synchronously from CJS. This means that ESLint (CJS) cannot load configs/plugins that are written as ES modules synchronously. Migrating to asynchronous API opens up doors to support those.+- The name of `CLIEngine`, our primary API, has caused confusion in the community and is sub-optimal. We have a lot of issues that say "please use `CLIEngine` instead.". A new class, `ESLint`, while fixing other issues, will also make our primary API more clear.++## Detailed Design++### Add new `ESLint` class++This RFC adds a new class `ESLint`. It has almost the same methods as `CLIEngine`, but the return value of some methods are different.++- [constructor()](#-constructor)+- [executeOnFiles()](#-the-executeonfiles-method)+- [executeOnText()](#-the-executeontext-method)+- [getFormatter()](#-the-getformatter-method)+- [static outputFixesInIteration()](#-the-outputfixesiniteration-method) (rename)+- [static extractErrorResults()](#-the-extracterrorresults-method) (rename)+- [getConfigForFile()](#-the-other-methods)+- [getRules()](#-the-other-methods)+- [isPathIgnored()](#-the-other-methods)+- ~~addPlugin()~~ (move to a constructor option)+- ~~resolveFileGlobPatterns()~~ (delete)+- [static collectResults()](#-new-methods) (new)+- [static compareResultsByFilePath()](#-new-methods) (new)++Initially the `ESLint` class will be a wrapper around `CLIEngine`, modifying return types. Later it can take on a more independent shape as `CLIEngine` gets more deprecated.++#### ● Constructor++The constructor has mostly the same options as `CLIEngine`, but with some differences:++- It throws fatal errors if the options contain unknown properties or an option is invalid type ([eslint/eslint#10272](https://github.com/eslint/eslint/issues/10272)).+- It disallows the deprecated `cacheFile` option.+- It has a new `pluginImplementations` option as the successor of `addPlugin()` method. This is an object that keys are plugin IDs and each value is the plugin object. See also "[The other methods](#-the-other-methods)" section.++<details>+<summary>A rough sketch of the constructor.</summary>++```js+class ESLint {+  constructor({+    allowInlineConfig = true,+    baseConfig = null,+    cache = false,+    cacheLocation = ".eslintcache",+    configFile = null,+    cwd = process.cwd(),+    envs = [],+    extensions = [".js"],+    fix = false,+    fixTypes = ["problem", "suggestion", "layout"],+    globInputPaths = true,+    globals = [],+    ignore = true,+    ignorePath = null,+    ignorePattern = [],+    parser = "espree",+    parserOptions = null,+    pluginImplementations = null,+    plugins = [],+    reportUnusedDisableDirectives = false,+    resolvePluginsRelativeTo = cwd,+    rulePaths = [],+    rules = null,+    useEslintrc = true,+    ...unknownOptions+  } = {}) {+    // Throws on unknown options+    if (Object.keys(unknownOptions).length >= 1) {+      //...+    }+    // Throws on the invalid value of options+    if (typeof allowInlineConfig !== "boolean") {+      // ...+    }+    if (typeof baseConfig !== "object") {+      // ...+    }+    // and other options...++    // Initialize CLIEngine because this is a tiny wrapper.+    const engine = (this._cliEngine = new CLIEngine({+      allowInlineConfig,+      baseConfig,+      cache,+      cacheLocation,+      configFile,+      cwd,+      envs,+      extensions,+      fix,+      fixTypes,+      globInputPaths,+      globals,+      ignore,+      ignorePath,+      ignorePattern,+      parser,+      parserOptions,+      plugins,+      reportUnusedDisableDirectives,+      resolvePluginsRelativeTo,+      rulePaths,+      rules,+      useEslintrc,+    }))++    // Apply `pluginImplementations` option.+    if (pluginImplementations) {+      for (const [id, definition] of Object.entries(pluginImplementations)) {+        engine.addPlugin(id, definition)+      }+    }+  }+}+```++</details>++#### ● The `executeOnFiles()` method++This method returns an object that implements [AsyncIterable] and [AsyncIterator], as similar to async generators. Therefore we can use the returned object with `for-await-of` statement.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++for await (const result of eslint.executeOnFiles(patterns)) {+  print(result)+}+```++The returned object yields the lint result of each file immediately when it has finished linting each file. Therefore, ESLint doesn't guarantee the order of the iteration. The order is random.++This method must not throw any errors synchronously. Errors may happen in iteration asynchronously.++<details>+<summary>A rough sketch of the `executeOnFiles()` method.</summary>++A tiny wrapper of `CLIEngine`.++```js+class ESLint {+  async *executeOnFiles(patterns) {+    yield* this._cliEngine.executeOnFiles(patterns).results+  }+}+```++But once [RFC42] is implemented, the returned object will be an instance of [`LintResultGenerator`](https://github.com/eslint/eslint/blob/836c0e48704d70bc1a5cbdbf0211368b0ada942d/lib/eslint/lint-result-generator.js#L136) class.++</details>++If you want to use the returned object with `await` expression, you can use a small utility to convert an async iterable object to an array.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++// Convert the results to an array.+const results = await ESLint.collectResults(eslint.executeOnFiles(patterns))++// Optionally you can sort the results.+results.sort(ESLint.compareResultsByFilePath)++print(results)+```++Once we got this change, we can realize the following things:++- [RFC42] We can implement linting in parallel by worker threads. It will reduce spending time of linting much.+- [RFC45] We can implement to print the results in streaming or to print progress state, without more breaking changes. Because ESLint may spend time to lint files (for example, ESLint needs about 20 seconds to lint [our codebase](https://github.com/eslint/eslint)), to print progress state will be useful.+- (no RFC yet) We can support ES modules for shareable configs, plugins, and custom parsers.++##### Iterate only one time++We can iterate the returned object of this method only one time similar to generators.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++const resultGenerator = eslint.executeOnFiles(patterns)+for await (const result of resultGenerator) {+  print(result)+}+// ↓ Throw "This generator has been consumed already"+for await (const result of resultGenerator) {+  print(result)+}+```++##### Move the `usedDeprecatedRules` property++The returned object of `CLIEngine#executeOnFiles()` has the `usedDeprecatedRules` property that includes the deprecated rule IDs which the linting used.++But the location doesn't fit asynchronous because the used deprecated rule list is not determined until the iteration finished. Therefore, this RFC moves the `usedDeprecatedRules` property to each lint result.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++for await (const result of eslint.executeOnFiles(patterns)) {+  console.log(result.usedDeprecatedRules)+}+```++As a side-effect, formatters gets the capability to print the used deprecated rules. Previously, ESLint has not passed the returned object to formatters, so the formatters could not print used deprecated rules. After this RFC, each lint result has the `usedDeprecatedRules` property and the formatters receive those.++##### Disallow execution in parallel++Because this method updates the cache file, it will break the cache file if called multiple times in parallel. To prevent that, every call of `executeOnFiles()` must wait for the previous call finishes.++##### Abort linting++The iterator interface has optional [`return()` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/return) that forces to finish the iterator. The `for-of`/`for-await-of` syntax calls the `return()` method automatically if the loop is stopped through a `braek`, `return`, or `throw`.++ESLint aborts linting when the `return()` method is called. The first `return()` method call does:++- ESLint cancels the linting of all pending files.+- ESLint updates the cache file with the current state. Therefore, the next time, ESLint can use the cache of the already linted files and lint only the canceled files.+- ESLint will terminate all workers if [RFC42] is implemented.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++for await (const result of eslint.executeOnFiles(patterns)) {+  if (Math.random() < 0.5) {+    break // abort linting.+  }+}+```++The second and later calls do nothing.++#### ● The `executeOnText()` method++This method returns the same type of an object as the `executeOnFiles()` method.++Because the returned object of `CLIEngine#executeOnText()` method is the same type as the `CLIEngine#executeOnFiles()` method. The `ESLint` class inherits that mannar.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++const [result] = await ESLint.collectResults(+  eslint.executeOnText(text, filePath),+)++print(result)+```++Example: Using along with the `executeOnFiles()` method.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()++const report = useStdin+  ? eslint.executeOnText(text, filePath)+  : eslint.executeOnFiles(patterns)++for await (const result of report) {+  print(result)+}+```++#### ● The `getFormatter()` method++This method returns a `Promise<Formatter>`. The `Formatter` type is a function `(results: AsyncIterable<LintResult>) => AsyncIterable<string>`. It receives lint results then outputs the formatted text.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()+const formatter = eslint.getFormatter("stylish")++// Verify files+const results = eslint.executeOnFiles(patterns)+// Format and write the results+for await (const textPiece of formatter(results)) {+    process.stdout.write(textPiece)+}+```++This means the `getFormatter()` method wraps the current formatter to adapt the interface.++<details>+<summary>A rough sketch of the `getFormatter()` method.</summary>++```js+class ESLint {+  async getFormatter(name) {+    const format = this._cliEngine.getFormatter(name)++    // Return the wrapper.+    return async function* formatter(resultIterator) {+      // Collect and sort the results.+      const results = await ESLint.collectResults(resultIterator)+      results.sort(ESLint.compareResultsByFilePath)++      // Make `rulesMeta`.+      const rules = this._cliEngine.getRules()+      const rulesMeta = getRulesMeta(rules)++      // Format the results with the formatter of the current spec.+      yield format(results, { rulesMeta })+    }+  }+}+```++</details>++Once we got this change, we can realize the following things:++- [RFC45] We can implement to print the results in streaming or to print progress state, without more breaking changes.+- (no RFC yet) We can support ES modules for custom formatters.++#### ● The `outputFixesInIteration()` method++The original `CLIEngine.outputFixes()` static method writes the fix results to the source code files.++The goal of this method is same as the `CLIEngine.outputFixes()` method, but we cannot share async iterators with this method and formatters, so this method receives an `AsyncIterable<LintResult>` object as the first argument then return an `AsyncIterable<LintResult>` object. This method is sandwiched between `executeOnFiles()` and formatters.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()+const formatter = eslint.getFormatter("stylish")++// Verify files+let results = eslint.executeOnFiles(patterns)+// Update the files of the results if needed+if (process.argv.includes("--fix")) {+    results = ESLint.outputFixesInIteration(results)+}+// Format and write the results+for await (const textPiece of formatter(results)) {+    process.stdout.write(textPiece)+}+```++#### ● The `extractErrorResults()` method++The original `CLIEngine.getErrorResults()` static method receives an array of lint results, then extracts only the messages, which are error severity, then returns the results that contain only those.++This method just changed the arrays to async iterables because this method is sandwiched between `executeOnFiles()` and formatters.++```js+const { ESLint } = require("eslint")+const eslint = new ESLint()+const formatter = eslint.getFormatter("stylish")++// Verify files+let results = eslint.executeOnFiles(patterns)+// Extract only error results+if (process.argv.includes("--quiet")) {+    results = ESLint.extractErrorResults(results)+}+// Format and write the results+for await (const textPiece of formatter(results)) {+    process.stdout.write(textPiece)+}+```++#### ● The other methods++The following methods return `Promise` which gets fulfilled with each result. Once we got this change, we can support ES modules for shareable configs, plugins, and custom parsers without more breaking changes.++- `getConfigForFile()`+- `getRules()`+- `isPathIgnored()`++The following methods are removed because those don't fit the new API.++- `addPlugin()` ... This method has caused to confuse people. We have introduced this method to add plugin implementations and expected people to use this method to test plugins. But people have often thought that this method loads a new plugin for the following linting so they can use plugins rules without `plugins` setting. And this method is only one that mutates the state of `CLIEngine` objects and messes all caches. Therefore, this RFC moves this functionality to a constructor option. See also "[Constructor](#-constructor)" section.+- `resolveFileGlobPatterns()` ... ESLint doesn't use this logic since `v6.0.0`, but it has stayed there for backward compatibility. Once [RFC20] is implemented, what ESLint iterates and what the glob of this method iterates will be different, then it will confuse users. This is good timing to remove the legacy.++#### ● New methods++- `collectResults()` ... This method receives an async iterable object then returns an array that contains the iterated values.

This doesn't seem ESLint specific?

mysticatea

comment created time in 3 months

Pull request review commenteslint/rfcs

New: `ESLint` Class Replacing `CLIEngine`

+- Start Date: 2019-09-28+- RFC PR: https://github.com/eslint/rfcs/pull/40+- Authors: Toru Nagashima ([@mysticatea](https://github.com/mysticatea))++# `ESLint` Class Replacing `CLIEngine`++## Summary++This RFC adds a new class `ESLint` that provides asynchronous API and deprecates `CLIEngine`.++## Motivation++- We have functionality that cannot be supported with the current synchronous API. For example, ESLint verifying files in parallel, formatters printing progress state, formatters printing results in streams etc. A move to an asynchronous API would be beneficial and a new `ESLint` class can be created with an async API in mind from the start.+- Node.js will support [ES modules](https://nodejs.org/api/esm.html) stably on `13.0.0` at last. Node.js doesn't provide any way that loads ES modules synchronously from CJS. This means that ESLint (CJS) cannot load configs/plugins that are written as ES modules synchronously. Migrating to asynchronous API opens up doors to support those.+- The name of `CLIEngine`, our primary API, has caused confusion in the community and is sub-optimal. We have a lot of issues that say "please use `CLIEngine` instead.". A new class, `ESLint`, while fixing other issues, will also make our primary API more clear.++## Detailed Design++### Add new `ESLint` class++This RFC adds a new class `ESLint`. It has almost the same methods as `CLIEngine`, but the return value of some methods are different.++- [constructor()](#-constructor)+- [executeOnFiles()](#-the-executeonfiles-method)+- [executeOnText()](#-the-executeontext-method)+- [getFormatter()](#-the-getformatter-method)+- [static outputFixesInIteration()](#-the-outputfixesiniteration-method) (rename)+- [static extractErrorResults()](#-the-extracterrorresults-method) (rename)+- [getConfigForFile()](#-the-other-methods)+- [getRules()](#-the-other-methods)+- [isPathIgnored()](#-the-other-methods)+- ~~addPlugin()~~ (move to a constructor option)+- ~~resolveFileGlobPatterns()~~ (delete)+- [static collectResults()](#-new-methods) (new)+- [static compareResultsByFilePath()](#-new-methods) (new)++Initially the `ESLint` class will be a wrapper around `CLIEngine`, modifying return types. Later it can take on a more independent shape as `CLIEngine` gets more deprecated.++#### ● Constructor++The constructor has mostly the same options as `CLIEngine`, but with some differences:++- It throws fatal errors if the options contain unknown properties or an option is invalid type ([eslint/eslint#10272](https://github.com/eslint/eslint/issues/10272)).+- It disallows the deprecated `cacheFile` option.+- It has a new `pluginImplementations` option as the successor of `addPlugin()` method. This is an object that keys are plugin IDs and each value is the plugin object. See also "[The other methods](#-the-other-methods)" section.++<details>+<summary>A rough sketch of the constructor.</summary>++```js+class ESLint {+  constructor({+    allowInlineConfig = true,+    baseConfig = null,+    cache = false,+    cacheLocation = ".eslintcache",+    configFile = null,+    cwd = process.cwd(),+    envs = [],+    extensions = [".js"],+    fix = false,+    fixTypes = ["problem", "suggestion", "layout"],+    globInputPaths = true,+    globals = [],+    ignore = true,+    ignorePath = null,+    ignorePattern = [],+    parser = "espree",+    parserOptions = null,+    pluginImplementations = null,+    plugins = [],+    reportUnusedDisableDirectives = false,+    resolvePluginsRelativeTo = cwd,+    rulePaths = [],+    rules = null,+    useEslintrc = true,+    ...unknownOptions+  } = {}) {+    // Throws on unknown options+    if (Object.keys(unknownOptions).length >= 1) {+      //...+    }+    // Throws on the invalid value of options+    if (typeof allowInlineConfig !== "boolean") {+      // ...+    }+    if (typeof baseConfig !== "object") {+      // ...+    }+    // and other options...++    // Initialize CLIEngine because this is a tiny wrapper.+    const engine = (this._cliEngine = new CLIEngine({+      allowInlineConfig,+      baseConfig,+      cache,+      cacheLocation,+      configFile,+      cwd,+      envs,+      extensions,+      fix,+      fixTypes,+      globInputPaths,+      globals,+      ignore,+      ignorePath,+      ignorePattern,+      parser,+      parserOptions,+      plugins,+      reportUnusedDisableDirectives,+      resolvePluginsRelativeTo,+      rulePaths,+      rules,+      useEslintrc,+    }))++    // Apply `pluginImplementations` option.+    if (pluginImplementations) {+      for (const [id, definition] of Object.entries(pluginImplementations)) {+        engine.addPlugin(id, definition)+      }+    }+  }+}+```++</details>++#### ● The `executeOnFiles()` method++This method returns an object that implements [AsyncIterable] and [AsyncIterator], as similar to async generators. Therefore we can use the returned object with `for-await-of` statement.

See end note about async iteration approach.

mysticatea

comment created time in 3 months

Pull request review commenteslint/rfcs

New: `ESLint` Class Replacing `CLIEngine`

+- Start Date: 2019-09-28+- RFC PR: https://github.com/eslint/rfcs/pull/40+- Authors: Toru Nagashima ([@mysticatea](https://github.com/mysticatea))++# `ESLint` Class Replacing `CLIEngine`++## Summary++This RFC adds a new class `ESLint` that provides asynchronous API and deprecates `CLIEngine`.++## Motivation++- We have functionality that cannot be supported with the current synchronous API. For example, ESLint verifying files in parallel, formatters printing progress state, formatters printing results in streams etc. A move to an asynchronous API would be beneficial and a new `ESLint` class can be created with an async API in mind from the start.+- Node.js will support [ES modules](https://nodejs.org/api/esm.html) stably on `13.0.0` at last. Node.js doesn't provide any way that loads ES modules synchronously from CJS. This means that ESLint (CJS) cannot load configs/plugins that are written as ES modules synchronously. Migrating to asynchronous API opens up doors to support those.+- The name of `CLIEngine`, our primary API, has caused confusion in the community and is sub-optimal. We have a lot of issues that say "please use `CLIEngine` instead.". A new class, `ESLint`, while fixing other issues, will also make our primary API more clear.++## Detailed Design++### Add new `ESLint` class++This RFC adds a new class `ESLint`. It has almost the same methods as `CLIEngine`, but the return value of some methods are different.++- [constructor()](#-constructor)+- [executeOnFiles()](#-the-executeonfiles-method)+- [executeOnText()](#-the-executeontext-method)+- [getFormatter()](#-the-getformatter-method)+- [static outputFixesInIteration()](#-the-outputfixesiniteration-method) (rename)+- [static extractErrorResults()](#-the-extracterrorresults-method) (rename)+- [getConfigForFile()](#-the-other-methods)+- [getRules()](#-the-other-methods)+- [isPathIgnored()](#-the-other-methods)+- ~~addPlugin()~~ (move to a constructor option)+- ~~resolveFileGlobPatterns()~~ (delete)+- [static collectResults()](#-new-methods) (new)+- [static compareResultsByFilePath()](#-new-methods) (new)++Initially the `ESLint` class will be a wrapper around `CLIEngine`, modifying return types. Later it can take on a more independent shape as `CLIEngine` gets more deprecated.++#### ● Constructor++The constructor has mostly the same options as `CLIEngine`, but with some differences:++- It throws fatal errors if the options contain unknown properties or an option is invalid type ([eslint/eslint#10272](https://github.com/eslint/eslint/issues/10272)).+- It disallows the deprecated `cacheFile` option.+- It has a new `pluginImplementations` option as the successor of `addPlugin()` method. This is an object that keys are plugin IDs and each value is the plugin object. See also "[The other methods](#-the-other-methods)" section.

Can this be combined with plugins to avoid needing two keys?

{
    plugins: {
        "react",
        "foo": { /* some object*/ }
    }
mysticatea

comment created time in 3 months

Pull request review commenteslint/rfcs

New: `ESLint` Class Replacing `CLIEngine`

+- Start Date: 2019-09-28+- RFC PR: https://github.com/eslint/rfcs/pull/40+- Authors: Toru Nagashima ([@mysticatea](https://github.com/mysticatea))++# `ESLint` Class Replacing `CLIEngine`++## Summary++This RFC adds a new class `ESLint` that provides asynchronous API and deprecates `CLIEngine`.++## Motivation++- We have functionality that cannot be supported with the current synchronous API. For example, ESLint verifying files in parallel, formatters printing progress state, formatters printing results in streams etc. A move to an asynchronous API would be beneficial and a new `ESLint` class can be created with an async API in mind from the start.+- Node.js will support [ES modules](https://nodejs.org/api/esm.html) stably on `13.0.0` at last. Node.js doesn't provide any way that loads ES modules synchronously from CJS. This means that ESLint (CJS) cannot load configs/plugins that are written as ES modules synchronously. Migrating to asynchronous API opens up doors to support those.+- The name of `CLIEngine`, our primary API, has caused confusion in the community and is sub-optimal. We have a lot of issues that say "please use `CLIEngine` instead.". A new class, `ESLint`, while fixing other issues, will also make our primary API more clear.++## Detailed Design++### Add new `ESLint` class++This RFC adds a new class `ESLint`. It has almost the same methods as `CLIEngine`, but the return value of some methods are different.++- [constructor()](#-constructor)+- [executeOnFiles()](#-the-executeonfiles-method)+- [executeOnText()](#-the-executeontext-method)+- [getFormatter()](#-the-getformatter-method)

I think this might want to be async as it may load a custom formatter from disk.

mysticatea

comment created time in 3 months

more