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

Add TypeScript support #426

Merged
merged 10 commits into from Feb 24, 2020
Merged

Add TypeScript support #426

merged 10 commits into from Feb 24, 2020

Conversation

pvdlg
Copy link
Contributor

@pvdlg pvdlg commented Feb 17, 2020

Fix #373
Fix xojs/eslint-config-xo-typescript#15

Xo will now automatically lint .ts and .tsx with the config from eslint-config-xo-typescript. No configuration needed!


IssueHunt Summary

Referenced issues

This pull request has been submitted to:


IssueHunt has been backed by the following sponsors. Become a sponsor

@sindresorhus
Copy link
Member

Can you make it handle .d.ts too?

Per issue description:

Should lint foo.d.ts (TS definition files) in non-TS projects automatically.

I don't have that many actual TypeScript projects, but I have hundreds of JS packages with a *.d.ts file and it would be nice to be able to enforce best practices in those.

@pvdlg
Copy link
Contributor Author

pvdlg commented Feb 18, 2020

Can you make it handle .d.ts too?

It should lint .d.ts files. index.d.ts is considered to have the extension .ts so it would be included.
Did you encountered an issue linting a .d.ts file?

@sindresorhus
Copy link
Member

Good point. I was out on my phone and didn't check yet.

@sindresorhus
Copy link
Member

There's a problem with npm link'ing XO. I tried npm link in XO and then npm link xo in, for example, https://github.com/sindresorhus/make-dir, and I'm getting:

Error: Failed to load config "xo-typescript" to extend from.
Referenced from: BaseConfig
    at configMissingError (/Users/sindresorhus/dev/oss/xo/node_modules/eslint/lib/cli-engine/config-array-factory.js:265:9)
    at ConfigArrayFactory._loadExtendedShareableConfig (/Users/sindresorhus/dev/oss/xo/node_modules/eslint/lib/cli-engine/config-array-factory.js:826:23)
    at ConfigArrayFactory._loadExtends (/Users/sindresorhus/dev/oss/xo/node_modules/eslint/lib/cli-engine/config-array-factory.js:731:25)
    at ConfigArrayFactory._normalizeObjectConfigDataBody (/Users/sindresorhus/dev/oss/xo/node_modules/eslint/lib/cli-engine/config-array-factory.js:660:25)
    at _normalizeObjectConfigDataBody.next (<anonymous>)
    at ConfigArrayFactory._normalizeObjectConfigData (/Users/sindresorhus/dev/oss/xo/node_modules/eslint/lib/cli-engine/config-array-factory.js:596:20)
    at _normalizeObjectConfigData.next (<anonymous>)
    at createConfigArray (/Users/sindresorhus/dev/oss/xo/node_modules/eslint/lib/cli-engine/config-array-factory.js:340:25)
    at ConfigArrayFactory.create (/Users/sindresorhus/dev/oss/xo/node_modules/eslint/lib/cli-engine/config-array-factory.js:395:16)
    at createBaseConfigArray (/Users/sindresorhus/dev/oss/xo/node_modules/eslint/lib/cli-engine/cascading-config-array-factory.js:86:48)

It works if I manually copy the XO directory into node_modules of make-dir.

@sindresorhus
Copy link
Member

It works if I manually copy the XO directory into node_modules of make-dir.

However, then there are some parse errors:

~/dev/oss/make-dir master
❯ xo

  index.test-d.ts:2:16
  ✖  2:16  Parsing error: Unexpected token =

  index.d.ts:4:9
  ✖  4:9   Parsing error: Unexpected token namespace

  2 errors

@sindresorhus
Copy link
Member

There's also a problem with reading tsconfig.json directly. tsconfig.json supports comments and it's very common to put it in there. I would look into how @typescript-eslint/eslint-plugin reads the tsconfig.

Here's the error:

~/dev/oss/got
❯ xo
JSONError: JSON Error in /Users/sindresorhus/dev/oss/got/tsconfig.json:

  3 | 	"compilerOptions": {
  4 | 		"outDir": "dist",
> 5 | 		"target": "es2018", // Node.js 10
    | 		                    ^
  6 | 		"lib": [
  7 | 			"es2018"
  8 | 		]

@pvdlg
Copy link
Contributor Author

pvdlg commented Feb 18, 2020

The loading is probably related to how ESlint resolve config/plugin names. I'll look into it to see if something can done (maybe passing absolute path to ESlint?)

I'll check the parsing errors and the comment in tsconfig.json tomorrow.

@sindresorhus
Copy link
Member

Non-TS projects with a d.ts file don't usually have a tsconfig.json file, but TS is way too loose by default. We should include some strict defaults for such cases, by using https://github.com/sindresorhus/tsconfig/blob/master/tsconfig.json

@pvdlg
Copy link
Contributor Author

pvdlg commented Feb 18, 2020

Non-TS projects with a d.ts file don't usually have a tsconfig.json file, but TS is way too loose by default. We should include some strict defaults for such cases, by using https://github.com/sindresorhus/tsconfig/blob/master/tsconfig.json

I'm not sure it's possible. We look at the tsconfig.json only to figure out the path and pass it to parserOptions.project but it's the parser that loads it. It doesn't seems to be a way to pass the config to the parser programatically.

@sindresorhus
Copy link
Member

I'm not sure it's possible. We look at the tsconfig.json only to figure out the path and pass it to parserOptions.project but it's the parser that loads it. It doesn't seems to be a way to pass the config to the parser programatically.

typescript-eslint/typescript-eslint#1613

@@ -47,6 +55,8 @@ const mergeFn = (previousValue, value) => {
}
};

const isTypescript = file => TYPESCRIPT_EXTENSION.includes(path.extname(file).replace('.', ''));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const isTypescript = file => TYPESCRIPT_EXTENSION.includes(path.extname(file).replace('.', ''));
const isTypescript = file => TYPESCRIPT_EXTENSION.includes(path.extname(file).slice(1));

Since you know what extname will always return a string with . in front or an empty string. It should be safer than just replacing the first dot.

readme.md Outdated Show resolved Hide resolved
@pvdlg
Copy link
Contributor Author

pvdlg commented Feb 18, 2020

I can't reproduce the issue you are having on https://github.com/sindresorhus/make-dir

I cloned the repo, then npm install -D xojs/xo#typesscript. I also had to add typescript to the project with npm install typescript -D which is a required dependency of the parser it seems.
Finally I had to add a tsconfig.json with:

{
  "include": ["**/*.ts"]
}

Then running xo:

~/g/make-dir (master ✚1…1) ❯ xo                                                                                                                 264ms

  index.test-d.ts:11:38
  ✖  11:38  Use octal literals instead of parseInt().  prefer-numeric-literals
  ✖  20:42  Use octal literals instead of parseInt().  prefer-numeric-literals

  2 errors

It seems that a tsconfig.json is required by @typescript-eslint/parser. From the doc:

This setting is required if you want to use rules which require type information

Maybe we can try to figure out which rules require parserOptions.project and disable them if no tsconfig.json is found. But that would be a pain to maintain as every time we add a rule to eslint-config-xo-typescript we would have to disable it in XO when no tsconfig.json is found.

Another solution is to enable the Typescript rules only when a tsconfig.json is found even if the file is a .ts. Which would require projects with only a .d.ts file to add a tsconfig.json.

@sindresorhus
Copy link
Member

Maybe we can try to figure out which rules require parserOptions.project and disable them if no tsconfig.json is found. But that would be a pain to maintain as every time we add a rule to eslint-config-xo-typescript we would have to disable it in XO when no tsconfig.json is found.

Yeah, I don't think that's a realistic solution.

Another solution is to enable the Typescript rules only when a tsconfig.json is found even if the file is a .ts. Which would require projects with only a .d.ts file to add a tsconfig.json.

That would severely limit the usefulness of this. There are much more projects with .d.ts than there are pure TS projects and I don't want to have to add a tsconfig to pure JS projects.

@sindresorhus
Copy link
Member

Hmm, an idea. We could just write out a tsconfig file to a temp directory and point parserOptions.project to that.

@pvdlg
Copy link
Contributor Author

pvdlg commented Feb 18, 2020

For info typescript-eslint/typescript-eslint#406 which is essentially the same as the one you opened hasn't been answered.

Also see typescript-eslint/typescript-eslint#689. It seems the tsconfig.json file is used at a minimum to read the include property to figure out all the files that are part of the project and compile them.

To be honest I though a tsconfig.json was mandatory for every TypeScript project and I didn't think about JS project have a type definition file.

If we add a default tsconfig.json I'm not sure if we should set the include with just the file we are currently linting or with ["**/*.ts",**/*.tsx"]. In the first case we wouldn't lint much as we would loose the ability to use the type definition from other files. In the other case we might create unintended behaviors on some projects.

@sindresorhus
Copy link
Member

If we add a default tsconfig.json I'm not sure if we should set the include with just the file we are currently linting or with ["/*.ts",/*.tsx"]. In the first case we wouldn't lint much as we would loose the ability to use the type definition from other files. In the other case we might create unintended behaviors on some projects.

