Skip to content
This repository has been archived by the owner on Jan 19, 2019. It is now read-only.

Discussion: Extending core ESLint rules to "just work" with TS-specific nodes #77

Closed
JamesHenry opened this issue Sep 1, 2016 · 39 comments

Comments

@JamesHenry
Copy link
Member

JamesHenry commented Sep 1, 2016

In agreement with @nzakas, I wanted to start a discussion here which summarises the findings so far on which core ESLint rules currently have issues with TypeScript-specific concepts, such as interfaces and decorators.

We do have a few options open to us with regards to making this all work (and the eslint-plugin-typescript plugin will still have its place regardless), but we are in agreement that it would be awesome if we did not have to duplicate any existing rules where all we are trying to do is match the same functionality as on standard JS nodes.

I have been running all of the core rules over a large TypeScript codebase (definitely not an exhaustive use of TypeScript features in there, but it's a great start) and noted the following, some of which is fairly subjective:


Rule: camelcase

We have the ability to control whether or not camelcase will apply to object properties, e.g.

"camelcase": ["error", { "properties": "never" }]

Opinion: We should be able to do this for TypeScript interfaces properties too


Rule: keyword-spacing

We can enforce spaces before and after keywords like so:

"keyword-spacing": ["error", { "before": true, "after": true }]

Opinion: We should be able to make type "casting" exempt from this in some way

E.g. I had this example:

<models.ICreativeTemplate>this.currentCreative.template

...where I would not want to put a space between the > and this keyword, but the rule naturally wants me to.


Rule: no-undef

With TypeScript, we are supporting class properties and interfaces. These are both causing false negatives with no-undef:

class A {
  foo = true
}

/* ESLint:
error, 'foo' is not defined
interface MyInterface {
  bar: string,
  baz: number
}

/* ESLint:
error, 'MyInterface' is not defined
error, 'bar' is not defined
error, 'baz' is not defined

It is very likely there are more TypeScript features that will cause the same, but currently those two are so noisy in my codebase that it is not worth investigating further.

This was also reported here: #75


Rule: no-used-vars

There are a couple of things causing false negatives with no-unused-vars:

(1) Class properties created via the constructor, and then later used in a method

class SomeClass {
    constructor(
        private foo: string
    ) {}
    someMethod() {
        return this.foo
    }
}

/* ESLint:
error, 'foo' is defined but never used

(2) Decorators

import { Injectable } from 'foo-framework'

@Injectable()
class Service {}

/* ESLint:
error, 'Injectable' is defined but never used

This was also reported here: #55


Rule: no-useless-constructor

Using the same class property assignment within a constructor example from above, we will also get a false negative on no-useless-constructor

class SomeClass {
    constructor(
        private foo: string
    ) {}
    someMethod() {
        return this.foo
    }
}

/* ESLint:
error, Useless constructor

Rule: space-infix-ops

This rule is basically unusable, given the type annotation syntax :


Rule: semi

It was pointed out here #61 (comment) that TypeScript-only statements will currently not be detectable as needing a semi-colon.

E.g.

"semi": ["error", "always"]
type Result<T> = Success<T> | Failure

/* ESLint:
(No error reported, but should point out missing semi-colon)

@JamesHenry
Copy link
Member Author

I would also like to point out that in spite of the above, I am running over 100 of the core ESLint rules on the large TypeScript codebase without any problems at all!

🎉

@nzakas
Copy link
Member

nzakas commented Sep 2, 2016

Thanks for doing this! I'm low on energy, so probably will dig in more next week.

Off the top of my head, I think we can make space-infix-ops skip type annotations in the core rule (we've done this elsewhere).

For semi, are there many TS-specific constructs that need semicolons? Or is it just a subset?

@JamesHenry
Copy link
Member Author

No problem at all! Just reply whenever you can.

For semi, are there many TS-specific constructs that need semicolons? Or is it just a subset?

I cannot say that I have ever thought about it (I personally use no semi-colons, regardless of JavaScript or TypeScript), but I feel like it would be limited to a subset.

I will look into submitting a PR to eslint for the space-infix-ops rule.

@nzakas
Copy link
Member

nzakas commented Sep 6, 2016

Part of what I'm wondering is if things like the type statement can be represented as VariableDeclaration so it would transparently work, but if there's a large number of statements besides that, then we need something a bit more generic.

@JamesHenry
Copy link
Member Author

JamesHenry commented Sep 6, 2016

There is no reference to semi-colons in the language spec: https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md

I have asked the TypeScript team here: microsoft/TypeScript#10730

@JamesHenry
Copy link
Member Author

JamesHenry commented Sep 7, 2016

@nzakas It seems like it might be worth doing it for just the type alias (e.g. type Result<T> = Success<T> | Failure) initially:

From Mohamed on the TS Team:

The only place that it is required by the grammar is the type alias declaration. and the implementation enforces the same ASI rules.

So, coming back to your suggestion:

Part of what I'm wondering is if things like the type statement can be represented as VariableDeclaration so it would transparently work

That would be an update to the core rule right? Not an AST (and therefore parser) change?

@nzakas
Copy link
Member

nzakas commented Sep 8, 2016

@JamesHenry what I meant was, we change what the parser output so that the type alias declaration outputs a VariableDeclaration node instead of whatever we're doing now. That way, the core rule will just work because it already checks VariableDeclaration nodes (though we'd have to double-check that other core rules aren't confused by this, but giving it a brief once-over, I think it should work fine).

Basically, VariableDeclaration.kind would be "type" instead of "var" or "let" or "const".

@JamesHenry
Copy link
Member Author

Ah gotcha! Yeah let's give that a go as a first step

@JamesHenry
Copy link
Member Author

🎉

sep-08-2016 21-48-20

@vitorbal
Copy link
Member

vitorbal commented Sep 8, 2016

This is great, awesome job @JamesHenry!

@jkillian
Copy link

@JamesHenry nice work on the type aliases, I think treating them as a special kind of variable declaration makes sense - since it's very similar syntacticly.

Not sure if you have a list of TS-specific constructs you're testing, but a couple more worth checking if they need special treatment:

Module declarations, for example shorthand ambient module declarations:

declare module "foo/*";

Type guards:

function isCat(a: any): a is Cat {   // specifically, the `a is Cat` part
  return a.name === 'kitty';
}

The non-null assertion operator:

function processEntity(e?: Entity) {
    validateEntity(e);
    let s = e!.name;  // Assert that e is non-null and access name
}

Typed usages of this (this is not really a parameter):

function f(this: void) {
    // make sure `this` is unusable in this standalone function
}

I don't think any of these will cause too big of issues, and some of them are relatively new TS features, but wanted to throw them out there as things to watch out for!

@JamesHenry
Copy link
Member Author

Thanks a lot, @jkillian! Those will all make great test cases for full TypeScript 2 support. I will be prioritising that as soon as it is "final/stable"

@nzakas
Copy link
Member

nzakas commented Sep 12, 2016

@jkillian can you paste those into a new issue for TypeScript 2 support? I'm afraid they'll get lost if left here.

@jkillian
Copy link

@nzakas sure, created it over in #90

Just a note, the Type Guard example above is not TS 2.0 specific, so I didn't move it over to #90.

@trainerbill
Copy link

trainerbill commented Sep 29, 2016

no-unused-vars is reporting an issue when you import an interface. Don't see that in the list anywhere.

@JamesHenry any timeframe on when this plugin will work with "extends": "eslint:recommended"? days/weeks/months.

@JamesHenry
Copy link
Member Author

@trainerbill I am back from vacation now, and this project is one of my top priorities outside of work.

Thanks, interfaces and no-unused-vars is known and will be handled in the complementary eslint-plugin-typescript project.

There have a been a number of wider topics to tackle within the wider ESLint ecosystem which affect this project, so it is difficult to estimate at this stage. Once they are nailed down, you will suddenly see a load of progress all at once.

As I mention above, I am already using it on a large project with 100+ standard rules active.

@JamesHenry
Copy link
Member Author

@trainerbill interfaces being marked as used is handled here: bradzacher/eslint-plugin-typescript#9

@trainerbill
Copy link

@JamesHenry I see that 1.0 was released. Just wondering if no-undef and no-unused-vars have been fixed. I have quite a few and spot checking them look like most are false negatives. Is there an ongoing list of work in progress items so we know what to submit as issues should we find one. Appreciate all you are doing here.

@JamesHenry
Copy link
Member Author

@trainerbill I have been really stretched on other things over the last few weeks, but things should improve as we hit the holiday break.

We moved to v1 purely because we are following semver and the support (and requirement) for TS 2 was a breaking change.

Specifically in the case of no-unused-vars, I would not use a linter at all any more - the TypeScript compiler can now enforce that for you:

Set the following flags to true when running the CLI or in your tsconfig.json

noUnusedLocals
noUnusedParameters

@trainerbill
Copy link

@JamesHenry awesome. did not know that existed in the compiler. Thanks!

@ikokostya
Copy link

Specifically in the case of no-unused-vars, I would not use a linter at all any more - the TypeScript compiler can now enforce that for you:

noUnusedLocals and noUnusedParameters are not configurable like eslint's no-unused-vars. See buzinas/tslint-eslint-rules#171

@scottohara
Copy link

I'm a little confused about the current status of TS support.

I planning the conversion an existing ES2016 codebase to TS, initially with minimal changes except for:

  • adding type annotations
  • using shorthand creation of class properties from constructor args, e.g:
constructor(public name: string) { 
}

instead of

constructor(name: string) {
  this.name = name;
}

As a test, I copied the existing .eslintrc.json into a new project, installed typescript-eslint-parser and specified the new parser:

"parser": "typescript-eslint-parser",

I then created a simple test.ts file:

/* A comment */
export default class Foo {
  constructor(public name: string) {
  }

  get greeting() : string {
    return `Hello ${this.name}`;
  }
}

As expected, linting this file encounters similar issues to the ones listed at the top of this thread:

  3:2  error  Useless constructor              no-useless-constructor
  3:22  error  'name' is defined but never used  no-unused-vars
  3:36  error  Unexpected empty constructor     no-empty-function

✖ 3 problems (3 errors, 0 warnings)

I noticed this comment later in the thread:

Thanks, interfaces and no-unused-vars is known and will be handled in the complementary eslint-plugin-typescript project.

So I installed eslint-plugin-typescript and updated the config to:

  "parser": "typescript-eslint-parser",
  "plugins": [
    "typescript"
  ],

…but the same errors still occur.

Have I misunderstood what eslint-plugin-typescript is intended for?

Versions used are:

eslint-plugin-typescript@0.1.0
eslint@3.19.0
typescript@2.3.0
typescript-eslint-parser@2.1.0

@scottohara
Copy link

Ah, it looks like I may need to explicitly enable the typescript/no-unused-vars rule? (which isn't listed in the README)

@soda0289
Copy link
Member

soda0289 commented Apr 12, 2017

@scottohara The typescript plugin rule no-unused-vars will correctly mark decorators as used and will soon have support for type annotations as well. You will need to use the master branch of this plugin as the one on npmjs is quite outdated.

@flying-sheep
Copy link
Contributor

flying-sheep commented Apr 12, 2017

i have a tiny test case for three 'no-undef' failures. it will complain for every single one of the three identifiers (Iface, prop, par)

const p = require('typescript-eslint-parser')
const { SourceCode, CLIEngine, linter } = require('eslint')

const filename = 'test.ts'
const code = `
interface Iface {
	prop?: (par: string) => void
}
`
const ast = p.parse(code, { tokens: true, comment: true })

const sc = new SourceCode(code, ast)

const cli = new CLIEngine({
	useEslintrc: false,
	parser: 'typescript-eslint-parser',
	parserOptions: { sourceType: 'module' },
	rules: { 'no-undef': 1 },
})

const config = cli.getConfigForFile(filename)

const messages = linter.verify(sc, config, { filename })

console.log(messages)

@mightyiam
Copy link

mightyiam commented Sep 28, 2017

I don't experience a problem with space-infix-ops. Perhaps that rule could be removed from this issue’s description?

@mightyiam
Copy link

At least with the example in the issue description, my experiments show that semi works fine. Both for "always" and for "never". If it is fixed by now, could you please remove that from the description?

@j-f1
Copy link
Contributor

j-f1 commented Sep 28, 2017

Another option would be to add a checklist (- [ ] foo) for each of the items, then check off the ones that are completed.

@JamesHenry
Copy link
Member Author

This issue is very outdated and potentially confusing to new users so I am going to close it. Please feel free to open new issues for anything which you feel has not been addressed yet.

@scottohara
Copy link

scottohara commented Dec 11, 2017

@JamesHenry is it worth filing a new issue for no-useless-constructor / no-empty-function; as I believe these are still outstanding (see example below).

Or is this something more appropriately handled by eslint-plugin-typescript? (similar to how typescript/no-unused-vars is used to correct any spurious unused vars warnings)

For now, I have turned off these two rules in my .ts projects; but for projects that mix *.ts and *.js code, it would be nice to be able to have these rules enabled to catch any useless constructors/empty functions in *.js code without it also warning about valid *.ts constructors.

Example:

export default class Foo {
  constructor(private name: string) {}

  get greeting() : string {
    return `Hello ${this.name}`;
  }
}
  2:2   error  Useless constructor           no-useless-constructor
  2:36  error  Unexpected empty constructor  no-empty-function

Edit: Created as #418

@nevir
Copy link

nevir commented Dec 11, 2017

Typescript itself now tests for unused member properties, so for .ts you probably want to leverage that (but it won't catch empty constructors, I suppose).

I think you can conditionally turn on the rule for .js files - see https://eslint.org/docs/user-guide/configuring#configuration-based-on-glob-patterns

(impl: eslint/eslint#8081 and eslint/eslint#7177)

@scottohara
Copy link

Sure, but this issue was originally about ESLint rules "just working" with TS-nodes; and clearly that's not the case yet for no-useless-constructor / no-empty-function.

I don't think configuring glob patterns to workaround this satisfies the original intent of "just works".

@nevir
Copy link

nevir commented Dec 12, 2017 via email

@tchakabam
Copy link

@JamesHenry I have created a seperate issue about the problem in mixed codebases with the no-undef rule, for which we have a workaround, but not a fix yet :)

#416

@keplersj
Copy link
Contributor

I apologize if this comment creates unnecessary noise but I'm a little confused about the status of this issue. Is this warning in the readme still accurate?

screen shot 2018-01-30 at 3 51 44 pm

@tchakabam
Copy link

tchakabam commented Jan 31, 2018

@keplersj It is still true for no-undef :) which is why issue #416 has been opened.

However, if your codebase is pure Typescript, this isn't likely to be a real problem. You can disable that rule, as the ts compiler will shout anyway if anything is undef :)

@keplersj
Copy link
Contributor

@tchakabam Thanks for clearing that up for me. 😁 I've sent a PR to update the README but haven't followed commit guidelines correctly. I'll fix that. #440

@ffxsam
Copy link

ffxsam commented Sep 10, 2018

Anyone know what the status of this is? I'm running into the issue where ESLint will throw no-unused-vars when importing TypeScript interfaces, even though they're being used.

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

No branches or pull requests