Tslint: 'no-inferrable-types' and 'typedef' rules conflict

Created on 3 Oct 2015  ·  33Comments  ·  Source: palantir/tslint

Hi again,

I am not sure if this is issue or design decision, but I can see conflict between rules no-inferrebale-types and typedef.

Ex.

function fn(): void {
    for (let i = 0; i < 100; i++) {
        console.log(i);
    }
}

Snippet from https://github.com/palantir/tslint/blob/master/docs/sample.tslint.json:

 "typedef": [true, ...],
 "no-inferrable-types": true,

When I have these two rules turned on, I get:

error (typedef) test.ts[2, 12]: expected variable-declaration: 'i' to have a typedef

Adding type annotation number to variable i causes in turn following error:

error (no-inferrable-types) test.ts[2, 14]: LHS type (number) inferred by RHS expression, remove type annotation

Is there any way to have these two rules coexisted with each other?

For example, I want to have inferrable variables directly declared with primitive type (as rule doc says: number, boolean or string), but on the other hand, I want to force typedefs on non-primitive types.

Thanks,

O.

P1 Enhancement 🌹 R.I.P. 🌹

Most helpful comment

:+1: Ideally (imo) I would like a way to say "always require a typedef, unless the type is inferrable". Which I don't think is possible right now.

All 33 comments

Good catch, thanks for the heads-up. I added the no-inferrable-types rule without thinking about how it might conflict with the typedef rule, whoops! For now I'd recommend turning one of the two off, or only using some options of the typedef rule.

Longer term, I think we'll either want to integrate no-inferrable-types into the typedef rule or we'll want to at least have TSLint detect conflicting configurations like having both rules on.

I am facing the same issue.
I've turned off no-inferrable-types for now.

Yes same here, I would like to have ability to have both the rules co-exist.
I would not want typedef rule to kick in if the variable's type can be inferred.
i.e. "no-inferrable-types" should have priority over "typedef"

+1

let id: number = 0;
for (let job: string of NAMES_PROFFESSIONS) {
     /** some code */
     id++;
}

(no-inferrable-types) LHS type (number) inferred by RHS expression, remove type annotation

+1

Does your definition of "inferrable" types include constructor assignments?

// BAD (this hurts my eyes to read)
let labels: Map<string, string> = new Map<string, string>();
// GOOD (type is obvious)
let labels = new Map<string, string>();

but also...

// BAD (in a diff, it's not obvious what this type is)
let labels = this.buildLabels();
// GOOD
let labels: Map<string, string> = this.buildLabels();

Yes, it's dangerous. If I want to simplify my code and prevent to use type declaration for directly initialized variables, I can't do this strictly and this brings to such thing:

let x = 2;
let y;
let z: number;

x = 1;
y = 1;
z = 1;
x = 's'; // Type 'string' is not assignable to type 'number'
y = 's'; // It's OK
z = 's'; // Type 'string' is not assignable to type 'number'

It's may be a very useful option to allow skip type declaration only for initialized variables.

… and really not only for primitive types, as @pgonzal says!
Look at this, it's terrible:

const onChange: () => void = () => this.update();

:+1: Ideally (imo) I would like a way to say "always require a typedef, unless the type is inferrable". Which I don't think is possible right now.

I ran into this and made an ignore-params flag that helps out in my case. I want to force typedefs for all method/function parameters even when they can be easily inferred.

See PR: #1190 if you want to try it out

It's been a while since the original issue was submitted. Is the recommend option at this point to disable no-inferrable-types and include the type on everything? Anything else to try and enable and combine both no-inferrable-types and typedef seems like a hack and results in a bunch of pointless warnings. Hoping for a better solution in the near future.

@corydeppen Note the 8 thumbs up on @englercj's suggestion, above. It's unclear what "combine" means in your "seems like a hack" comment. If you mean "use together in tslint.json, then, yes. But "combining" an optional-inferrable-types argument on typedef would be great (at least, for 9 of us).

Can we get an update on this, please?

I think the best way to handle this is to deprecate the no-inferrable-types and just pass an option object to typedef to ignore the lack of type definitions if the type is inferrable according to certain patters, as initialized, initialized primitives, call signatures and other patters that would fulfill our needs as developers.

For me this makes more sense, cause there should be always a typedef, unless there is something telling you what it's the type of the function. And it would be configurable as well, because, maybe we want the initialized properties in a class to have inferrable type, but not for the call signatures for example.

It would be great if someone could pitch in to get this fixed. Until then, we are forced to choose between "not enough type declarations to be readable" versus "cluttered with too many type declarations".

This issue has been open since Oct 3, 2015 -- since then, my team has authored around 2000 TypeScript source files that are all cluttered with too many type declarations.

The typedef accepts a configuration. So maybe just another configuration just to ignore the type definition if the type is inferrable, meaning

Just type wherever is not being initialized.

It looks like because there is no clear consensus on how to deal with the issue, no progress is being made. Example comment: https://github.com/theia-ide/theia/issues/356#issuecomment-319350833

I suspect we all agree that any solution is better than leaving things as is. If you think any change to the status-quo with respect to this issue is good, please 👍 this comment. If you're knowledgeable enough to create a PR to fix this issue, please help all of us out ❤️ .

Would accept a PR to:

  • add an option to typedef that lets it ignore cases where no-inferrable-types says not to provide a type

The typedef accepts a configuration. So maybe just another configuration just to ignore the type definition if the type is inferrable, meaning 'Just type wherever is not being initialized.'

The typedef rule is for people who like having explicit type definitions in their codebase. If you desire the above behavior to "just type wherever is not being initialized", you're better off disabling typedef and making sure you have noImplictAny enabled as a TypeScript compiler option.

There's also the tricky case where some things that are initialized need a typedef anyways, as in the following snippet:

interface Literal {
    field: "value"
}

const literal0 = {
    field: "value",
};
const literal1: Literal = {
    field: "value",
};

const func = (obj: Literal) => { };
func(literal0);  // Error! Type 'string' is not assignable to type '"value"'.
func(literal1);

Also, while we get this fixed, wanted to mention that there are likely some great 3rd-party rules out there, like no-unnecessary-type-annotation(https://github.com/ajafff/tslint-consistent-codestyle/blob/master/docs/no-unnecessary-type-annotation.md) for example. If anyone knows of any other 3rd-party rules that give the desired behavior, please post them here and we can officially recommend them or adopt them into core if it makes sense.

@JKillian thanks for the recommendation, I think that's actually what I wanted. There is a very good post about avoiding any type: Don't use "naked any", create an "any interface" instead.

About:

interface Literal {
    field: "value"
}

const literal0 = {
    field: "value",
};
const literal1: Literal = {
    field: "value",
};

const func = (obj: Literal) => { };
func(literal0);  // Error! Type 'string' is not assignable to type '"value"'.
func(literal1);

I don't see how this could be an undesired behavior nor a tricky case. You want to make sure that obj have a property field with value value, and even when you are initializing literal0 with a property that seems like the constrains, you could modify that to another string.

I know is not a good use case, but most of the cases when you are using a literal, you probably want that literal, not a primitive.

I have the following configuration:
json "no-inferrable-types": true, "typedef": [true, "call-signature", "parameter"],
And this code:
javascript private static readonly DEVICE_UID: string = 'device_uid'; private static readonly DEVICE_PLATFORM: string = 'browser'; private static readonly AGENT_DEFAULT_ICON = 'http://localhost:3000/icon.png';

Why I'm not getting error in the two first declarations?

@sandrocsimas Interesting, but off-topic I think; AFAICT that problem's unrelated to this issue. I'd suggest you start another issue (fwiw!).

@estaub , yes I will. I'm getting the same behavior even without the typedef rule.

@sandrocsimas that's because it's a readonly property, and such Typescript infer its type as a literal. Typing it as a string you are telling that it should have a string, it does not necessarily will have that literal value and the value should not change statically.

It would be nice to have a 'require-typedef-except-inferrable' rule.

@FiretronP75 as @JKillian said, that's just noImplicitAny option of the TSC.

@michaeljota thanks, I didn't realize the noImplicitAny option of the compiler gives exceptions for inferrable. It still would be nice to have in tslint though, for the option of making it a warning instead of breaking compile, and for having the tslint comment flags.

I see why this would be something wanted, but having no-unused-variables as an example, I don't think use cases covered by the TSC are going to be supported by the TSLint team. I know that is not the same an _linter error_ than a _compiler error_ but at the end, they both are about written better code. Now days with solutions as Webpack or Parcel that allows you to compile and run the code even with TSC errors, I don't see this as a real issue.

Has this been fixed in the latest version?

I still don't think this is on the roadmap. You should consider using noImplicitAny from the TSC

☠️ TSLint's time has come! ☠️

TSLint is no longer accepting most feature requests per #4534. See typescript-eslint.io for the new, shiny way to lint your TypeScript code with ESLint. ✨

It was a pleasure open sourcing with you all!

🤖 Beep boop! 👉 TSLint is deprecated 👈 _(#4534)_ and you should switch to typescript-eslint! 🤖

🔒 This issue is being locked to prevent further unnecessary discussions. Thank you! 👋

Was this page helpful?
0 / 5 - 0 ratings