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
Plugin initialization API #14139
Comments
I'll kick off the discussion by relating an idea @nzakas had in the TSC meeting discussion that led to this issue. This is only one possible approach, so please share any other ideas you have. Right now, What if, after parsing source files and before running any rules, ESLint provided However, this could increase memory usage on larger projects by having full ASTs for all files in memory simultaneously. Right now, I assume that This approach might constrain parallel linting. ESLint's current one-file-at-a-time model maps well to parallel workers that could do their own parsing and traversal. This approach would split that into a parse phase followed by a lint phase and require us to pass ASTs around. |
Only some of eslint-plugin-import's rules need its ExportMap, so ideally, each rule should be able to "register" a hook, and only when the rule is going to be invoked (ie, not disabled by config) would the hook need to run. It does, however, eagerly parse every file in the project, regardless of what's being currently linted. I'm pretty sure we discard AST nodes as soon as we've derived our data structure from them, so I don't anticipate memory being a huge problem. If it were, we could always serialize it to the filesystem (which would also allow us to share it across threads). |
Some background/context on Our parser is built on top of TypeScript. This means that we don't do any parsing ourselves. Instead we invoke TypeScript and have it parse any given file, and then we transform TS's AST representation to match the ts-estree representation. In "non-type-aware" mode we work 100% the same as any other ESLint parser; meaning we cache nothing and just produce an AST. "Type-aware" mode is where we break from other parsers. Users provide a reference to one or more of their
When a type-aware runs it works as per a normal ESLint rule until it specifically needs type information and then it simply gets the TS AST node via the node map, and then asks TS for type information for the TS AST node. For As above; in type-aware mode all of the work is front-loaded during the parse step. ESLint currently does not provide any separate steps, so the work is specifically performed during the first file that gets parsed. In regards to an initialisation API, ideally we'd want an API that gets called before parsing which does something along the lines of "broadcast which files are being linted in this run and the config they are being linted with". This will allow us to do a few things:
(1) and (2) would allow us to understand the workload ahead of time, and plan accordingly. One other suggestion would be for the ability for a parser to influence the order in which ESLint processes files. One final suggestion would be for ESLint to differentiate between the "persistent" usecase (like IDEs) and the "one and done" usecase (like CLI runs). In a "one and done" run we don't really need to care about what happens on the filesystem - we can pretty much assume that the state at the start of the lint run is the state at the end of the lint run. Right now there's no mechanism for this so we have to treat all runs as if they're potentially "persistent" runs; meaning we can waste a lot of time on CLI runs. |
@bradzacher there's a lot of great info in your comment, thanks for sharing that. To keep this thread focused, would you mind opening separate issues about "allowing a parser to influence the order of files" and "persistent vs one-and-done modes"? Those are both interesting to me, and I'd like to explore those more, but I don't feel they are related enough to the topic of this issue to risk derailing the overall conversation. |
Not all rules need these hooks, so hooks should be registered based on enabled rules, got it.
To clarify,
For High-level requirements: Before parsing or linting any files, ESLint's main thread (today, the only thread) needs to enumerate files and calculate their configs. If any enabled rules register hooks, ESLint should then call them with the list of to-be-linted files and their configs.
After hooks have been called, the main thread or worker threads parse and lint each file. What about hook results? What should happen with hooks' results, particularly once we have parallel linting? Are those are being shared between rules as in-memory singletons today? Would
|
yes, exactly.
No requirements really; the easiest thing would be to be able to share a fully populated instance of a class (https://github.com/benmosher/eslint-plugin-import/blob/master/src/ExportMap.js#L27). If the requirement were to provide something serializable, I suspect we could, for example, use a Symbol provided by eslint to provide an instance method that produced whatever serialization format eslint required, and then we could deserialize it ourselves later on demand. |
The parser provides parserServices which includes the "program" object created by TS. No part of the type-aware linting is async because ESLint does not (/did not) support asynchrony.
TS is designed so that any type information is accessed within the same process. I think it would require some serious bespoke engineering to make it work as a singleton type server. TS has a language server, but the design there is for an IDE to ask specific things: "this file changed, tell me the problems", "what is the hover information to display at this specific range?", "what refactoring operations can be done at this specific location?". This is why I mentioned "the ability for a parser to influence the order in which ESLint processes files" (I'll see if I can write up an issue tomorrow). Ideally you would group files by tsconfig (project), and have one thread per project. |
I was looking to create a similar plugin to
I found that finding all files seems like a somewhat easier problem ... But the performance hit of parsing the entire project is quite large. The naive thought I had: This way I can atleast write my plugin without parsing the entire project 3 times in my application |
Sorry, we don’t add temporary APIs to ESLint. A better approach for you might be to create a custom parser that wraps Espree and memoizes the parse() function. |
I wonder if, for modules like typescript-eslint and eslint-plugin-import that need state persisted across files, it'd be a good idea to allow plugins to register something like a worker or just a module to run on the main thread, and communicate to each file worker via a provided message channel or RPC mechanism. This would provide some independence as well from however ESLint decides to implement parallel linting. |
The problem you want to solve.
ESLint assumes that each rule and source file can be processed independently.
typescript-eslint
(ref eslint/rfcs#42 (comment)) andeslint-plugin-import
(ref eslint/rfcs#42 (comment)) need to do upfront initialization work beyond the scope of a single rule and source file, specifically loading type information and tracing a module graph. Lacking a first-class API, they have inserted these initialization steps into the regular rule linting flow.If we were to ship parallel linting without supporting this use case, the duplicated initialization could make parallel linting slower than single-threaded linting with these plugins. The large number of ESLint users who also use one of these plugins would not benefit from parallel linting.
Your take on the correct solution to problem.
I'm not familiar with
typescript-eslint
andeslint-plugin-import
internals, so I'm opening this issue to gather requirements.What I (think I) know:
What I want to know:
context
object)?typescript-eslint
andeslint-plugin-import
whose authors should be part of this discussion?Are you willing to submit a pull request to implement this change?
The next step will be to write an RFC, which I will write once we've settled on requirements and hopefully brainstormed some solutions.
Related discussions:
The text was updated successfully, but these errors were encountered: