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

Why is having a tsconfig file required? #689

Closed
brianjenkins94 opened this issue Jul 9, 2019 · 14 comments
Closed

Why is having a tsconfig file required? #689

brianjenkins94 opened this issue Jul 9, 2019 · 14 comments
Labels
awaiting response Issues waiting for a reply from the OP or another party package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin

Comments

@brianjenkins94
Copy link

brianjenkins94 commented Jul 9, 2019

Repro

{
	"env": {
		"es6": true,
		"node": true
	},
	"parser": "@typescript-eslint/parser",
	"plugins": ["@typescript-eslint"],
	"extends": [
		// Disable rules that are incompatible with or better handled by TypeScript
		"plugin:@typescript-eslint/eslint-recommended",

		// Turn on TypeScript-specific recommended rules
		"plugin:@typescript-eslint/all"
	]
}

Any code will do.

Expected Result

Successful linting.

Actual Result

ESLint: Error while loading rule '@typescript-eslint/await-thenable': You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser. Occurred while linting /Users/bjenks/Desktop/index.ts. Please see the 'ESLint' output channel for details.

Because I'm not specifying parserOptions.project.

Additional Info

tsc falls back on sensible defaults, shouldn't @typescript-eslint/eslint-plugin do the same?

I'd rather not specify a tsconfig.json file if I don't have to. It's just another configuration file to keep track of and I've made it this far without needing it.

Versions

package version
@typescript-eslint/eslint-plugin 1.11.0
@typescript-eslint/parser 1.11.0
TypeScript 3.5.3
ESLint 6.0.1
node 11.14.0
npm 6.9.0
@brianjenkins94 brianjenkins94 added package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin triage Waiting for maintainers to take a look labels Jul 9, 2019
@bradzacher bradzacher added awaiting response Issues waiting for a reply from the OP or another party and removed triage Waiting for maintainers to take a look labels Jul 10, 2019
@bradzacher
Copy link
Member

what command do you run when you run tsc?

Do you literally just run tsc, or do you pass in some flags or file arguments like tsc main.ts or tsc src/**/*.ts?

The reason we require a tsconfig.json is so that typescript knows which files are included in the project.
It doesn't really matter what compiler options are set in your tsconfig.json (some flags like strict can change how certain rules work, however).

If you're using a rule that requires type information, typescript has to parse and typecheck every dependency of that file, so it can provide complete types.
Unfortunately eslint doesn't have a mechanism to tell us ahead of time which files are being linted, which means that we have to be greedy and parse the entire project ahead of time.

We can only do a greedy parse if we know what files are in your project. How can we do that?
Well we could let users supply extra config to the parserOptions which lets them specify which files are part of their project... or alternately tsconfig.json already has a well used and well defined mechanism for doing exactly that.

@brianjenkins94
Copy link
Author

brianjenkins94 commented Jul 10, 2019

I just keep my TypeScript TypeScript and setup my start script to run ts-node <entrypoint>, or use Rollup and the TypeScript plugin to transpile to JavaScript.

I guess what I'm saying is: I used to be able to lint TypeScript files -- whether they built correctly or not, using tslint and now there's extra configuration for me to do.

But now I'm second-guessing myself, maybe I wasn't taking advantage of rules that required type information in the first place... I'll go check.

Update: No, I was definitely dealing with rules like promise-function-async and no-floating-promises.

@brianjenkins94 brianjenkins94 changed the title tsc works without a tsconfig file, why is having a tsconfig file required? Could it be optional with sensible defaults instead? tsc works without a tsconfig file, why is having a tsconfig file required? Jul 10, 2019
@bradzacher
Copy link
Member

bradzacher commented Jul 10, 2019

When using ts-node <entrypoint> what you're saying is "ts-node, tell typescript that this is the entry file", and then typescript will parse the dependency graph found with that file at its root.

tslint works in the same way; i.e. when you use tslint <entrypoint>, it's doing the same thing.

The difference between us and tslint, is that tslint controls their entire stack - from cli to parser to rules.

We, on the other hand, don't control the eslint stack - just the parser and the rules. Because we don't control the CLI, we can't make the same optimisation that tslint can.

ESLint tells us one file at a time which files are going to be linted, but the typescript compiler API requires you to tell it what files to parse up front.
Which means we need another mechanism for telling typescript which files to parse - hence tsconfig.json.

You can achieve the same result with this minimal tsconfig.json:

{
  "files": [
    "<entrypoint>.ts"
  ]
}

@brianjenkins94
Copy link
Author

brianjenkins94 commented Jul 10, 2019

Hmm, okay, I've confirmed that that works. But doesn't that mean I can't use my @typescript-plugin rules (that require typings) on my JavaScript files without setting up a tsconfig.json first?

It just feels like having the tsconfig.json file is just a formality when the eslint plugin in Visual Studio Code is just running eslint on individual files as far as I can tell.

@bradzacher
Copy link
Member

if you have a project with only javascript files, and you want to use a rule which requires type information - the process is the same. Though I think you might be able to use a jsconfig.json instead? I'm not sure - I've never tried it.

@brianjenkins94
Copy link
Author

brianjenkins94 commented Jul 10, 2019

Huh, didn't know that was a thing.

But furthering my why-is-this-necessary point, I just set my tsconfig.json file to:

{
  "files": [
    "foo.ts"
  ]
}

And now I can open both JavaScript and TypeScript files and the eslint plugin is linting them.

I have achieved my desired functionality by having a tsconfig.json that points to a file that doesn't exist.

Update: Even better, I made my tsconfig.json file an empty object and it seems to be working just fine.

@brianjenkins94 brianjenkins94 changed the title tsc works without a tsconfig file, why is having a tsconfig file required? Why is having a tsconfig file required? Jul 10, 2019
@brianjenkins94
Copy link
Author

brianjenkins94 commented Jul 10, 2019

Why did this get closed?

I shouldn't have to set parserOptions.project to point to an empty JSON file to get this to work.

Something about this code is wrong:

/**
* Try to retrieve typescript parser service from context
*/
export function getParserServices<
TMessageIds extends string,
TOptions extends any[]
>(
context: TSESLint.RuleContext<TMessageIds, TOptions>,
): RequiredParserServices {
if (
!context.parserServices ||
!context.parserServices.program ||
!context.parserServices.esTreeNodeToTSNodeMap
) {
/**
* The user needs to have configured "project" in their parserOptions
* for @typescript-eslint/parser
*/
throw new Error(
'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.',
);
}
return context.parserServices as RequiredParserServices;
}

@bradzacher
Copy link
Member

If the above comments above aren't enough of a reason to explain why..

The typescript compiler api requires us to provide a config file. It takes in a path to a config file, not a config file object, but a path.

We can parse without a config, but typescript will parse in isolated modules mode, meaning you don't get much (if any) type information.

We also use the project to turn on/off a type check parse. The full project parse can take a lot longer, so some users don't want to use it.

The typescript compiler is pretty smart however, it can react to new files. Which is why it will allow files not defined within the config file to be parsed.

@brianjenkins94
Copy link
Author

Are those "new" files parsed in isolated modules mode?

@bradzacher
Copy link
Member

bradzacher commented Jul 10, 2019

Something about this code is wrong:

Sorry, I was on mobile before so I missed this bit.

This is where you need to be careful with how the project is setup and how eslint works.

typescript-estree - this package provides tooling to:

  • control the typescript compiler,
  • convert the typescript AST to the ESTree AST spec,
  • expose the root "Program" object to access type checking (if, and only if a tsconfig is provided).

parser - this package is a thin layer on top of typescript-estree to expose it in ESLint's expected parser format. (Also includes a few other eslint compatability features).

eslint-plugin - this package provides a set of eslint rules to validate your code.

ESLint passes a filename to parser, which passes it through to typescript-estree, which coordinates the typescript compiler API. If you provided a tsconfig, typescript-estree will return (in addition to the AST) what is called the parserServices, containing the objects required to access type information.

ESLint passes these parser services to the rules in eslint-plugin. The function you linked just checks if they exist and throws when they don't. Rules that require type information to function use this function so that they hard fail if you don't configure the parser correctly.

The code for coordinating the typescript compiler API is located here, in typescript-estree: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/typescript-estree/src/tsconfig-parser.ts


Are those "new" files parsed in isolated modules mode?

I'll be honest with you, we're at the limit of my knowledge of how typescript-estree (and more importantly the typescript compiler API) works exactly.

I think it might parse the "unknown" files as if they were a new program - which I believe means it invalidates any caching that's been done, meaning it will duplicate work (the side effect of this is that if the dependency graph is big, it will be very, very slow because it duplicates work).

If you've got a small project, doing this is fine - there's not enough files for the parse + typecheck to take too long.
However the best bet is to include your files in your tsconfig, so they can be parsed exactly once, up front. Either via the method I mentioned earlier, or alternately you can use globs if you use include instead:

{
  "include": [
    "src/**/*"
  ]
}

More info: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html

@brianjenkins94
Copy link
Author

brianjenkins94 commented Jul 10, 2019

I'm okay with specifying parserOptions.project as a workaround but I would prefer to have some way of communicating to eslint that I'm okay with it running in isolated modules mode.

Is that a feature request for this project or am I in the wrong place?

Looks like a feature request for @typescript-eslint/typescript-estree.

@bradzacher
Copy link
Member

that I'm okay with it running in isolated modules mode

This use case is supported - just don't provide a project option.

However in isolated modules mode, you cannot use any rules that require type information, because it straight up isn't generated by the typescript API.

@brianjenkins94
Copy link
Author

brianjenkins94 commented Jul 10, 2019

But if I do specify a project I get (albeit limited) use, of lint rules that require type information.

This is what I want.

I just don't want to have to pass in a path to a file that contains an empty object.

Something like project: true would be great.

Basically like what's being suggested here: #101 (comment) but with a default of empty object.

@brianjenkins94
Copy link
Author

brianjenkins94 commented Jul 11, 2019

Well I dug real deep into the source code and then realized what you were pointing at when you linked tsconfig-parser.ts:

const watchCompilerHost = ts.createWatchCompilerHost(
tsconfigPath,

So this isn't possible, as rootFileNames:

https://github.com/microsoft/TypeScript/blob/1bd631e593c1f3cf3ec0bd8ccba27d67094a158a/src/compiler/watch.ts#L1048-L1049

is strictly determined by parsing the config file.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 21, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
awaiting response Issues waiting for a reply from the OP or another party package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin
Projects
None yet
Development

No branches or pull requests

2 participants