Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Flat config support in Linter (refs #13481) #15185

Merged
merged 37 commits into from Dec 4, 2021
Merged

Conversation

nzakas
Copy link
Member

@nzakas nzakas commented Oct 18, 2021

Prerequisites checklist

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
[x] Add something to the core
[ ] Other, please explain:

What changes did you make? (Give an overview)

Updated Linter to accept FlatConfigArray as an argument. To do so, I added a new configType option that must be set to "flat". When set to "flat", the defineRule, defineRules, and defineParser methods all throw an error because there is no good way to mimic this functionality with flat config. (People can instead define a plugin directly in the config.) Ultimately, we'll end up removing these methods.

I've only added enough tests to verify each of the initial paths for flat config. In a future pull request I'll work on converting the existing tests to work with flat config. I just don't want to wait too long before merging these changes in order to avoid conflicts.

Note: These changes are a prerequisite for the ESLint class changes.

Task list:

  • Linting both with and without processors
  • Environments do not work when configType is "flat"
  • Globals added via comments are correctly added to the global scope
  • Autofixing
  • context.parserPath compatibility
  • context.languageOptions doesn't exist
  • languageOptions.ecmaVersion is year-based (2015 instead of 6)
  • languageOptions.ecmaVersion translates to parserOptions.ecmaVersion
  • languageOptions.ecmaVersion adds globals for the appropriate version
  • languageOptions.ecmaVersion: 3 only has ES3 globals
  • languageOptions.sourceType: "commonjs" adds Node.js globals
  • languageOptions.sourceType: "commonjs" switches parser options to parse global return
  • Convert all eslintrc tests into flat config tests
  • Update defaults (reconsider the defaults of eslint config #14588)
  • All tests passing

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

Is there any danger in merging this?

@nzakas nzakas added core Relates to ESLint's core APIs and features accepted There is consensus among the team that this change meets the criteria for inclusion labels Oct 18, 2021
lib/linter/linter.js Outdated Show resolved Hide resolved
lib/linter/linter.js Outdated Show resolved Hide resolved
@nzakas
Copy link
Member Author

nzakas commented Oct 20, 2021

Since feedback is a bit slow these days, I'm going to convert to a draft and keep adding changes to this PR.

@nzakas nzakas marked this pull request as draft October 20, 2021 16:55
@nzakas
Copy link
Member Author

nzakas commented Oct 25, 2021

With context.parserPath -- it appears there's no easy way to make this work. I thought I could stick something into require.cache, but I didn't fully appreciate the complexity of attempting to do that.

At this point, I think the best path forward is to encourage people to switch to using context.languageOptions.parser if present and default back to context.parserPath if not.

This situation will only arise when someone is using flat config, which case context.parserPath won't have a value; for eslintrc configs, everything will continue to work as usual.

Thoughts?

@mdjermanovic mdjermanovic changed the title Update: Flat config support in Linter (refs #13481) feat: Flat config support in Linter (refs #13481) Oct 26, 2021
@mdjermanovic
Copy link
Member

With context.parserPath -- it appears there's no easy way to make this work. I thought I could stick something into require.cache, but I didn't fully appreciate the complexity of attempting to do that.

I don't think there's any way to reliably find the path, because the config gets just the loaded parser object.

At this point, I think the best path forward is to encourage people to switch to using context.languageOptions.parser if present and default back to context.parserPath if not.

Agree, this was already predicted in the RFC:

  1. Replace context.parserPath with context.parser (the path to the parser is no longer valid)

@@ -0,0 +1,138 @@
/**
* @fileoverview Globals for ecmaVersion/sourceType
* @author Nicholas C. Zakas
Copy link
Member Author

@nzakas nzakas Oct 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved globals here since the list is much smaller without environments, allowing us to update more easily and also support ES3 correctly (right now we still add JSON when ecmaVersion is 3.

sourceType: "module",
parser: "@/espree",
parserOptions: {}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating the defaults as briefly discussed in #14588. I think it makes sense to assume people will be using ESM and always want the latest ecmaVersion.

{
files: ["**/*.cjs"],
languageOptions: {
sourceType: "commonjs"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For .cjs files, I think it makes sense to opt them into "commonjs" sourceType.

@nzakas
Copy link
Member Author

nzakas commented Nov 23, 2021

Okay, I think I've got everything

@nzakas
Copy link
Member Author

nzakas commented Nov 24, 2021

I just upgraded Espree and eslint-scope to the versions that support sourceType:commonjs and eliminated a bunch of the code that managed that conversion. Should be all set now.

lib/linter/linter.js Outdated Show resolved Hide resolved
lib/linter/linter.js Show resolved Hide resolved
lib/linter/linter.js Outdated Show resolved Hide resolved
nzakas and others added 2 commits November 26, 2021 10:09
Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
@mdjermanovic
Copy link
Member

Is there any danger in merging this?

I could find only 3 differences with eslintrc configs before and after this change.


  1. If configured parserOptions doesn't have sourceType property, what will be passed to parsers as sourceType property in the options object that is the second argument of parse()/parseForESLint().
  • Before this change: there will be no sourceType property in the options object.
  • After this change: sourceType: undefined.

In theory, this could affect parsing, depending on how the handling of options is implemented in a custom parser, although it does not seem likely. Thoughts? I checked espree, @typescript-eslint/parser and @babel/eslint-parser, and It doesn't seem that this change will affect the behavior.


  1. Possible ecmaVersion values passed to eslint-scope.
  • Before this change: 3, 5, 6, 7, 8, 9, 10, 11, 12, 13.
  • After this change: 3, 5, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022.

eslint-scope expects ecmaVersion normalized to 6 ... 13, not 2015 ... 2022. Nonetheless, this change doesn't really affect the scope analysis, because the only conditions related to ecmaVersion in eslint-scope are ecmaVersion >= 5 and ecmaVersion >= 6, so they'll work the same with 2015 ... 2022 instead of 6 ... 13. However, there are no tests in eslint-scope that would make sure that 2015 and others work the same as 6, all tests are based on the 6 ... 13 normalization.


  1. ecmaVersion passed to eslint-scope when a custom parser is used and ecmaVersion is not a number.

Similar to the previous point, this doesn't really change how eslint-scope works.

@nzakas
Copy link
Member Author

nzakas commented Nov 27, 2021

  1. I don’t think it’s worth addressing this now. While it’s technically a change, it shouldn’t affect anything. And if it does affect something, it will be easier to solve when we have a real world use case.

  2. Can you open an issue on eslint-scope for that? We can certainly add tests.

  3. This is intentional. The new default ecmaVersion with flat config is latest.

@mdjermanovic
Copy link
Member

2. Can you open an issue on eslint-scope for that? We can certainly add tests.

eslint/eslint-scope#82

Comment on lines 1726 to 1729
// check for options-based processing
if (options.preprocess || options.postprocess) {
return this._verifyWithFlatConfigArrayAndProcessor(textOrSourceCode, config, options);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the if (config.processor) path, _verifyWithFlatConfigArray calls _verifyWithFlatConfigArrayAndProcessor with options.preprocess, and then _verifyWithFlatConfigArrayAndProcessor calls _verifyWithFlatConfigArray with the same options.preprocess, so this will run config.processor.preprocess() again on its output:

const { Linter } = require("eslint");

const linter = new Linter({ configType: "flat" });

const config = {
    files: ["*.md"],
    processor: {
        preprocess(text, filename) {
            console.log(`Called with text = '${text}', filename = '${filename}'`);

            return [{ text: "bar", filename: "0.js" }];
        },
        postprocess() {
            return [];
        }
    }
};

linter.verify("foo", config, "a.md");

// logs:
//       Called with text = 'foo', filename = 'a.md'
//       Called with text = 'bar', filename = 'a.md/0_0.js'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, nice catch. I'll look at that.

Just FYI: It was a little bit difficult to understand that you were pointing out a problem here. When you state the behavior you're seeing, it would help me out a lot if you'd say something like, "This behavior is unexpected and I don't see it when using eslintrc." This is probably just the brain fog, but little hints like saying "unexpected" make it a lot easier for me to decipher descriptions.

Comment on lines +1552 to +1558
if (
parserOptions.sourceType === "module" &&
parserOptions.ecmaFeatures &&
parserOptions.ecmaFeatures.globalReturn
) {
parserOptions.ecmaFeatures.globalReturn = false;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_verifyWithoutProcessors (resolveParserOptions) is overwriting globalReturn regardless of the parser, but _verifyWithFlatConfigArrayAndWithoutProcessors will do that only if the parser is Espree?

Copy link
Member Author

@nzakas nzakas Nov 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I don’t think we should be messing around with other parsers’ options. Ideally, the parser itself will throw an error with an unexpected combination of options. I think we should do that, too, but it would be a breaking change.

Copy link
Member

@mdjermanovic mdjermanovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@nzakas
Copy link
Member Author

nzakas commented Dec 2, 2021

@btmills can you give this a once-over before I merge?

Copy link
Member

@btmills btmills left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only found one potential functional change where this branch is currently passing void 0 as parserName. I left a couple inline suggestions, but if the void 0 question is answered, this can be merged as-is.

I'm very excited to see this go into the release, even if undocumented. Great work!

lib/shared/types.js Outdated Show resolved Hide resolved
checker(context) {
return {
Program() {
assert.strictEqual(context.languageOptions.ecmaVersion, 2015);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be possible for this test and the next few, which also use this pattern, to pass if Program() were never called. Elsewhere you declare let ecmaVersion; above, assign it here, and move the assertion to the line after the verify() call so that the assertion is guaranteed to be called. Not a required change, but I wanted to point it out in case you prefer that pattern.

lib/linter/linter.js Outdated Show resolved Hide resolved
tests/lib/linter/linter.js Outdated Show resolved Hide resolved
tests/lib/linter/linter.js Outdated Show resolved Hide resolved
Co-authored-by: Brandon Mills <btmills@users.noreply.github.com>
@eslint-github-bot eslint-github-bot bot added the feature This change adds a new feature to ESLint label Dec 4, 2021
nzakas and others added 3 commits December 3, 2021 17:15
Co-authored-by: Brandon Mills <btmills@users.noreply.github.com>
Co-authored-by: Brandon Mills <btmills@users.noreply.github.com>
Co-authored-by: Brandon Mills <btmills@users.noreply.github.com>
@mdjermanovic mdjermanovic merged commit 32ac37a into main Dec 4, 2021
@mdjermanovic mdjermanovic deleted the flat-config-linter branch December 4, 2021 01:31
@eslint-github-bot eslint-github-bot bot locked and limited conversation to collaborators Jun 3, 2022
@eslint-github-bot eslint-github-bot bot added the archived due to age This issue has been archived; please open a new issue for any further discussion label Jun 3, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion archived due to age This issue has been archived; please open a new issue for any further discussion core Relates to ESLint's core APIs and features feature This change adds a new feature to ESLint
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants