Skip to content
This repository was archived by the owner on Jan 19, 2019. It is now read-only.
This repository was 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

@JamesHenry

Description

@JamesHenry
Member

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)

Activity

JamesHenry

JamesHenry commented on Sep 1, 2016

@JamesHenry
MemberAuthor

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

nzakas commented on Sep 2, 2016

@nzakas
Member

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

JamesHenry commented on Sep 6, 2016

@JamesHenry
MemberAuthor

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

nzakas commented on Sep 6, 2016

@nzakas
Member

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

JamesHenry commented on Sep 6, 2016

@JamesHenry
MemberAuthor

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

JamesHenry commented on Sep 7, 2016

@JamesHenry
MemberAuthor

@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

nzakas commented on Sep 8, 2016

@nzakas
Member

@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

JamesHenry commented on Sep 8, 2016

@JamesHenry
MemberAuthor

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

JamesHenry

JamesHenry commented on Sep 8, 2016

@JamesHenry
MemberAuthor

🎉

sep-08-2016 21-48-20

vitorbal

vitorbal commented on Sep 8, 2016

@vitorbal
Member

This is great, awesome job @JamesHenry!

jkillian

jkillian commented on Sep 11, 2016

@jkillian

@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!

32 remaining items

JamesHenry

JamesHenry commented on Dec 11, 2017

@JamesHenry
MemberAuthor

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

scottohara commented on Dec 11, 2017

@scottohara

@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

nevir commented on Dec 11, 2017

@nevir

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

scottohara commented on Dec 11, 2017

@scottohara

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

nevir commented on Dec 12, 2017

@nevir
tchakabam

tchakabam commented on Dec 19, 2017

@tchakabam

@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

keplersj commented on Jan 30, 2018

@keplersj
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

tchakabam commented on Jan 31, 2018

@tchakabam

@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

keplersj commented on Jan 31, 2018

@keplersj
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

ffxsam commented on Sep 10, 2018

@ffxsam

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 join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @nzakas@nevir@scottohara@flying-sheep@ikokostya

        Issue actions

          Discussion: Extending core ESLint rules to "just work" with TS-specific nodes · Issue #77 · eslint/typescript-eslint-parser