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

New ESLint config #9748

Closed
wants to merge 16 commits into from
Closed

Conversation

tylerbutler
Copy link
Member

@tylerbutler tylerbutler commented Apr 5, 2022

Part of #9501.


Update 2022-04-20

After discussing more with @ChumpChief, I am preparing additional PRs to apply fixes for some of the new rules before merging this and publishing a prerelease. This will ease the integration of the new config.


Update 2022-04-12

The latest PR contains the following changes based on feedback in the review:

Changed to Error

  • @typescript-eslint/func-call-spacing
  • @typescript-eslint/keyword-spacing
  • @typescript-eslint/member-delimiter-style
  • @typescript-eslint/object-curly-spacing
  • @typescript-eslint/type-annotation-spacing
  • new-parens
  • semi-spacing
  • valid-typeof
Changed to Warn
  • @typescript-eslint/no-empty-function
  • @typescript-eslint/no-unsafe-assignment
  • @typescript-eslint/no-unsafe-call
  • @typescript-eslint/no-unsafe-member-access
  • @typescript-eslint/no-unused-expressions
  • @typescript-eslint/require-await
  • @typescript-eslint/restrict-template-expressions
  • unicorn/consistent-function-scoping
  • unicorn/expiring-todo-comments
  • unicorn/prefer-top-level-await

Disabled

  • @typescript-eslint/no-parameter-properties
  • @typescript-eslint/sort-type-union-intersection-members

Other changes

  • @typescript-eslint/explicit-function-return-type -- set allowConciseArrowFunctionExpressionsStartingWithVoid to true
  • import/no-unresolved -- set caseSensitiveStrict to true

This PR updates our shared lint config by consolidating the three separate configs into a single config, shared-config.js. This config is much stricter than the current minimal config. Every rule that we override is listed alphabetically in the config, along with a brief summary of the intention behind the rule.

A plea

Choosing default lint rules is inherently an opinionated exercise. In the absence of feedback, my opinions regarding lint defaults will become the "law of the land," and that should scare you as much as it does me. 😄 Please let your own opinions be heard!

All that said, after this is merged, I'll follow up with another PR to apply the config across the repo. When that happens, you'll have another opportunity to weigh in on the rules. But it's easier to make changes now.

How to review

To ease this review, I suggest reviewing default.json, which is the diff of the final applied config, taking into account all our plugins, parent configs, etc. That is easier than looking at the config file itself since that file is all new.

Q and A

How did you decide what should be a warning vs. an error?

Practically speaking, the difference between an ESLint error and a warning is that errors fail the build, while warnings don't. The default is that everything is an error, and I generally feel that rules that aren't enforced by build breaks aren't useful. However, there are two situations where I feel a warning is better.

First, if the rule is likely to have a lot of offenders in our code, or if fixing the issues is complex and warrants careful code review, then warning is appropriate because it lets us adopt the new config without temporarily disabling rules and then forgetting to ever turn them off again. Warnings have a tendency to get fixed because eventually someone gets annoyed, 😄 Large-scale lint changes are hard to review because there are often so many changes, so warnings enable us to have more targeted changes that are better reviewed. An example of this kind of rule:

        // Parameter properties can be confusing to those new to TypeScript as they are less explicit than other
        // ways of declaring and initializing class members.
        "@typescript-eslint/no-parameter-properties": "warn",

Second, if the rule is more about formatting or style than code correctness, and I was worried about it being too strict or too controversial, I used warnings. Examples of these kinds of rules:

        // Catches a common coding mistake where "resolve" and "reject" are confused.
        "promise/param-names": "warn",

        // Prefer type-only imports where possible,
        "@typescript-eslint/consistent-type-imports": [
            "warn",
            {
                prefer: "type-imports"
            }
        ],

        // Enforces that members of a type union/intersection are sorted alphabetically.
        "@typescript-eslint/sort-type-union-intersection-members": "warn",

These are all opinionated decisions, and I may have been more arbitrary than the above describes. Let me know where you disagree.

How did you decide what formatting rules to enable?

I prefer to focus linting on finding code issues, either in logic or style. Formatting is a different task, better left to dedicated formatting tools like tsfmt, which is what VS Code uses internally when you format a file. Many lint rules regarding formatting require a lot of connfiguration to work as we want, but our "style guide" is pretty simple -- whatever VS Code produces is the "correct" formatting. Configuring lint rules to do what VS Code "just does" makes me sad, which is why we have a tsfmt task for most projects. It isn't running as part of the build (yet), so some formatting-related lint rules are enabled, but they should be the exception.

How do we apply this config to projects with lots of offenses?

You can disable rules as needed to unblock yourself. You should always prefer line-level disables. If there is a contiguous block of the same offense, then using an disable/enable block comment is acceptable. Project-level overrides (these live in .eslintrc.js) should not be used. They're too easy to forget to clean up. The exception to this is test projects, which might have legitimate reasons to adjust rules at the project level.

@github-actions github-actions bot added area: build Build related issues base: main PRs targeted against main branch labels Apr 5, 2022
@tylerbutler tylerbutler mentioned this pull request Apr 5, 2022
4 tasks
@github-actions github-actions bot removed the area: build Build related issues label Apr 6, 2022
@tylerbutler tylerbutler marked this pull request as ready for review April 7, 2022 01:05
@tylerbutler tylerbutler requested review from msfluid-bot and a team as code owners April 7, 2022 01:05
Copy link
Contributor

@ChumpChief ChumpChief left a comment

Choose a reason for hiding this comment

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

I'm maybe halfway through default.json, submitting some initial feedback. Couple broader feedback items:

  • This PR is making two main changes (1) structure and (2) actual rule changes. It would be easier to review and understand the changes if this were split into two PRs, first just restructuring but changing no policy, and second being the opinionated policy changes.
  • I want to be super pedantic about formatting, I've spent too much time giving formatting feedback in PRs when the rules have not been enabled, and I'm sure the recipients of that feedback would have loved for eslint:fix to work :-P. If/when tsfmt eventually becomes part of the build pipeline then maybe we move the responsibility, but in the shorter term we should keep these as build failures so our code doesn't decay.

],
"@typescript-eslint/dot-notation": [
"error"
],
"@typescript-eslint/explicit-function-return-type": [
"@typescript-eslint/eol-last": [
Copy link
Contributor

Choose a reason for hiding this comment

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

I like eols to be enforced, plus can easily autofix -- would prefer error here.

Copy link
Member Author

Choose a reason for hiding this comment

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

EOL enforcement is handled by the editorconfig plugin.

],
"@typescript-eslint/indent": [
"off"
],
"@typescript-eslint/interface-name-prefix": [
Copy link
Contributor

Choose a reason for hiding this comment

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

interface-name-prefix is probably a good rule to have turned on (I thought it had been on?)

Copy link
Member Author

Choose a reason for hiding this comment

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

This has never been on AFAIK. We don't seem to follow a naming standard today for interfaces. Are you suggesting we adopt one?

@@ -162,13 +180,22 @@
"off"
],
"@typescript-eslint/no-invalid-this": [
"off"
"error"
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice :)

Copy link
Contributor

@ChumpChief ChumpChief left a comment

Choose a reason for hiding this comment

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

Reviewed everything except for the unicorn rules now

],
"no-undef": [
"off"
],
"no-undef-init": [
"error"
],
"no-underscore-dangle": [
"off"
Copy link
Contributor

Choose a reason for hiding this comment

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

We use dangling underscores quite a bit, want to make sure this is still permitted.

Copy link
Member Author

Choose a reason for hiding this comment

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

Where you see a rule that was previously off that is now removed completely, it means that the rule was always off by default, and the previous config explicitly turned it off unnecessarily.

I see how confusing this is. ☹️

@@ -734,17 +757,14 @@
"error"
],
"no-void": [
"off"
"error"
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should continue to permit void probably.

Copy link
Member Author

Choose a reason for hiding this comment

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

I feel intimidated by the docs: https://eslint.org/docs/rules/no-void

I think that anyone who feels confident enough to use void can disable the rule with a comment. It doesn't seem like something most TS devs would need.

@@ -2,6 +2,7 @@
"env": {
"browser": true,
"es6": true,
"jest/globals": true,
"node": true
Copy link
Contributor

@heliocliu heliocliu Apr 7, 2022

Choose a reason for hiding this comment

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

how does this work with isomorphic packages trying to directly use node types? or is that fine since the work to move to webpack 5 is underway?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. Ideally we'd have individual configs for every package with the minimal env for each... but I also think that we'll need other mechanisms to help enforce browser-compatible packages, so maybe this is ok.

"error"
],
"unicorn/no-nested-ternary": [
"error"
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not exactly sure how unicorn/no-nested-ternary handles multiline ternary nesting, but seems OK for single line based on the documentation. I guess we'll see how it plays out in the repo.

Copy link
Member Author

Choose a reason for hiding this comment

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

It seems to do pretty well on multi line too. I find it aids readability, but YMMV.

"error"
],
"unicorn/no-new-array": [
"error"
Copy link
Contributor

Choose a reason for hiding this comment

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

Love this no-new-array rule, just heads up it might hit issues in some of our packages

"unicorn/no-new-buffer": [
"error"
],
"unicorn/no-null": [
"error"
Copy link
Contributor

Choose a reason for hiding this comment

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

I like disallowing null, but I think we have multiple rules for this? Wondering if we need all of them long-term.

Copy link
Member Author

Choose a reason for hiding this comment

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

They pick up different cases, and I don't know why. My working theory is that the rushstack one is too conservative (doesn't detect cases I would expect it to) but the unicorn one doesn't know TypeScript. Let's see how they play out.

"error"
],
"unicorn/prevent-abbreviations": [
"off"
Copy link
Contributor

Choose a reason for hiding this comment

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

prevent-abbreviations seems interesting -- agree with disabling to start but might be nice to experiment with enabling.

Copy link
Member Author

@tylerbutler tylerbutler Apr 8, 2022

Choose a reason for hiding this comment

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

You refer to experimenting... I'm finding it difficult to experiment with our rules. I think if we want to experiment we should do it on a project by project basis, enabling them in the project overrides. But I never know which projects are interesting to experiment with what rules. 😄

I think this one will need a lot of manual tweaking to cross the useful/annoying threshold.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure, I was thinking more about just private experimentation to understand the rule better and see what works well before trying to enable it anywhere.

@tylerbutler
Copy link
Member Author

@ChumpChief @heliocliu I just pushed an update that incorporates a lot of your feedback. Details are in the PR description.

@tylerbutler tylerbutler added this to the April 2022 milestone Apr 12, 2022
],
"@typescript-eslint/no-unsafe-return": [
"error"
],
"@typescript-eslint/no-unused-expressions": [
"error"
"warn"
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should keep errors on no-unused-expressions

Copy link
Member Author

Choose a reason for hiding this comment

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

I can't remember why I changed this, so I'll change it back, remember why I lowered it 😆 then RAGING DEBATE can ensue.

@tylerbutler
Copy link
Member Author

I converted this to a draft since I don't intend to merge as-is.

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

6 participants