Skip to content

Enhancement: Allow specifying default TSConfig behavior with project service #7761

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

Closed
4 tasks done
JoshuaKGoldberg opened this issue Oct 18, 2023 · 11 comments
Closed
4 tasks done
Labels
enhancement New feature or request package: parser Issues related to @typescript-eslint/parser wontfix This will not be worked on

Comments

@JoshuaKGoldberg
Copy link
Member

JoshuaKGoldberg commented Oct 18, 2023

Before You File a Proposal Please Confirm You Have Done The Following...

Relevant Package

parser

My proposal is suitable for this project

  • I believe my proposal would be useful to the broader TypeScript community (meaning it is not a niche proposal).

Description

I've heard of two requests around EXPERIMENTAL_useProjectService:

These are technically two requests, but I think it'd be good to discuss them together. Whatever API we end up going with may need to accommodate both.

Proposal: how about a fallbackCompilerOptions?

type FallbackCompilerOptions = 
  /* Don't allow fallback/inferred projects at all */
  | "error" 
  /* Allow fallback/inferred projects but log a warning */
  | "warn"
  /* Directly use these */
  ts.CompilerOptions;

// (approximation of the shape we'd allow)
interface ProjectServiceParserOptions {
  EXPERIMENTAL_useProjectService: true;

  // If undefined, go with TypeScript's defaults
  fallbackCompilerOptions?: FallbackCompilerOptions;
}

Additional Info

Courtesy of @jakebailey in https://discord.com/channels/508357248330760243/1163326359100199012/1163526441103802398: project services have a setCompilerOptionsForInferredProjects we can use.

#7752 largely enables the case of an inferred project in the first place.

Edit: oh, and also mentioned here: #6575 (comment)

@bradzacher
Copy link
Member

The difficult thing here is that if we allow this fallback it will be impossible to tell if things are misconfigured.

Back in like v1 and v2 IIRC we used to have the default program fallback and the issue is that if the user has misconfigured something the result was that things "just worked" but REALLY REALLY SLOWLY. This lead to a lot of annoyed people proclaiming that type-aware linting was horribly slow - when in actual fact they just had misconfigured things and were silently falling into the default program case.

The reason the "default program" case is so slow is because TS cannot share anything. So if you have say two files and A imports B. If you lint B and find its config - then you create program1 for that. When you lint A - say that it's not included in the config correctly. What happens is we would create a new program2 for A and typecheck it which in turn means we need to also include B in program2 - so B has been typechecked twice and is duplicated in memory!

For a fully defined, well configured codebase - this option would be awesome because you wouldn't need to rely on hacks like a tsconfig.eslint.json to define a config for additional files (eg config files) that you want to type-aware lint but don't want to include in your main config.

It's just hard though - how can we validate that the user is in a valid state whilst also providing a fallback option? Without an answer to this - this is why we went with the hard error without a fallback - because it's explicit as to when things are correctly configured.

@JoshuaKGoldberg
Copy link
Member Author

JoshuaKGoldberg commented Oct 19, 2023

create a new program2 for A and typecheck it which in turn means we need to also include B in program2 - so B has been typechecked twice and is duplicated in memory!

I was hopeful this wouldn't be an issue when using the project service. As in, I was hoping the project service would give the same default project and also the same created program for each unlisted file. But per https://github.com/JoshuaKGoldberg/repros/tree/project-service-multiple-js-files that's not the case. When useSingleInferredProject is enabled:

  • Programs are now reused between files (good!)
  • Projects are created anew for each .js file

A couple thoughts:

  • Is there any chance default/inferred programs could have files added to them? Would this break everything in TypeScript-land?
    • Edit: or, take in / deduplicate work from previously created default/inferred programs?
  • Maybe there are some configurable heuristics we could add in? E.g. only allowing root-level .js files?

cc @jakebailey

@jakebailey
Copy link
Collaborator

With the ProjectService to the best of my knowledge, I would expect you to be effectively as efficient as one can be within the regular TS API.

Are you actually seeing it create a whole new program for every individual file? I would expect not, otherwise people's unconfigured code with global scripts wouldn't work. E.g, the test files in TypeScript's test/cases/... dir do not have an associated tsconfig, but opening more than one of those at a time in the editor when they represent global scripts makes TS think there are duplicate names in scope, which can only mean that they are sharing a Program.

@jakebailey
Copy link
Collaborator

Specifically, see:

https://github.com/microsoft/TypeScript/blob/b1f5ef69e86e564a131a49b80cd0f5dc428e515d/src/server/editorServices.ts#L3805-L3819

My understanding is that any orphaned files are all getting shoved into a single inferred project, not getting their own. So I don't think you'll hit a perf hole here.

@JoshuaKGoldberg
Copy link
Member Author

Aha! Thanks, that's very good for us. Added https://github.com/JoshuaKGoldberg/repros/tree/4aaf9f35761f9922beccbe544231401076e6b593 showing that source files are indeed re-used.

I think next up would be to prove that type checking isn't duplicated either. I'm a bit tired now so will have to leave that on the table for a bit. But for now I'd be happy to continue under the assumption that this is all performance-friendly.

@jakebailey
Copy link
Collaborator

I'm a little confused; shouldn't useSingleInferredProject be a noop when the project service is enabled, since the service takes care of this? Or am I confusing this with something else?

@jakebailey
Copy link
Collaborator

Oh, that's a tsserver option. I guess I'd have to see what VS Code sets.

@bradzacher
Copy link
Member

Yeah it's worth noting that for IDEs it's desirable to have a fall back because it means they can always provide IDE features even if things are misconfigured.

But it does run the same risk of providing incorrect features due to misconfigurations and I've seen first hand when this can go wrong. For example the "automatic type acquisition" fallback can add types for your code and make code look correct in the IDE but fail from the CLI which doesn't support ATA.

I personally think we should keep an error for non-configured files unless we can ensure that the misconfiguration case doesn't turn into a performance hot mess.

@bradzacher
Copy link
Member

Note: the language service will cache source files and share them - however TS doesn't contain a mechanism to share types between programs.

This has always been a problem for us in monorepos and a major source of OOMs.

@JoshuaKGoldberg
Copy link
Member Author

JoshuaKGoldberg commented Nov 7, 2023

Following up on the discussion: #7752 adds a allowDefaultProjectForFiles right now. This intentionally is positioned as a last-ditch fallback: if a file is included in it and what the project includes, that's an error.

#7870 also posts a performance comparison showing that right now, with our current usage of APIs, the project service does add a good chunk of overhead for each not-included-by-TSConfig file.

@JoshuaKGoldberg
Copy link
Member Author

This conversation has gotten pretty long. Now that #7871 -> #7752 has been resolved, I'm going to close this one out as wontfix due to the performance considerations.

Note that while #7752 allowed non-included files to receive type information, giving those non-included files compiler options is still open. Filing the more targeted #8206 to deal with that.

Thanks for the discussion all!

@JoshuaKGoldberg JoshuaKGoldberg closed this as not planned Won't fix, can't repro, duplicate, stale Jan 6, 2024
@JoshuaKGoldberg JoshuaKGoldberg added wontfix This will not be worked on package: parser Issues related to @typescript-eslint/parser and removed triage Waiting for team members to take a look labels Jan 6, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jan 14, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request package: parser Issues related to @typescript-eslint/parser wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

3 participants