diff --git a/cspell.json b/cspell.json index f81041fd..a7de5116 100644 --- a/cspell.json +++ b/cspell.json @@ -107,6 +107,7 @@ "dprint", "dtslint", "Durrant", + "declutter", "dustinspecker", "Dweck", "Eghbal", @@ -126,6 +127,7 @@ "Fassina", "Ferrel", "Feucht", + "fileoverview", "fimbullinter", "flamegraph", "fontsource", @@ -280,6 +282,7 @@ "Rearchitectures", "Rebecca", "rebrand", + "reimplementation", "Reineke", "relatability", "remediations", @@ -327,6 +330,7 @@ "Spampinato", "Spongebob", "SpriteMakr", + "stackoverflow", "StaffPlus", "Startr", "Stepanek", @@ -341,10 +345,12 @@ "Telerik", "templating", "Testcafe", + "tevye", "themself", "Timișoara", "transpiles", "triaging", + "typerunner", "tsconfigs", "tsdevtest", "tseslint", diff --git a/src/assets/blog/tevye-dancing.webp b/src/assets/blog/tevye-dancing.webp new file mode 100644 index 00000000..7436fdcf Binary files /dev/null and b/src/assets/blog/tevye-dancing.webp differ diff --git a/src/content/blog/if-i-wrote-a-linter/index.mdx b/src/content/blog/if-i-wrote-a-linter/index.mdx new file mode 100644 index 00000000..0e41845b --- /dev/null +++ b/src/content/blog/if-i-wrote-a-linter/index.mdx @@ -0,0 +1,580 @@ +--- +pubDate: 2024-09-13 +description: "Why I'd write a TypeScript linter in TypeScript, build in TypeScript syntax and type awareness always, and other musings on the state of web linting in 2024." +image: + alt: "Tevye from Fiddler on the Roof TODO_ACTIVITY" + src: "~/assets/blog/sisyphus.jpg" +title: "If I Wrote a Linter" +--- + +import tevyeDancing from "~/assets/blog/tevye-dancing.webp"; +import LabeledImage from "~/components/blog/mdx/LabeledImage.astro"; +import LabeledVideo from "~/components/blog/mdx/LabeledVideo.astro"; + +> # 🛑 **THIS IS JUST A DRAFT.** +> +> I need to run it by other developers in the linter ecosystem. +> It might be horribly wrong and it might be horribly misrepresenting reality. +> Please don't take it seriously! + + + +Anybody who works with a project long enough inevitably fantasizes about rebuilding it themselves. +Part of that is natural human _"Not Built Here"_ syndrome. +Part is because any one person will have different drives and goals than the person or group of people in charge of the tool- even if they are one of them. +And part is the inevitable struggle of long lived tools simultaneously trying to preserve legacy support and keep up with industry trends in real time. + +I've been working on TypeScript linting for a while. +I started contributing to [TSLint](https://palantir.github.io/tslint) community projects in 2016 and am now a number of the [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint) and [ESLint](https://github.com/eslint/eslint) teams. +I enjoy both those projects. +This post isn't to rebuttal of either project or their direction, just my idle fantasizing about what could be. + +## Core Architecture + +This is how I would choose to build a linter in 2024. + +### TypeScript Core + +It is my sincere belief that **the standard linter for an ecosystem should be written in the standard flavor of that ecosystem's primary language**. +For the web ecosystem, that means TypeScript. + +I love the speed gains of native-speed tooling such as Biome and Oxc. +Those are fantastic projects run by excellent teams, and they serve a real use case of ultra-fast tooling. +But I think the following reasons around _developer_ and _ecosystem_ compatibility weigh me preferring JavaScript flavor for the core linter and its plugins. + +#### Developer Compatibility + +One of the best parts of modern linters is the ability for teams to write custom rules in their linter. +Lint rules are self-contained exercises in using ASTs. +The linter is an important entry point for many developers to enter the wonderful world of tooling. + +Using an alternative language for linter restricts development to developers who are familiar with both languages. +Most developers writing TypeScript, a high-level memory-managed VM language, aren't also familiar -let alone confident- with Rust, a low-level bare metal language. + +One compromise that Rust linters will likely come to is allowing third-party rules to be written easily in TypeScript. +That solves some of the issue. +But that also bifurcates the lint ecosystem: any JavaScript/TypeScript developer who isn't confident in Rust will only be able to contribute to a likely small slice of the linter's ecosystem. + +#### Ecosystem Compatibility + +Most libraries for any ecosystem are written exclusively for that ecosystem's one primary runtime. +Third-party lint rules, especially those specific to a framework, often end up using those utilities. + +Writing JavaScript/TypeScript lint rules in JavaScript/TypeScript guarantees the lint rules have access to the same set of utilities userland code uses. +Having to cross the bridge between JavaScript/TypeScript and Rust for a JavaScript/TypeScript would be an added tax to development and maintenance. + +## Type Aware, Always + +[typescript-eslint's Typed Linting](https://typescript-eslint.io/getting-started/typed-linting) is the most powerful JavaScript/TypeScript linting in common use today. +Lint rules that use type information are significantly more capable than traditional, AST-only rules. +Many popular lint rules have ended up either dependent on typed linting or having to deal with known bugs or feature gaps without typed linting.[^eslint-plugin-react-hooks-detection-typed-linting][^eslint-plugin-vitest-valid-type-type-checking] + +But, typed linting is not an easy feature for many users right now. +The divide between untyped core rules and _only some_ typed rules is painful for the ecosystem: + +- Core rules are less powerful than they could be +- Extension rules have to choose between being fast and easy to set up vs. slow and type-aware +- ESLint core isn't structured for cross-file linting, so there are known typed linting performance woes[^rfc-feat-parsing-session-objects] and blatant editor extension bugs[^vscode-eslint-cross-file-information] + +Even if you do understand typed linting, you have to go through an additional setup on top of your config's TypeScript configuration. +Setting it up without hitting typed linting's common configuration pitfalls[^tseslint-troubleshooting-typed-linting] is not a straightforward task. + +On the other hand, if rules can always assume type awareness, the linting story becomes much simpler: + +- Core rules don't need to be delegated to or duplicated by plugins to add in typed linting support +- Extension rules don't have to choose an awkward dependency on a non-core parser for type information +- The core linter architecture can be optimized for type-checked linting performance + +For this always-type-aware-world, I envision projects effectively always having [typescript-eslint's new Project Service](https://typescript-eslint.io/blog/announcing-typescript-eslint-v8#project-service) enabled. +And because the core can optimize for it, it wouldn't have performance issues from including "out-of-project" files. +All files could be linted with type information! +What a wonderful world that would be. + +### TypeScript For Type Awareness + +TypeScript is the only tool that can provide full TypeScript type information for JavaScript or TypeScript code. +Every public effort to recreate it is either abandoned[^stc-abandoned] or stalled[^typerunner-stalled]. +The closest publicly known effort right now is [Ezno](https://github.com/kaleidawave/ezno), which is a very early stage language and has a long way to go. + +TypeScript is a huge project under active development from a funded team of incredibly dedicated, experienced Microsoft employees -- as well as an active community of power users and contributors. +The TypeScript team receives the equivalent of _millions of dollars a year_ in funding from employee compensation alone. +A new version of TypeScript that adds type checking bugfixes and features releases every three months. + +Can you imagine the Herculean difficulty of any team trying to keep up with TypeScript? + +I hope for a day when there is a tool that can reasonably compete with TypeScript. +Competition is good for an ecosystem. +But it's going to be _years_ until a tool like that can develop. + +### No Type Checking Shortcuts + +It'd be great to avoid the performance cost of a full TypeScript API call. +One workaround could be to support only limited type retrievals: effectively only looking at what's visible in the AST. +I'd wager you could get somewhat far with basic AST checks in a file for many functions, and even further with a basic TypeScript parser that builds up a scope manager for each file and effectively looks up where identifiers are declared. + +Sadly, an AST-only type lookup system falls apart fairly quickly in the presence of any complex TypeScript types (e.g. conditional or mapped types). +Most larger TypeScript projects end up using complex types _somewhere_ in the stack. +Any modern ORM (e.g. [Prisma](https://github.com/prisma/prisma), [Supabase](https://github.com/supabase/supabase)) or schema validation library (e.g. [Arktype](https://github.com/arktypeio/arktype), [Zod](https://github.com/colinhacks/zod)) employs conditional types and other shenanigans. +Not being able to understand those types blocks rules from understanding any code referencing those types. +Inconsistent levels of type-awareness would be very confusing for users. + +A full type system such as TypeScript's is the only way path to fully working lint rules that perform any go-to-definition or type-dependent logic. + +### TypeScript's Assignability APIs + +One shortcut in reimplementing TypeScript could be to only implement part of it. +Typed linters haven't traditionally needed type errors, just type retrievals. +Reducing scope for a TypeScript reimplementation could make it achievable outside of the TypeScript team. + +However, typescript-eslint will soon start using TypeScript's type _assignability_ APIs too.[^tseslint-discussion-intent-to-use-checker-is-type-assignable-to] +That means any TypeScript API replacement would have to not just _retrieve_ the types of AST nodes, but also be able to perform _assignability_ checking (i.e. compare them). + +TypeScript's type retrievals and type assignability are a majority of the tricky logic within the core type checker. +At this point, the scope reduction from excluding type error reporting isn't enough to make me much less pessimistic about reimplementation efforts landing soon. + +## Built-In TypeScript Parsing + +ESLint is one of the few common modern JavaScript utilities that doesn't support TypeScript syntax out-of-the-box. +To add support, your configuration must use typescript-eslint. +Even if you bypass creating your configuration yourself by using a creation tool such as [`@eslint/create-config`](https://www.github.com/eslint/create-config), you'll still come across that complexity whenever you need to meaningfully edit that config file. + +More inconvenient long-term is the inability of core ESLint rules to understand TypeScript types or concepts. +That's led to the concept of "extension rules" in typescript-eslint[^extension-rules]: rules that replace built-in rules. +Extension rules are confusing for users and inconvenient to work with for both maintainers and users. + +I'm excited that ESLint is rethinking its TypeScript support[^rethinking-typescript-support-in-eslint]. +Hopefully, once the ESLint rewrite comes out, we'll be able to declutter userland configs and deduplicate the extension rules. + +If I wrote a linter, it would support TypeScript natively. +No additional packages or "extension" rules. +The core parser would be TypeScript's, and core rules would understand TypeScript syntax and types. + +### Probably TypeScript's AST + +ESLint's AST representation is [ESTree](https://github.com/estree/estree). +`@typescript-eslint/parser` works by parsing code using TypeScript's parser into TypeScript's AST, then recursively creating a "TSESTree" (ESTree + TypeScript nodes) structure roughly adhering to ESTree from that. +Every so often, a tooling aficionado will notice this parse-and-convert duplication and suggest removing one of the two trees to improve performance. + +First off, the cost of parsing two ASTs out of source code has never been the relevant bottleneck in any linted project I've seen. +Parse time is practically always dwarfed by type-checked linting time[^tseslint-pr-new-eslint-parser-on-swc]. +Runtime performance is not a real reason to avoid the parse-and-convert. + +Second, both of those ASTs are useful: + +- ESTree: means lint rules have no dependency on the corporate-backed TypeScript -- they are compatible with ESLint core + - One of the main downsides of TSLint being based on TypeScript's AST was having to rewrite every ESLint/ESTree-based lint rule for TSLint +- TypeScript's: must be used for AST nodes passed to TypeScript APIs, most notably for typed linting + +The main downside of this dual-tree format is the complication for linter teams and lint rule authors working with TypeScript APIs. +On the typescript-eslint team, we've had to dedicate a bit of time for every TypeScript AST change to update node conversion logic. +For lint rule authors, having to convert TSESTree nodes to their TS counterparts before passing to TypeScript APIs is an annoyance. +We've written utilities to help with common cases[^tseslint-pr-type-checker-wrapper-apis] but the conceptual overhead alone is bad enough. + +Now that typed linting is stable in typescript-eslint and Flow is explicitly not targeting competing with TypeScript for public mindshare[^flow-blog-clarity-on-direction], I'm leaning towards preferring a TypeScript AST shape for core. +We should be making the acts of writing lint rules and adding type awareness to lint rules as streamlined as possible. +Especially given my desire for built-in type awareness, I think the tradeoff of having to depend on TypeScript is worth it. + +## Embeddable by Design + +Right now, most web projects that employ both linting and type checking run them separately in CI. +Projects typically either run them in parallel across two workflows or in series within the same workflow. +That's inefficient. +You either use an extra workflow or take roughly twice as long to run. + +The root problem is that projects typically don't connect the type information generated by TypeScript to typed linting in ESLint. + +Designing an embeddable linter is not a straightforward problem. +A TypeScript plugin isn't sufficient for all projects. +What if a project lints non-TypeScript files, such as JSON or YML, that the type checker won't run on? +What if those files include embedded snippets that may run with type information, such as fenced ```ts code blocks in Markdown? + +I haven't had time to deeply investigate how to deduplicate type checking work would work well. +[typescript-eslint-language-service](https://github.com/Quramy/typescript-eslint-language-service) is a direction I'd already like to explore in working more closely with typescript-eslint. +[TSSLint](https://github.com/johnsoncodehk/tsslint) is a recent project that does a great job of integrating with tsserver. + +## User Experience + +### Formatting After Linting + +The primary reason why many ESLint users move to Biome or Oxc is that those tools perform both _formatting_ and _linting_ with a single devDependency and configuration file. +In doing so, they provide a much easier setup and maintenance story, as well as eliminate the all-too-easy common ESLint misconfigurations that lead to performance issues [^tseslint-troubleshooting-performance-stylistic][^tseslint-troubleshooting-performance-prettier][^tseslint-troubleshooting-performance-import]. + +Even better, they can automatically apply formatting after lint rule autofixes and suggestions. +No more running `--fix` and seeing a bunch of bizarrely formatting `{`s and `}`s until you hit save. + +I still believe a linter should not be used for formatting [^post-stop-using-eslint-for-formatting] [^post-the-blurry-line-between-formatting-and-style]. +But built-in automatic formatting after linting is _really nice_. +I want that. + +### Only Errors + +All web linters I've found allow configuring rules as _errors_ or _warnings_. +In theory, this is straightforward: errors are visualized with red squigglies and fail builds; warnings are visualized with yellow squigglies and don't fail builds. +Warnings are supposed to be transient indicators during migrations or when rules aren't certain about issues [^eslint-pr-docs-warn-severity], not long-lived noice. + +In practice, I think this is not useful: + +- Using the same red color and terminology for _lint_ errors and _type-checking_ errors is confusing both ideologically and practically. + I personally tell my VS Code to visualize lint errors with yellow squigglies, to not conflict with red TypeScript squigglies. +- Warnings tend to live forever in codebases, which trains developers to ignore lint reports. +- If a problem can't be determined with certainty, it either should be suppressed using an inline config comment with an explanation, or not turned into a lint rule at all! + +In other words, I think warnings are a bad fit for the migration use case. +Tools like [eslint-nibble](https://github.com/IanVS/eslint-nibble) can provide a more comprehensive experience. +Editor features such as [VS Code's `eslint.rules.customizations`](https://github.com/microsoft/vscode-eslint/pull/1164) can now change how rules are visualized. + +If I were to write a linter, I would have it so rules can only be turned off or on. +Gradual onboardings of new rules or rule options would be a separately managed feature. +Changing of visuals for specific rules or categories thereof would be separately managed features in editor extensions. + +### Strongly Typed Rules + +One of my biggest gripes with all existing linter configuration systems today is that _rule options are not type-safe_. +To recap, you specify them as properties an object, where their string key is their plugin name and rule name, and their value is their severity and any options: + +```js +{ + "my-plugin/some-rule": ["error", { + setting: "..." + }] +} +``` + +Those string keys have no associated types in config files. +Linters themselves can validate rule options, such as [ESLint's options schemas](https://eslint.org/docs/latest/extend/custom-rules#options-schemas), but those don't translate to TypeScript types. +You don't get editor intellisense while authoring; instead, you have to use `@eslint/config-inspector` or run your config to know whether you've mistyped the name of a rule or an option. + +I'd love to make a standard plugin creator function that plugin authors are encouraged -even required- to use. +It could take in a set of rules and return some kind of well-typed function. + +Vaguely, maybe it'd use a TypeScript-friendly schema validation library such as [Zod](https://zod.dev) and look something like: + +```ts +import { createRule, createPlugin } from "@joshuakgoldberg/if-i-wrote-a-linter"; +import { z } from "zod"; + +const someRule = createRule({ + options: { + option: z.string(), + }, +}); + +export const myPlugin = createPlugin({ + name: "My Plugin", + rules: [someRule], +}); +``` + +...and in usage could look something like: + +```ts +// linter.config.ts +import { myPlugin } from "@joshuakgoldberg/my-plugin"; + +export default [ + { + rules: myPlugin.rules({ + someRule: { + option: "...", + }, + }), + }, +]; +``` + +Under that kind of system, users would receive intellisense as they type plugin rules, and all those settings could be type checked. +Doing so would even coincidentally solve the issue of [plugin namespacing and rule config duplication](https://github.com/eslint/eslint/discussions/17766). +Config values would still be verified at runtime by the schema validation library. + +### Strongly Typed Plugin Settings + +An even less type-safe part of ESLint's current config system is the [shared `settings` object](https://eslint.org/docs/latest/use/configure/configuration-files#configuring-shared-settings). +You can put whatever you want in there, and any plugin may read from it. + +In theory, cross-plugin shared settings can be used for related plugins, while plugin-specific settings are by convention namespaced under their name. +In practice, I don't think I've ever seen a shared setting used across plugins. + +I think a settings system more true to how plugins use it would have plugins define their own settings and settings types. + +Vaguely, maybe it'd use a TypeScript-friendly schema validation library such as [Zod](https://zod.dev) and look something like: + +```ts +import { createPlugin } from "@joshuakgoldberg/if-i-wrote-a-linter"; +import { z } from "zod"; + +export const myPlugin = createPlugin({ + name: "My Plugin", + rules: [ + /* ... */ + ], + settings: { + setting: z.string(), + }, +}); +``` + +...and in usage could look something like: + +```ts +// linter.config.ts +import { myPlugin } from "@joshuakgoldberg/my-plugin"; + +export default [ + { + plugins: [ + myPlugin({ + setting: "...", + }), + ], + }, +]; +``` + +As with rules, allowing plugins to define their own settings types would help with the config authoring experience. +It would also newly allow shared settings to be validated by both type-checking and the core linter. +Doing so means plugins can be more confident in defining settings and changing them over time as needed. + +### Strongly Typed Configuration Files + +Let's take a step back from all these strong typings. +I think there are roughly two classifications of linter configs in common use today: + +- Direct JSON ([`biome.json`](https://biomejs.dev/linter/#configuration), [Deno](https://docs.deno.com/runtime/fundamentals/configuration/#linting), [Oxlint](https://oxc.rs/docs/guide/usage/linter#configuration-file)) +- Nuanced JS (ESLint: [`.eslintrc.js` (deprecated)](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated), [`eslint.config.js`](https://eslint.org/docs/latest/use/configure/configuration-files)) + +Direct JSON is a nice and straightforward "walled garden" that shines in small projects. +But I don't think it scales well. +The user experience of typing config files isn't great if you're not using a custom editor extension to get JSON intellisense. +More importantly, specifying plugin modules isn't conceptually straightforward. +Once the native language linters support plugins, we'lll have to specify them by some string specifier matching the plugin's module entry point. +That duplication of core JavaScript semantics feels off to me. + +Nuanced JS configurations from ESLint, on the other hand, are "just JavaScript" and so utilize native module importing for plugins, global variables, and shared configurations. +That's great for understandability and simplifying the plugin loading model. +ESLint's flat config is a huge step forward from the confusing `overrides` model of ESLint's legacy configs. +But, I think we're learning the hard way what nuances trip people up this far outside the "walled garden": + +- Because all config entries are just JavaScript objects, there's no way to lint only the user's own config entries [^eslint-change-request-report-unnecessary-config-overrides] +- Directory relativity becomes confusing when nested configs `import` from a higher-up config [^eslint-change-request-inherit-flat-configs-from-root] +- `ignores` is not very intuitive right now [^stackoverflow-parsing-error-was-not-found-by-the-project-service-but-ive-ignored-these-file] [^tseslint-discord-help-eslint-not-ignoring-js-files] +- Plugins' shared configs being able to set file matches and other settings besides rules adds complexity to configs that use multiple plugins + +I think we could solve most of that by: + +- Creating delineated functions or objects to create `ignores` blocks, shared configs, and plugins. +- Having plugins automatically register themselves when their rules are included +- Only allow plugin configs to specify rules and shared rule settings + +Doing so would clarify the intentions behind each portion of a config both to users and to tooling. +I think a good goal would be to make it so you don't have to understand anything about a config _system_ to be able to read and understand a _config file_. + +Here's a rough sketch of what the root monorepo config could like with a config system more akin to a [`tsup.config.ts`](https://tsup.egoist.dev/#using-custom-configuration) or [`vitest.config.ts`](https://vitest.dev/config), but with explicit functions for common operations: + +```ts +// if-i-wrote-a-linter.config.ts +import { linter } from "@joshuakgoldberg/if-i-wrote-a-linter"; +import { someExample } from "@joshuakgoldberg/plugin-some-example"; + +export default linter.config({ + files: [ + { + extends: [ + linter.recommended.logical(), + linter.recommended.stylistic(), + someExample.recommended({ + setting: true, + }), + ], + glob: "**/*.{js,ts}", + rules: [ + linter.rules({ + someCoreRuleA: false, + someCoreRuleB: true, + someCoreRuleC: { + someSetting: true, + }, + }), + someExample.rules({ + somePluginRule: true, + }), + ], + }, + ], + ignore: ["packages/*/dist/", "generated/"], + workspaces: ["packages/*"], +}); +``` + +Each `packages/*` workspace directory could define its own config that explicitly indicates its root directory: + +```ts +// packages/example/if-i-wrote-a-linter.config.ts +import { linter } from "@joshuakgoldberg/if-i-wrote-a-linter"; + +export default linter.config({ + files: [ + { + glob: "src/**/*.ts", + rules: [ + linter.rules({ + someCoreRuleD: false, + }), + ], + }, + ], + ignore: "lib/", + root: "../..", +}); +``` + +Those config file sketches are a little more verbose than ESLint flat config. +But they're more explicitly clear on what they do, and can be made much more type-safe using the APIs like `linter.config()` and `linter.rules()`. +The core linter could then even let users know of any redundant properties passed to a `linter.rules()`, `somePlugin.rules()`, or `somePlugin.settings`. + +That config system is just a sketch and I have no way of knowing how its tradeoffs would work in production today. +But I'd really love to see how its tradeoffs are experienced by users. + +## Userland Standardization + +### Consistent Glossary + +Most developers I've talked to do not understand or use the right terms when talking about linting. +Heck, most developers don't want to talk about the linter in the first place -- let alone understand the difference between, say, a config and a plugin. + +Many important linting terms have inconsistent usage or even definitions in the wild today. +For example, _"stylistic"_ can alternately refer to: + +- [Stylistic (Rule)](https://eslint.org/docs/latest/use/core-concepts/glossary#stylistic-rule): The category of lint rules that enforce _formatting_, naming conventions, or consistent usage of equivalent syntaxes +- [ESLint Stylistic](https://eslint.style): The plugin that ESLint's _formatting_ rules were migrated to, along with a small selection of non-formatting stylistic rules +- [typescript-eslint's stylistic shared configs](https://typescript-eslint.io/users/configs#stylistic), which enforce consistent usage of equivalent syntaxes, as well as general TypeScript best practices that don't impact program logic + +...what!? + +I work on linters and I have a hard time keeping this all straight. + +The [ESLint Glossary](https://eslint.org/docs/latest/use/core-concepts/glossary) is a very good first step towards solidifying terminology. +I would want to go one step further and have a single term and definition for all the linting concepts that authors and/or end users care about. +Doing so would provide authors with the guidance to name their configs, plugins, and rules consistently to each other and the linter core. +Users then would have an easier time navigating the plugin ecosystem and understanding how they all fit together. + +### Granular Rule Categories + +Over the last decade, the linter community learned the hard way that users need properly categorized shared configs. +If you give everyone one config that mixes _logical_ and _stylistic_ rules the way, say, `eslint-plugin-airbnb` did, users will resent the portions they disagree with and turn away from linting altogether. +Plugins that include both of those broad areas of rules now tend to provide dedicated shared configs for different areas of rules [^jsdoc-granular-flat-typescript-configs] [^typescript-eslint-configs]. + +Once a consistent glossary exists, its definitions can be used to consistently categorize configs and rules. +Heck, the linter could even provide recommended utilities for generating configs based on rule categories. +I think that'd go a long way towards further standardizing and making clear the ecosystem of plugin rules. +At the very least, it'd help encourage plugin authors to make it easier for users to include only the categories of rules they want from the plugin. + +### First Party Templates + +## Userland Help + +### Thorough Config Initializer + +### Thorough Examples + +### Thorough FAQs + +Full explanation docs for all decisions + +### Thorough Troubleshooting Guide + +## Community + +### First Party Community Repositories + +First party built in for what is the current slate of popular plugins + +## Features for Developers + +### Full Virtual File System + +[^tseslint-explore-ts-vfs] + +something something linting file permissions + +### Rich Cross File Fixes + +A linter is in many ways the best codemod platform for many kinds of migrations. +It allows you to define a granular, testable set of migration rules, and then keep them enforced over time so developers don't add regressions. +I've personally used lint rules to great effect in rolling out design system updates, enforcement of best practices, and other niceties. + +Unfortunately, the "one file at a time" model all of today's linters doesn't lend itself well to all the operations a codemod might need. +Rules may need to make fixes or suggestions to files other than the one being linted [^eslint-change-request-apply-suggestions-to-other-files]. + +If I were to write a linter, I would add in a rich system for rule fixes and suggestions: + +- The ability to indicate changes to virtual files other than the one being linted +- Other file system operations, such as renames and permissions changes + +## Implementation + +### Session Objects + +Full project context available up front including preprocessors and session object + +### Pluggable Architecture and APIs + +Pluggable api for embedding in places like typescript, TS, config and biome project + +[^eslint-change-request-apply-suggestions-to-other-files]: [eslint/eslint#17881 Change Request: Provide a way for rules to apply suggestions to other files](https://github.com/eslint/eslint/issues/17881]) + +[^eslint-change-request-inherit-flat-configs-from-root]: [eslint/eslint#18385 Change Request: Make it easier to inherit flat configs from the repo root](https://github.com/eslint/eslint/issues/18385). [eslint/rfcs#120 feat!: Look Up Config Files From Linted File](https://github.com/eslint/rfcs/pull/120) was accepted to change lookup locations, but there's still a _conceptual_ ambiguity of how one config file's relative paths should work in another config file. + +[^eslint-change-request-report-unnecessary-config-overrides]: [eslint/eslint#15476Change Request: report unnecessary config overrides](https://github.com/eslint/eslint/issues/15476) + +[^eslint-plugin-react-hooks-detection-typed-linting]: [facebook/react#25065 Bug: Eslint hooks returned by factory functions not linted](https://github.com/facebook/react/issues/25065) + +[^eslint-plugin-vitest-valid-type-type-checking]: [vitest-dev/eslint-plugin-vitest#251 valid-type: use type checking to determine test name type?](https://github.com/vitest-dev/eslint-plugin-vitest/issues/251) + +[^eslint-pr-docs-warn-severity]: [eslint/eslint#16696 docs: Add explanation of when to use 'warn' severity](https://github.com/eslint/eslint/issues/16696) + +[^extension-rules]: [typescript-eslint > Rules > Extension Rules](https://typescript-eslint.io/rules/#extension-rules) + +[^flow-blog-clarity-on-direction]: [Clarity on Flow's Direction and Open Source Engagement](https://medium.com/flow-type/clarity-on-flows-direction-and-open-source-engagement-e721a4eb4d8b) + +[^jsdoc-granular-flat-typescript-configs]: [eslint-plugin-jsdoc: Configuration > Granular Flat Configs](https://github.com/gajus/eslint-plugin-jsdoc?tab=readme-ov-file#granular-flat-configs) + +[^post-stop-using-eslint-for-formatting]: [Configuring ESLint, Prettier, and TypeScript Together > STOP USING ESLINT FOR FORMATTING](/blog/configuring-eslint-prettier-and-typescript-together/#stop-using-eslint-for-formatting) + +[^post-the-blurry-line-between-formatting-and-style]: [The Blurry Line Between Formatting and Style](/blog/the-blurry-line-between-formatting-and-style) + +[^rethinking-typescript-support-in-eslint]: [eslint/eslint#18830 Rethinking TypeScript support in ESLint](https://github.com/eslint/eslint/discussions/18830) + +[^rfc-feat-parsing-session-objects]: [eslint/rfcs#102 feat: parsing session objects](https://github.com/eslint/rfcs/pull/102) + +[^stackoverflow-parsing-error-was-not-found-by-the-project-service-but-ive-ignored-these-file]: [StackOverflow: Parsing error: was not found by the project service, but I've ignored these files](https://stackoverflow.com/a/78873727/1830407) + +[^stc-abandoned]: [dudykr/stc#1101 Project is officially abandoned](https://github.com/dudykr/stc/issues/1101) + +[^tseslint-discord-help-eslint-not-ignoring-js-files]: [Discord help thread: Eslint not ignoring .js files and throwing Definition for rule ... not found error](https://discord.com/channels/1026804805894672454/1283776448188121158) + +[^tseslint-discussion-intent-to-use-checker-is-type-assignable-to]: [typescript-eslint/typescript-eslint#7936 🔓 Intent to use: checker.isTypeAssignableTo](https://github.com/typescript-eslint/typescript-eslint/discussions/7936) + +[^tseslint-explore-ts-vfs]: [typescript-eslint/typescript-eslint#1891 Explore switching tests to @typescript/vfs](https://github.com/typescript-eslint/typescript-eslint/issues/1891) + +[^tseslint-pr-new-eslint-parser-on-swc]: [typescript-eslint/typescript-eslint#7680 feat: add a new ESLint parser built on top of SWC](https://github.com/typescript-eslint/typescript-eslint/pull/7680) + +[^tseslint-pr-type-checker-wrapper-apis]: [typescript-eslint/typescript-eslint#6404 feat(typescript-estree): add type checker wrapper APIs to ParserServicesWithTypeInformation](https://github.com/typescript-eslint/typescript-eslint/pull/6404) + +[^tseslint-troubleshooting-performance-import]: [typescript-eslint Troubleshooting & FAQs > Typed Linting > Performance > `eslint-plugin-import`](https://typescript-eslint.io/troubleshooting/typed-linting/performance#eslint-plugin-import) + +[^tseslint-troubleshooting-performance-prettier]: [typescript-eslint Troubleshooting & FAQs > Typed Linting > Performance > `eslint-plugin-prettier`](https://typescript-eslint.io/troubleshooting/typed-linting/performance#eslint-plugin-prettier) + +[^tseslint-troubleshooting-performance-stylistic]: [typescript-eslint Troubleshooting & FAQs > Typed Linting > Performance > `@stylistic/ts/indent` and other stylistic rules rules](https://typescript-eslint.io/troubleshooting/typed-linting/performance#stylistictsindent-and-other-stylistic-rules-rules) + +[^tseslint-troubleshooting-typed-linting]: [typescript-eslint > Troubleshooting & FAQs > Typed Linting](https://typescript-eslint.io/troubleshooting/typed-linting) + +[^typerunner-stalled]: [marcj/TypeRunner Is there still a chance of kickstarting the project?](https://github.com/marcj/TypeRunner/issues/14) + +[^typescript-eslint-configs]: [typescript-eslint: Shared Configs > Recommended Configurations](https://typescript-eslint.io/users/configs#recommended-configurations) + +[^vscode-eslint-cross-file-information]: [microsoft/vscode-eslint#1774 ESLint does not re-compute cross-file information on file changes](https://github.com/microsoft/vscode-eslint/issues/1774)