We should only do it for .d.ts files. Actual TypeScript projects are expected to have their own tsconfig file.

@sindresorhus
Copy link
Member

Hmm, maybe we could fix xojs/eslint-config-xo-typescript#15 at the same time (It's a huge pain-point). Often the tsconfig file contains more files than you actually want to lint. We could read the tsconfig file and switch out includes with only the files being linted.

// @fregante

@pvdlg
Copy link
Contributor Author

pvdlg commented Feb 18, 2020

After doing a lot of tests, we can create a temporary one and pass it to parserOptions.projet. However the temporary tsconfig.json has to contains absolute paths in the include property.

This will not work:

{
  "include": ["**/*.ts"]
}

This will work:

{
  "include": ["/users/pvdlg/project/**/*.ts", "/users/pvdlg/project/**/*.tsx"]
}

So we could do:

  • if we don't find a tsconfig.json => create a temp one with "include": ["${options.cwd}/**/*.ts", "${options.cwd}/**/*.tsx"]
  • if we find a tsconfig.json => create a temp one with "include": ["${options.cwd}/**/*.ts", "${options.cwd}/**/*.tsx"] that extends the file we've found

I think it should solve both xojs/eslint-config-xo-typescript#15 and projects without a tsconfig.json.

That seems like a clunky workaround, but it's the only solution I've found.

@sindresorhus
Copy link
Member

if we don't find a tsconfig.json => create a temp one with "include": ["${options.cwd}//*.ts", "${options.cwd}//*.tsx"]

I'm not sure how @typescript-eslint/parser works under the hood with the include option, but just globbing everything might be slow. Wouldn't it be better to pass in the .ts files XO has already found?

Also, here we should inject some sensible defaults: https://github.com/sindresorhus/tsconfig/blob/9c49b210a7e7dee5a0feeb8433c8cc165f6319bb/tsconfig.json#L15-L19

That seems like a clunky workaround, but it's the only solution I've found.

I'm totally fine with it being clunky if it fixes the problem.

@pvdlg
Copy link
Contributor Author

pvdlg commented Feb 24, 2020

@sindresorhus I updated the PR per our discussion.
This should solve the case of projects without a tsconfig.json and xojs/eslint-config-xo-typescript#15.

Note that in the case of project without a tsconfig.json, when linting a subset of files @typescript-eslint/eslint-plugin will have no way to know about other files in the project and that might affect how the rules behave.
For example if you have a project with files src/a.ts and src/b.ts and run xo src/a.ts @typescript-eslint/eslint-plugin will execute its rule without knowledge of b.ts. Not sure if that's really an issue though. Plus there is nothing we can do other than globing the whole project...

This problem will not happen if you have a tsconfig.json.

@sindresorhus
Copy link
Member

Can you also ensure the generated tsconfig is strict by adding this config to it?

"target": "es2018", // Node.js 10
"newLine": "lf",
"strict": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,

@sindresorhus
Copy link
Member

Can you mention in the readme that it works for d.ts files in JS projects without a tsconfig file? I don't think that would be obvious to the user.

@sindresorhus
Copy link
Member

I've been testing this on a bunch of projects and it seems to work great. 👌

@pvdlg
Copy link
Contributor Author

pvdlg commented Feb 24, 2020

I made the requested changes.

I added the default config only when no tsconfig.json is found.
I don't think it's a good idea to add those when the project has a tsconfig.json because it would result in the linter using different options than the the compiler. Also there is a lot of project that doesn't set options like strict, noImplicitReturns and use the default on purpose. For example when transitioning from JS to TS. What do you think?

@sindresorhus
Copy link
Member

Oh yeah. Definitely. I meant when there’s no tsconfjg file.

@sindresorhus
Copy link
Member

Manually tested it again and seems to work great.

@sindresorhus sindresorhus merged commit b0dfcbd into master Feb 24, 2020
@sindresorhus sindresorhus deleted the typescript branch February 24, 2020 18:58
@sindresorhus
Copy link
Member

Woooot! Really awesome that you decided to implement this. 🎉✨

Should I do a new release?

@bmealhouse
Copy link

Very excited for these changes. Thanks so much @pvdlg!

@pvdlg
Copy link
Contributor Author

pvdlg commented Feb 24, 2020

Should I do a new release?

Yes that would be awesome!

@sindresorhus
Copy link
Member

It's out: https://github.com/xojs/xo/releases/tag/v0.27.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

test files create parser issue Built-in support for TypeScript
3 participants