7.2.0 • Published 6 days ago

eslint-config-current-thing v7.2.0

Weekly downloads
-
License
MIT
Repository
github
Last release
6 days ago

Lint The Current Thing

An extremely unopinionated config based on an automatically generated popularity contest between the most downloaded ESLint configs/plugins/parsers/rules.

Now with automated conflict discovery and mitigation!

"Finally, a config everyone hates!"


Linting is too opinionated. In fact, it can become political. Eslint Config Current Thing generates an ESLint config objectively based on whatever the current thing is at the time of build w/r/t broad ecosystem-wide definitions and opinion around JavaScript linting. Now, market competition can settle the debate.

Presently, lint the current thing combines these configs (and their plugins, submodules, etc.):

AirBnbAirBnb BaseAirBnb-TypescriptAva
CSS ModulesChai FriendlyCommentsCompat
Create React AppCypressES-XESLint
Emotion CSSFunctionalGoogleGraphQL
ImportInternationalization (i18n)JSDocJSONC
JSX Accessibility (JSX A11y)JasmineJestJest Dom
Jest FormattingMDXMarkdownMicrosoft SDL
MochaNextJSNo Only TestsNo Unsanitized
No Unused ImportsNo Use Extend NativeNode.jsPerfectionist
PlaywrightPrefer ArrowPrettierPrettier Plugin PackageJSON
PromisesReactReact HooksReact Native (Independent Plugin/Config)
React Native ConfigReact Native PluginReact PerformanceReact Prefer Function Component
React RefreshRegular ExpressionsRxJSSecurity
ShopifySimple Import SortSonarJSSort Class Members
StandardStandard JSXStandard ReactStandard TS
StorybookStylisticTSDocTailwind CSS
Testing LibraryTypeScriptTypescript Sort KeysUnicorn
XOYMLYou Don't Need Lodash

The winning (and thus most readily supported) high-level architecture is:

TypesFrontEndTestingStyle
TypescriptReact / React NativeJestPrettier

Technical explainer

This is a meta-config of many configs. Each config/plugin above is added to ESLint's flat config in the order of its NPM download popularity. After the rules of flat-configs are applied, each rule is turned on or off and set with the settings that are present in the config that is the most popular which has an opinion about that rule. Rule are applied only to their appropriate file types. The result is an objectively set config.

Why?

Linting is usually very basic or incrementally added as teams have no reason or time to sit down like AirBnB or Vercel and justify and debate every last ESLint rule, but they do all sit down and figure something out. Internally, the linting is almost always sub-par, and externally, over time, hundreds of eslint plugins and configurations have emerged with various levels of popularity and maintenance, but they usually only address the small set of things that the team who developed them needed to address and invariably miss something. This is complicated by the fact that NPM does not make searching for ESLint configs/plugins easy. Then, the JS ecosystem moves to the next framework, the config goes out of maintenance and rules become irrelevant/bad and everybody has to solve the same problems again (for instance, Prettier Plugins are now solving many of the same things the ESLint plugins have been solving for years). Lint The Current Thing essentially grabs as many rules as possible, indiscriminately, provided they meet minimum thresholds of quality, and compares each rule on a popularity contest, so whatever the Current Thing is (provided you update your packages) that will fundamentally be tied to ecosystem-wide best practices as supported by the most people--this is to say Lint The Current Thing produces code that is accessible to the widest possible set of developers and stays in-step with the ecosystem. You can read more in the "Why is this a good idea?" section.

Unsurprisingly, it is incredibly strict with popularity-based opinions on:

  • 1261 TSX rules
  • 1246 Testing rules
  • 1208 JSX rules
  • 1110 Typescript rules
  • 1066 Javascript rules
  • 33 JSON rules
  • 24 YML rules
  • 5 MD rules

Surprisingly, it works.


Install

This config is premised on the new ESLint Flat Config, so it needs a eslint.config.js. Obviously, it requires eslint and typescript as well.

npm i -D eslint-config-current-thing

Then in your eslint.config.js

import currentThing from "eslint-config-current-thing";

const config = [...currentThing()];

export default config;

You'll also probably want something like this in your package.json (edit accordingly):

{
  "type": "module",
  "prettier": {},
  "remarkConfig": {
    "plugins": ["remark-preset-lint-recommended"]
  },
  "engines": {
    "node": ">=20.0.0"
  },
  "browserslist": [
    "defaults and supports es6-module",
    "maintained node versions"
  ]
}

And if you are using VS Code, you should get the extension for ESLint dbaeumer.vscode-eslint where you will need to add this to your settings.json:

{
  "eslint.experimental.useFlatConfig": true
}

And you'll probably want to restart your ESLint server, maybe more than once. Command Pallet > Restart ESLint Server

Oh god, what have we done.


Usage

import currentThing from "eslint-config-current-thing";

const config = [
  // Rules here will be overridden by the current thing, if they collide
  ...currentThing(),
  // Rules here will override the current thing, if they collide
];

export default config;

Options

Presently, there are three options: disable, override, and threshold.

For disable and override a few configs will have internal configs or overrides, for instance shopify has a config for esnext, react, and others. These can be targeted via adding a secondary namespace, ie, @shopify/eslint-plugin/jest (all lowercase, but look up in the source def for exact targets).

disable

To disable the entirety of a particular config, that config must be named via its package name, like so:

import currentThing from "eslint-config-current-thing";

const config = [...currentThing({ disable: ["eslint-config-import"] })];

export default config;

E.g. if you disable every config, in order from the most downloaded down, until you hit functional, then functional will be your leading config and your project will then be linted with the recommended functional style.

WARNING: disable may have unintended consequences where the linted code might get into an irreconcilable state, as various configs can toggle rules. We attempt to guard against this with automated conflict resolution but things can still sneak by.

override

In the event that you need to override or change a rule in order with respect to all other rules:

import currentThing from "eslint-config-current-thing";

const config = [
  ...currentThing({
    override: { "eslint-config-airbnb": { "no-restricted-syntax": 2 } },
  }),
];

export default config;

override will not clear existing rules but is provided in the unlikely case that a given rule needs more fine-tuning,

disable takes precedence over override.

threshold

threshold can be passed to the options object to set the popularity threshold for packages. Defaulting to 400,000 downloads per month.

import currentThing from "eslint-config-current-thing";

const config = [
  ...currentThing({
    threshold: 1_000_000,
  }),
];

export default config;

This would only include packages that exceed 1,000,000 downloads per month, and thus would constitute a more lenient linting config as less rules will be activated.

Observability

There are a crazy amount of rules in this config, and they are completely overwhelming. Try easing into them by setting a high threshold, and understanding what you are getting into:

Overwrite Preferences

What is more, you can Enable, Disable, Add or Alter any rule you come across, like so:

import currentThing from "eslint-config-current-thing";

const config = [
  ...currentThing(),
  {
    // Enable/Disable/Alter any set of specific rules
    rules: {
      "functional/no-this-expressions": 2,
    },
  },
];

export default config;

Config Methodology - "The Rules"

1. Additions

For a CONFIG to be added, it needs to have around 400,000 monthly downloads (or 4 consecutive weeks above 100,000 weekly downloads). This is to prevent spam, bloat, needless PRs, and arguments about minutia. But it's also variable as configs change popularity. Yes, it's arbitrary (and it can be spoofed/is naively calculated), but it seems to be a good heuristic for the cutoff between generally used configs and niche configs.

For a PLUGIN to be added, it needs to be included in an added config, or meet similar config quality standards. This is to ensure that configs work as intended.

In short, it should be easy for a plugin to get added and stay added, but hard for a config.

To determine if your preferred config is eligible, visit the NPM page and check the weekly download count, or scroll to the bottom to see a list of packages presently under consideration.

You can always extend/disable this config to meet your own needs. However, if you think a config should be included, and it passes the monthly count test (and is not garbage/has stars/contributors, etc.), open a PR or Issue, and it will be added. When adding a rule, the "recommended" version of that rule will be unashamedly smashed used with as little alterations as possible.

Outside of getting the various pieces to play nice together, Lint the Current Thing does all it can to not define any new individual rules or to have any opinion. See Conflicts below.

[Holding back intensifies]

2. Conflicts

Conflicting rules favor the preferences of more popular configs via the rules of ESLint flat-config merging. Adding a bunch of configs will introduce conflicts. But conflicts are now automatically resolved on a config and rule basis, where conflicting rules are disabled (set to 0) at the conflict config order. This way, they are ideologically in line with the popularity contest. If two conflicting configs switch order, the rules will switch as well. To see the full list of generated conflicts check out incompatibilities. If conflicts can not be resolved this way (i.e. three or more differently named rules which cyclically enforce opposing behavior) then the rules are manually added to the config as known conflicts. It would add insane complexity to fix this automatically to find the less than 5 cases where this actually happens.

3. Sub-Categories

Whatever the leading config is, for any particular thing, that is the config which gets to decide other config's sub-preferences, winner take all, for that particular thing. ESLint is about linting JavaScript, and many rules and opinions exist around what constitutes "appropriate" use of JS. Many of those opinions not only deal with JavaScript but also deal with auxiliary or tangential systems. Of those tangential systems, where there are conflicts which can not be resolved via merging, the popular winner takes all. For instance, despite being well-supported, very few people use Flow in comparison to TypeScript for typing. Thus, Typescript gets to dominate typing framework and other packages are expected to use their TypeScript-native rules, while Flow is discouraged / unsupported. If Flow suddenly became more popular than TypeScript, this would change. In most cases, opposing frameworks can both be included, like varying style guides (e.g. prettier vs standard vs functional) as neither present fundamentally breaking changes to the other.

4. Linting Lag

In the case of the linting ecosystem strongly favoring against something but that thing being an integral part of new, cutting-edge development, new cutting-edge tech will take precedent by disabling rules which oppose it. For example, the semi-recent linting sentiment of "we will never use loops again" being encumbered by the usefulness (and perhaps need) of for await ... of loops and AirBnB (a very popular config) still recommending against.

5. Abandoned Configs

Considering that linting is such a minor part of coding, often times popular configs will be abandoned and then forked, and the community will continue to use both config versions, and most will have no idea of the split. In such cases (e.g. Comments and Node) the two versions will be grouped together such that their download counts are combined and dependencies are flushed, but only the maintained rules will be added or supported.

If a repository is abandoned due to lack of community interest and has no fork--because no one wants/needs it. It will be removed.


Why is this a bad idea?

  1. "Everything popular is wrong" - Oscar Wilde
  2. There is a KXCD for this:

    KXCD

  3. Somebody will game the system to get a billion weekly downloads.

  4. Because NPM naively calculates the download count, once a config makes it into the current-thing it will never and can never leave (Until we up the download count minimum or check in nodes modules, etc.)
  5. We are moments away from AI linting code way better than ESLint. Mere moments!

Why is this a good idea?

That said, there are some very compelling reasons to use this config above other configs:

  1. Please watch "Code is for Humans" by Kyle Simpson, author of "You Don't know JS". Tying the way that code is written to the way that the most people read it helps ensure that it is accessible for the most humans. A project which adheres to whatever the current thing is can be objectively certain that the most developers in the entire ecosystem will be somewhat comfortable approaching it. This makes debugging, contributing, using, understanding, and sharing code easier. No one will be 100% happy with the linting results (what else is new?), but this at least provides some basis to ease developer ramp up time with any particular project or JS code base. Linting React code as React Native code sounds dumb at first, but its really no different from using a superset of React, which is the exact same pattern as Typescript and JS, by putting tighter limits on the code, it makes it more accessible to a wide range of participants.

  2. It adds a "higher-authority" or "single source of truth" to config discussions--which is not just someone else's opinion, but aggregate opinion at large, as it spreads across use cases. And as such, it better arbitrates disagreement. Many rules offer links to justification, and it's hard to argue that more learning about JS is bad.

  3. Further, it encourages developers to stop making yet more highly opinionated individualized ESLint configs, and instead coalesce around the community configs they like. We love Prettier's stance embracing opinionated configurations, but it does not do well as an example for the ecosystem--if everyone embraces being highly opinionated, everyone loses. If someone truly is willing to die on some linting hill, and wishes that everyone would conform to their demands, they should seek to add those rules to an existing config which this config extends, simply because this config exists, and someone else, somewhere, might also be already using it, and love the new idea. Sure, you could do that with any other config, but what other config is as encompassing and as unopinionated as this? If you go it alone, you may not have any allies, may never get the message out, or worse still, may never have the rude awakening that it's just not a good idea.

  4. Custom or extremely granular rules which add value to qualifying configs and are not defined in higher-ranking configs still make it into the current thing final config, no matter how obscure. This alters strategies for convincing people about the merits of your new rule. It puts a bid on new rule creation in existing/popular repos, making existing rule sets more robust. In addition to stopping config proliferation, it actually helps build better rules. And more so, it encourages developers behind more popular configs to remain competitive.

  5. This config will shift as the opinions in JS Linting shift, just as all human languages shift. All of those shifts in opinion, both outside and in the config will happen on the margin. Non-democratic configs do not listen to this nuance, they take a stance and see if anyone follows. To demonstrate how current thing manages this, clone this repo and open up src/config.js. Then, alter the order of the configs, and you will see how the rules marginally change. It's not just that software is eating the world, it's actually JavaScript software, and we must be cognizant of how everyone is using it in wildly disparate ways as new styles, processes, and technologies rise and old ones fall.

  6. Unlike many other configs, this config completely abstracts away the need to add any other eslint packages, configs, plugins, and management. It is a pure barbell--absolutely simple in its application, and yet insanely complex and heavy-weight in its implementation.

  7. This config can be, and should be, used as an auditing tool. It exposes an eye watering amount of linting problems. I'm not crying. You're crying.

What is everyone saying?


Great resources that this project draws heavily upon

Architecture

We're expecting to see an Architecture section, huh?

graph LR
    gen[generate] --> fix[generate:fixables]
    fix --> node_fix[node: get-fixables.ts] ==> lint_fix[lint] --> x[Fixable rules now known]

    fix --> confl[generate:conflicts]
    confl --> node_confl[node: generate-conflicts.ts] --> lint_confl[lint] --> y[Individual configs now generated]

    confl --> incomp[generate:incompatibles]
    incomp --> node_incomp[node: get-incompatibles.ts] --> lint_incomp[lint] --> z[Individual conflicting rules now known]

    incomp --> confs[generate:configs]
    confs --> gen_conf[generate:config] --> node_gen_conf[node: generate-config.ts] --> node_npm_q[NPM queried for counts] --> lint_conf[lint] --> a[Final configuration now generated]
    confs --> gen_curr[generate:currents] --> curr_all[all currents]
    curr_all --> curr_ts[current:ts]
    curr_all --> curr_js[current:js]
    curr_all --> curr_test[current:test]
    curr_all --> curr_tsx[current:tsx]
    curr_all --> curr_jsx[current:jsx]
    curr_all --> curr_jsx[current:md]
    curr_all --> curr_jsx[current:yml]
    curr_all --> curr_jsx[current:json]
    curr_all --> lint_curr[lint] --> b[All JSON configs now generated]

    confs --> readme[generate:readme]
    readme --> node_readme[node: generate-readme.ts] --> node_npm_s[NPM searched for all configs] --> c[Current state of all config rankings now known]

We will not be adding these configs

In an effort to find as many possible ESLint appropriate packages, plugins, and configs, we have cast a massive net to comb through NPM packages so you don't have to. The amount of acceptable packages is bottomless, but taken from the most downloaded package that meets our widest search terms ("config" "plugin" or "eslint") the count of rejected packages should be a heuristic for the depth at which the current package runner-up is at.

The amount of packages reviewed and rejected 498.

See rejected for the full list with notes. But it's always open for debate!

We are considering adding these configs

The following section is generated according to spec.

Generated on 4/22/2024, downloads for the previous 28 days.

@babel/eslint-plugin@emotion/eslint-plugin@eslint-community/eslint-plugin-eslint-comments@eslint/eslintrc@eslint/js@graphql-eslint/eslint-plugin@microsoft/eslint-plugin-sdl@next/eslint-plugin-next@react-native-community/eslint-config@react-native/eslint-plugin@shopify/eslint-plugin@stylistic/eslint-pluginconfusing-browser-globalseslinteslint-config-airbnbeslint-config-airbnb-baseeslint-config-airbnb-typescripteslint-config-googleeslint-config-loveeslint-config-prettiereslint-config-standardeslint-config-standard-jsxeslint-config-standard-reacteslint-config-xoeslint-define-configeslint-import-resolver-typescripteslint-mdxeslint-plugin-avaeslint-plugin-chai-friendlyeslint-plugin-check-fileeslint-plugin-compateslint-plugin-css-moduleseslint-plugin-cypresseslint-plugin-deprecationeslint-plugin-eseslint-plugin-es-xeslint-plugin-eslint-commentseslint-plugin-flowtypeeslint-plugin-ft-floweslint-plugin-functionaleslint-plugin-headereslint-plugin-htmleslint-plugin-i18nexteslint-plugin-importeslint-plugin-jasmineeslint-plugin-jesteslint-plugin-jest-domeslint-plugin-jest-formattingeslint-plugin-jsdoceslint-plugin-jsonceslint-plugin-jsx-a11yeslint-plugin-markdowneslint-plugin-mdxeslint-plugin-mochaeslint-plugin-neslint-plugin-no-only-testseslint-plugin-no-unsanitizedeslint-plugin-no-use-extend-nativeeslint-plugin-nodeeslint-plugin-perfectionisteslint-plugin-playwrighteslint-plugin-prefer-arroweslint-plugin-prettiereslint-plugin-promiseeslint-plugin-reacteslint-plugin-react-hookseslint-plugin-react-nativeeslint-plugin-react-perfeslint-plugin-react-prefer-function-componenteslint-plugin-react-refresheslint-plugin-regexpeslint-plugin-rxjseslint-plugin-securityeslint-plugin-simple-import-sorteslint-plugin-sonarjseslint-plugin-sort-class-memberseslint-plugin-storybookeslint-plugin-tailwindcsseslint-plugin-testing-libraryeslint-plugin-tsdoceslint-plugin-typescript-sort-keyseslint-plugin-unicorneslint-plugin-unused-importseslint-plugin-ymleslint-plugin-you-dont-need-lodash-underscoreespreeglobalsjsonc-eslint-parserprettierprettier-plugin-packagejsonremark-lintremark-preset-lint-recommendedtypescript-eslintyaml-eslint-parser
7.2.0

6 days ago

7.1.0

13 days ago

7.0.0

18 days ago

6.0.1

19 days ago

6.0.0

19 days ago

6.0.2

19 days ago

4.3.1

1 month ago

4.3.0

1 month ago

4.2.1

1 month ago

4.1.0

1 month ago

4.0.1

1 month ago

4.0.2

1 month ago

3.0.1

1 month ago

3.0.0

1 month ago

2.4.0

2 months ago

2.0.1

2 months ago

2.0.0

2 months ago

1.3.0

9 months ago

1.2.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago