microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.16k stars 12.38k forks source link

Support @ts-ignore for specific errors #19139

Open ghost opened 6 years ago

ghost commented 6 years ago

TypeScript Version: 2.6.0-dev.20171011

Code

function countDown(n: number): void {
    switch (n) {
        // @ts-ignore
        case 1:
            console.log("1");
            // intentional fall through
        case 0:
            console.log("0");
    }
}

Expected behavior:

Ability to make ts-ignore apply to the --noFallthroughCasesInSwitch error but not to other errors.

Actual behavior:

case "1": would also compile.

RyanCavanaugh commented 4 years ago

Just to be clear, the issue here the future-upgrade scenario. Let's say you write something like this in hundreds of places around your codebase:

// @ts-ignore: TS2339, TypeScript is wrong about `href` not existing on `event`
const elementIdName = event.target.href.split("#").pop();

Then we improve the error message to be

 TS7632 Property 'href' exists on 'HTMLAnchorElement' but not 'HTMLElement'. Are you missing a type assertion?
 ^^^^^^

The error code is going to change because there's a 1:1 relationship between error codes and messages. Now what? A routine compiler upgrade that should only introduce a handful of errors is now giving you hundreds of errors with a tedious process for fixing your ignorecomments. That's really bad and we'd like to not paint people into that corner unless we absolutely have to.

You can fix everything with more tooling but at some point the cognitive burden doesn't outweigh the benefits.

dwelle commented 4 years ago

A routine compiler upgrade that should only introduce a handful of errors is now giving you hundreds of errors with a tedious process for fixing your ignore comments.

TS can always offer a codemod for upgrades that would take care of this, similar to React. Not ideal, but it's an option.

joebowbeer commented 4 years ago

@RyanCavanaugh If I had hundreds of ts-ignores in my code, I'd definitely want them to have TScodes associated with them, to restrict their error-hiding radius.

But in the few(?) cases where the.TScode kept changing, I might revert to a simple comment to avoid churn.

The situation seems analogous to eslint, where users are already accustomed to updating their ignores as the error types subtly change. Are ts-ignores fundamentally different from eslint-ignores in this regard?

vpanta commented 4 years ago

This sounds largely like the need for a perfect solution is preventing a good solution. I can think of multiple responses to your specific argument, but I don't really want to get bogged down in arguing on one specific thing.

Offhand, I've been through multiple migrations where they're much simpler this sort of one-off error squashing. Start with tooling to ignore the current errors so we can move forward writing new error-free code with the codes enabled. Is there a better path to enabling "strict" mode? Or say enabling new error codes in the future?

falsandtru commented 4 years ago

Probably this feature isn't implemented because of TS team's test process. TS team frequently tests TypeScript using DefinitelyTyped and some projects but if @ts-ignore for specific errors is used in them, TS team has to maintain these @ts-ignore comments. TS team hates this maintenance cost. This would be why @ts-ignore for specific errors isn't implemented.

FYI, I needs @ts-ignore for specific errors to ignore some language bugs in most cases:

https://github.com/falsandtru/spica/search?q=ts-ignore&unscoped_q=ts-ignore

A lot of bugs have still not been fixed. TypeScript hasn't provided any suitable solution to care these bugs.

NetOpWibby commented 4 years ago

Would it be possible for the team to ignore ignore comments? 🤔

doberkofler commented 4 years ago

@RyanCavanaugh I completely understand that there is no perfect solution (as @vpanta called it) but nevertheless I would very much prefer the option to only ignore specific errors. Like in eslint, a user can still decide if he wants to ignore all errors or just specific ones. I also believe that there most likely should be a reasonable compromise between completely changing all errors codes in each minor revision and not being able to change them at all. I believe that by trying to rather add new error codes instead of changing existing ones where possible, trying to combine the breaking changes in error codes to bigger releases and just documenting the changes should very much alleviate this problem.

RyanCavanaugh commented 4 years ago

Today: "Why let perfect stand in the way of the good? Just do something right away, I'm sure it'll be fine"

Tomorrow: "Yes, that new proposal would be more ideal, but we can't change the behavior now" / "Well why did you rush the feature instead of figuring it out first?"

The day after: "Why is this language so full of inconsistencies? It's like the designers just rushed something out the door to satisfy people. Pretty disappointing that they weren't willing to take their time to do it right."

doberkofler commented 4 years ago

@RyanCavanaugh I would agree with you on most design decisions but I think this is different because I don't think there is a "perfect" solution and silently ignoring possible errors does also not seem like the perfect solution. For this problem I would rather aim for a practical compromise.

NetOpWibby commented 4 years ago

Today: "Why let perfect stand in the way of the good? Just do something right away, I'm sure it'll be fine"

In all fairness, any decision made today or tomorrow wouldn't be "right away" considering this issue was opened in 2017...

The day after: "Why is this language so full of inconsistencies? It's like the designers just rushed something out the door to satisfy people. Pretty disappointing that they weren't willing to take their time to do it right."

Full? Are there really people here who sound like this?

cantiero commented 4 years ago

@RyanCavanaugh > there's a 1:1 relationship between error codes and messages.

It seems to me like this is the source of our problems. Is this something that can be changed?

thw0rted commented 4 years ago

Has anybody proposed solving Ryan's problem by maintaining a mapping of "deprecated" / "refined" / "OBE" error codes? (Remember folks, naming things is one of the Hard Problems. Sorry.)

In his example, before TS7632 existed, the code would have been marked with TS2339. So, capture TS2339 -> TS7632 somewhere, once, and in future if there's code with @ts-ignore TS2339 and the compiler spits out TS7632, it walks up the tree and finds an equivalence, so the line is still ignored. There might be cases where this doesn't work out exactly, but it could at least address some of the concern.

cantiero commented 4 years ago

My proposal is that an error dictionary is created. All errors would have an immutable number and mutable description. Any change in an error description would never break anything. No need for extra maintenance. The text editor would be able to use this dictionary to show a tooltip with the current error description on hover and that is it.

thw0rted commented 4 years ago

Ryan's example above isn't actually a change to an error, it's the introduction of a new, more detailed check that catches a subset of the errors that would previously have been caught. The critical thing is that the hypothetical TS7632 would not mean that TS2339 is completely obsolete. Consider

type Foo = { x?: number; }

type Foo2 = { y?: string; }

const f: Foo = {};
f.y = "hey";
f.z = true;

I assume Ryan is suggesting that, maybe in the future, the compiler will be smart enough to do string-similarity checks and give us a "did you mean?" style error. In that case, the f.y line could be marked with "Did you mean Foo2?", but the f.z line would still get the old error (TS2339) because you're simply referring to an unknown property.

That's why I suggested a mapping that captures this information. The new error message (TS7632) would have shown up as a more general message (TS2339) in a previous TS version, so knowing that means that @ts-ignore TS2339 should also always ignore TS7632. Of course, such a system could also work if a new error message number / string did completely replace an older one.

cantiero commented 4 years ago

@thw0rted My impression is: if a more recent TS version is really catching a new kind of error, that was previously no cought because of an oversight or language limitation, it should never be suppressed by old error codes being ignored. IMO that is the whole point of this feature rquest.

Your example is not really a new error, it is an augumented way of dealing with an old error. I undersntand that, the way things work now, it would be required to use the new error code TS7632, thecnically making it a new error. But, this is not the true nature of this thing.

Would it make sense to separate them into what is an error and what is a smart suggestion to solve this error? In that case, we would have preserved the original error TS2339, which is not in fact obsolete. And there would be a list of possible smart suggestions for each error code.

ethanresnick commented 4 years ago

@thw0rted I believe what you’re describing is what I proposed here, though I confess I haven’t followed this thread super closely of late. I think it addresses @RyanCavanaugh’s upgrade issue

thw0rted commented 4 years ago

Pretty much, Ethan. Hah, I even thumbs-up'd the old post back in October! The only difference is that I was imagining storing the "parent" or "alt" information as a tree -- it could be thought of as "ancestry".

I think this still makes the most sense. If all current errors are leaf nodes, then you can walk up to the root of the tree and each visited node is an error that the same line of code would have gotten under some previous version of the compiler. Storing it this way instead of as a flat array might be adding complexity for very little benefit, though.

nojvek commented 4 years ago

@RyanCavanaugh if I understand correctly, your main issue is with the fact that TS could give a different error code for an issue in the future causing upgrade pains? However every TS version is already assumed to be a breaking change, so why not give users the ability to choose how much the ignore radius should be?

AFAIK the proposal is the following

@ts-ignore(?<comment> .*)? - Behavior remains as is Ignores the error on next line, if there is no error, tsc doesn't complain. @ts-expect-error(?<comment> .*)? - Behavior remains as is. If there is any class of error on next line, error is ignored. If no error, then tsc says Unused '@ts-expect-error' directive.

New proposal @ts-expect-error (TS\d+): (?<comment>.+)? - Expecting a specific error and a reason why it's ignored. If in future there is a different error code, then user has to explicitly acknowledge that and change it.

Basically we're saying, let us decide how strict we want our ignore radius to be. In some projects, going yolo @ts-ignore makes sense, in some projects we want to tightly control ignored errors and slowly work on reducing the number of ignored errors. Having a smaller ignore radius is a big boon.

Every TS version upgrade is already a breaking change. There has been exactly 0 times, where a TS upgrade has just worked for me for our 100k+ lines codebase. So if you're fear is introducing more errors for a version upgrade, this hasn't been a big concern for us personally. Fixing @ts-expect-error issues with a new TS error code is a simple find replace. That's an easy class of errors to fix. I actually want to know when that happens and explicitly fix each one rather than it happening transparently.

Also this issue is something I feel strongly about and would be happy to champion a PR if Typescript core members agree it's something they are willing to accept.

vegerot commented 4 years ago

This is something that would highly valuable to us. Having a ts-expect-error should always be a last resort, but if it comes to that then I would at least like to limit the scope of it as much as possible.

RyanCavanaugh commented 4 years ago

@nojvek These things are all true qualitatively, but there is a quantitative aspect that needs consideration. We totally expect that most very large codebases will see a dozenish breaks during version migrations, and that the maintainers of those codebases look at the breaks and mostly say "Yeah I can see how that was sketchy".

So if you're fear is introducing more errors for a version upgrade, this hasn't been a big concern for us personally

The reason you haven't had this problem is precisely because we've avoided adding features (like this one) that would create that scenario. I don't know what more to say on that; not getting wet in a rainstorm is a bad reason to throw away one's umbrella.

Here's a scenario that is entirely foreseeable:

You're acknowledging the danger of this feature, saying that you accept the danger, and therefore there's no problem with the feature existing. The problem is, we're not just adding the feature for you, we're adding it for everyone, and there's no way to put a EULA sticker on this that says "FYI, version to version upgrades are totally YOLO if you use this". They're going to see it on Stack Overflow, stick 40 different ts-specific-ignores (which feel "safer" than a blanket ts-ignore) in places where they should used a tactical any instead, get burned on version N+1, and complain about how TS sucks at versioning and broke their app with no warning.

The downstream effects of "I accept the danger" thinking can already be seen with ts-ignore: people misapprehend what ts-ignore does, thinking it will somehow appear in declaration files. Or that they can use it to ignore TS telling them about a problem that will prevent correct emit. ts-ignore is a "last resort" feature and I would really argue that any line of code for which the ignored error is not immediately apparent by syntactic form alone is a red flag. There's little upside IMO to expanding its use or encouraging it to be more brittle unless there are use cases that simply can't be solved other ways.

nojvek commented 4 years ago

Proposal: Having an opt-in way to limit ts-expect-error radius by specifying exactly which error to ignore.

If I understand correctly, the summary of your viewpoint is that TS team thinks that providing specific error code ignores is not a good future-proof solution. It should not be exposed to the user at all. Since it will regress the upgrade experience and TS team doesn't want to paint itself into a corner.

That's a reasonable argument. Ultimately you are the guardians of TS so it's your call (even if some of us may disagree). I appreciate the explicit clarification.

vegerot commented 4 years ago

@RyanCavanaugh Then why have @ts-expect-error at all? If we don't want people abusing things then why were per-line @ts-ignores added back in 2.6 instead of having only global no-checks?

I admit the second example is a bit contrived; but for my first question, the documentation for ts-expect-error seems to be in direct conflict with the (good) points you were just making.

I feel conflicted, because I agree with the points you're making, but agree with the points made in the 3.9 documentation, and would be valuable to our team.

@RyanCavanaugh how would you respond to these excerpts:

Unfortunately if our tests are written in TypeScript, TypeScript will give us an error! ... That’s why TypeScript 3.9 brings a new feature: // @ts-expect-error comments. When a line is prefixed with a // @ts-expect-error comment, TypeScript will suppress that error from being reported; but if there’s no error, TypeScript will report that // @ts-expect-error wasn’t necessary.

Given this logic--that it's better to say expect-error than ignore in this type of test--then why isn't it EVEN BETTER to specifically say which error you're expecting?

Pick ts-expect-error if:

  • you’re writing test code where you actually want the type system to error on an operation

By adding a specific error code, instead of just blindly ignoring the line, the error can be PART of the test itself.

In addition, many of the points that you bring up (that I agree with) are similar to the ones given in this article for picking ts-ignore:

Pick ts-ignore if:

  • you have an a larger project and and new errors have appeared in code with no clear owner
  • you are in the middle of an upgrade between two different versions of TypeScript, and a line of code errors in one version but not another.
  • you honestly don’t have the time to decide which of these options is better.

You're right that a danger of adding specific errors is that you will use it to squash random errors in a codebase (point 1). Another danger you point out is how this can be abused and bite people in the future when updating (point 2).

From what I gather, having specific error codes amplifies the benefits listed in "Pick ts-expect-error if:", and exacerbates the problems in "Pick ts-ignore if:".

Just wondering what you think about this. Also, I'm not going to go through the initial expect-error PR threads, but during these debates were you on the side of not adding expect-error at all?

RyanCavanaugh commented 4 years ago

Also, I'm not going to go through the initial expect-error PR threads, but during these debates were you on the side of not adding expect-error at all?

Consider the fact that ts-ignore doesn't error on a "missing" error, though we could have easily made it do so. This was a very big discussion at the time we added that feature, with very similar characteristics to this one, and ultimately we went in the direction that would minimize upgrade pain. I think adding ts-expect-error before ts-ignore would have been a huge mistake; doing it in the order we did, with a long gap between, cemented ts-ignore as the "default" way to suppress an error, and then later having ts-expect-error meant it was fairly clear that you were opting in to a very specific behavior with reasonably foreseeable caveats.

I don't see the same analogue here, because it's not at all obvious that error codes are not necessarily stable between versions. C# has to do a ton of backcompat engineering to make sure their warnings are consistent version-to-version and we don't want to do that (though people are already speculating about how we should implement it). If we add this feature, we are guaranteed going to stoke ire from engineers who go all-in on it without realizing this, and there's nothing I see that can prevent it.

Maybe that future ire is worth it for the sake of whatever people are experiencing here, but frankly I have not seen the evidence for what kinds of problems people are encountering that this feature would solve better than other existing solutions. People just keep going in circles asking why it doesn't exist.

I get that it feels bad to ts-ignore some line of code that might acquire some additional error in the future, but it's instructive to read the original ts-ignore thread (which we also pushed back on for a long time) where 95% of people said they "needed" the feature for suppressing some particular error that they could have suppressed with a more tactical as any. We added ts-ignore with the intent that it be used for the remaining 5% that can't be suppressed by any existing type system mechanics; in this light I think the concern about oversuppression is somewhat misguided because there should be very very very few ts-ignores in your codebase, and because so few constructs require a ts-ignore, it should be very unlikely that they acquire additional errors later. I can't reason people out of their worries but we have to weigh "dev X will sleep better at night" vs "dev Y is facing an error cliff during upgrades because dev Z told them that they should always ignore specific error codes instead of using as any"

doberkofler commented 4 years ago

@RyanCavanaugh As a possible use case for this feature. I'm responsible for a pretty large (~100000 loc) project. During our incremental migration from JavaScript to TypeScript we had to use ts-ignore quite a bit and sometimes (by ignoring a complete line) we missed a problem that was accidentally hidden because ts-ignore ignores all errors. The option to restrict ts-ignore to the specific error that "must" be ignore could have prevented quite a few problems during the migration.

vegerot commented 4 years ago

@RyanCavanaugh Very concisely written, and as someone that is only just thinking about this problem, you illustrated the history of these features in a way that really helped me understand where you're coming from. Thanks!

vegerot commented 3 years ago

@denissabramovs That's not very helpful. Check out the thread starting here for some rationale

macdaj commented 3 years ago

@RyanCavanaugh I get the perspective of backwards compatibility and maintenance of old code being a burden.

In my specific case I'm running into a problem related to immer losing symbol keys which I believe is caused by keyof removing well known symbols. For this case I have to use a ts-ignore statement. I think adding some compiler options would really help because ideally I want to remove these ts-ignore lines once they are no longer needed, or in a perfect world pay attention to different ts exceptions that pop up on the ignored line.

I think for ts-ignore having an explicit opt-in typescript compiler option that would throw an error if there is no typescript error on the line would be useful.

I think the same approach where specific error ts-expect-error-#### statements would fall back to the general ts-ignore would work. You can have a compiler option which specifies whether you want the fallback (by default it is true), effectively making ts-expect-error-#### be an explicit opt-in.

Even if you don't opt into either of these behaviors you have the ability to periodically run a build with these rather than checking each line manually.

ghost commented 3 years ago

Personally, I find entire TSC errors to be, without explicit rationale behind them, 100% useless. Consider TS18022, why is that even an error? Or TS2775. Or TS18019. I legitimately had made a class that used a static, private class method, that asserted and determined whether or not something was genuinely from that class.

Oops, I broke at least 3 TS rules at once :) No static private, no private methods, no assertion object methods. None of these are poor coding practice, they aren't inherently unsafe, they can be statically analyzed, but they are denied by TS, which prevented me from porting my JS to TS.

There are plenty of errors that are just that, errors, they should make someone stop and think about whether what they had just written was unsafe, and rewrite it, but the "errors" that I would like to disable en masse fall into a whole different category outside of safety and linting, into the area of "why is that an error in the first place?"

thw0rted commented 3 years ago

For both TS18022 and TS18019, your linked issue points out that private methods only reached "Stage 3" quite recently and TS hasn't gotten around to supporting them yet. Likewise for other up-and-coming ES private features (static #foo, etc). The good news is that TS legend and all-around swell guy @dragomirtitian says in https://github.com/microsoft/TypeScript/issues/39066 that support is inbound soon.

For TS2775, the linked issue (and a PR that is linked several times from there) make it pretty clear that there's a design limitation for control-flow analysis where you have to basically "re-type" the assert function when it's imported. (I agree with what appears to be the general consensus that the error message could be clearer, but at least searching for that error number brings you to those discussions.) This is irksome but not insurmountable.

ghost commented 3 years ago

For TS2775, the linked issue (and a PR that is linked several times from there) make it pretty clear that there's a design limitation for control-flow analysis where you have to basically "re-type" the assert function when it's imported. (I agree with what appears to be the general consensus that the error message could be clearer, but at least searching for that error number brings you to those discussions.) This is irksome but not insurmountable.

I think I'm experiencing a different error now that I look at it; I already know that reassigning an assertion function requires an explicit type, but what about direct calls?

let azzert = new Assertion().assert;
azzert(foo);

vs

new Assertion().assert(foo);

my particular case looked more like:

class T {
    static #assertIsT(x: unknown): asserts x is T { ... } // error

    method(x: unknown) {
        T.#assertIsT(x); // error
        ...
    }
}

but the real problem here is the lack of static private support and is the only reason that it can't be typed correctly.

For both TS18022 and TS18019, your linked issue points out that private methods only reached "Stage 3" quite recently

Well... those issues have actually been open for quite some time, 10 and 11 months respectively. That means in a month or two, private methods and private statics will have been at stage 3 for at least a year.

But, in reality, the numbers are a bit different: Proposal name Date that it reached stage 3
proposal private methods Sepetember 2017
proposal static class features May 2018
proposal class fields July 2017

An average of three years since they reached stage 3; these are bound to reach stage 4 soon. And, to top it off, Chrome already ships a full implementation, by default (e.g. no experimental flags).

Also, there are errors like TS2376 and TS17009, which seem... like they fulfill the same role, why does one exist, if the other does?

class T extends H {
     foo = ...;

    constructor() {
        const x = bar();
        super(x, x); // TS#### : "don't do that"
        // do I do this? // super( bar(), bar() );
    }
}
soullivaneuh commented 3 years ago

I am not a Typescript expert, so I'll not add arguments of the previous debate.

However, I would like to express the need of this granularity with an example.

I use react-navigation that allows us to declare routes with Typescript:

export type MyStackParamList = {
  Index: undefined;
  Show: {
    id?: string;
  };
  Edit: {
    id: string;
  };
};
const Stack = createStackNavigator<RidesStackParamList>();

You may define multiple stacks like that, but I will not going to details here.

Using the navigation to change the current route is supposed to be limited to the current stack, so if I do:

navigation.navigate('Payment');

Typescript will throw error TS2345 because the Payment parameter does not match the related MyStackParamList but an another stack.

However, this code part works like a charm. You can navigate to an another route of an another stack without issue

Is it me wrongly using the library? Maybe, surely. It's classified.

However, supposing it's an issue related to the vendor type definition, I can do anything waiting the fix than ignoring the error.

But as @andy-ms said, I would like to be sure only this error will be silenced and avoid possible other mistakes on the same line.

If specifying errors to ignore is dangerous according to some people here, what are the alternative for this case?

Thanks for reading

arthur-clifford commented 3 years ago

The only actual argument, if I'm not mistaken, is the instability of the error codes.

A couple thoughts 1) I really hope your team did something to standardize error codes between versions; the fact that there was any resistance to that idea is itself disturbing. It is the more forward compatible option that gives the most flexibility in the long run even if you end up with a bunch of unused codes. It may be hard, but who cares about hard; if we wanted easy we'd all be using square space and not coding our own stuff in typescript. 2) There have been very compelling arguments for the utility of refined expect-error or ts-ignore entries. 3) noIplicitAny and other tsconfig linter options are already examples of this capacity except they use names rather than codes; the advantage of the codes is that they can be linked to definitions that are internationalized.

All we are asking for is the ability to ignore those errors that we deem to be more style than error especially when the code actually runs, yet not have to not turn everything off.

The general reply in effect is that it is a bad idea to turn off errors but if you have to then turn them all off which makes no sense at all whether at the global or individual line level.

What would the actual level of effort be to implement ignorable/expectable error codes at the comment AND tsconfig level for consistency?

If it were implemented, would the folks in this thread be ok with the ts compiler generating a report of ignored errors that could in some way (via opt-in program) reported back to the ts dev team so that they can have some real world feedback on what all is getting ignored?

My problem with suggestions like "we are not seeing issues with ..." is that you aren't looking at any real code. You are waiting for the rarified people who know enough to complain, and with enough time to, to actually complain. To make those kind of arguments you need real feedback but if you are making it so we can't know what the errors we're ignoring are, except all of them, then how can you possibly know that your errors are useful or what the consequences of your choices are?

I'm thinking there is an opportunity for a meaningful feedback loop and a win-win if you let us codify our overrides inline with some metric info going back to you rather than have to come here to complain about things,

// @ts-ignore TS/1234 because I know what I'm doing Seems a lot more reasonable than // @ts-ignore developers Which leads to // @ts-expect-error typescript devs do not care so neither should we

doberkofler commented 3 years ago

@arthur-clifford could not agree more

nojvek commented 3 years ago

The other big argument is error code specific ignores on a single line.

Suppose you want to ignore two specific errors on the same line, you should be able to // @ts-expect-error TS123, TS2345 only those two kinds of errors. If at a future date, a new error pops up on the same line but of a different kind, you don't want to have that ignored.

Having a more granular "hold the line" functionality would be a massive boon.

rluvaton commented 3 years ago

I'm really looking to this feature and it really important IMO

(I'm sorry if it's already been said...)

[...] Suppose you want to ignore two specific errors on the same line, you should be able to // @ts-expect-error TS123, TS2345 only those two kinds of errors. [...]

The problem with using error code numbers (e.g. TS123) is that the reader is not able to understand from the code what the error we're expecting, I think an ESLint type of ignoring errors would be much more user friendly (e.g. // eslint-disable-next-line no-alert, quotes, semi)

hsalkaline commented 2 years ago

Another case where disabling specific error is useful is refactoring large codebase. Imagine we want to set noUncheckedIndexedAccess to true in our tsconfig. And turning this flag on reveals ~1.5k errors. Fixing that much errors in one PR is hard and dangerous. Possible solution is to mark all errors occurrences with @ts-expect-error with specific code (which is safe to do and can by done with a script, not manually) and then refactor errors iteratively.

somebody1234 commented 2 years ago

i know im in a tiny minority here, but i (try to) have some sanity types to enforce relations (?) between various variables/types etc.

since they have zero use, they always trigger ts(6196) aka type declared but never used. and of course, they're there so they throw a type error if the relation is violated. given the yellow highlight of the warning, it makes it extremely difficult to figure out whether i've fixed all the useful warnings.

benkeen commented 2 years ago

Adding my voice to the choir here. I inherited an old, large TS codebase where at some point someone decided to suppress the errors in the webpack build. With > 4,000 TS errors that spring up after fixing it, a single "Big Bang" PR to fix them all is unrealistic. I assumed I could just write a script to funnel the newly-identified errors identified from the linter into a script to add file-level @ts-ignore's, thus suppressing the specific errors in each file, then we could tackle the monstrous job of fixing all the problems piecemeal afterwards. But without the option to ts-ignore specific errors I find myself scratching my head...

joebowbeer commented 2 years ago

@benkeen to be clear, neither @ts-ignore or @ts-expect-error operate at the file level. @ts-nocheck does.

If @ts-expect-error accepted one or more tsErrno tags, as so many of us are hoping for, then you could parse the eslint output and precede every offending line with an expect-error comment.

By the way, not to make light of your situation, but disabling all the strict checking might reduce your issue to one that is easier to manage.

langri-sha commented 2 years ago

I really like that Flow forces me to ignore specific errors:

< // $FlowFixMe[incompatible-variance]
< // $FlowFixMe[incompatible-type]
< // $FlowFixMe[prop-missing]
> // @ts-ignore: Uhh.
foo(bar)

Slightly unrelated, but would be better if errors could match offending lines more closely:

> // @ts-ignore: Can't assign number.
process.env = {
  FOO: 'bar',
<  // $FlowFixMe[incompatible-type]  
  QUUX: 1000,
}
stdedos commented 2 years ago

For this feature, if it wasn't thought/said before, I would be expecting something like // @ts-ignore TS1801 to ignore only a specific warning.

I also don't like the @ts-expect-error wording; it feels like I am writing this in tests. I'd prefer, instead, to have an option to make @ts-ignores act like @ts-expect-error instead.

JonasDoe commented 2 years ago

Another use case where I actually want warnings, but not all of them:

export function notEmpty<TValue>(
  value: TValue | null | undefined
): value is TValue {
  if (value == null) return false;
  const testDummy: TValue = value; // for compile warning
  return true;
}

I want a compile warning for the type check, but I don't want a warning for it being unused. Yes, I can add a line like testDummy = testDummy to make the compiler happy, but 1) that's an extra operation, 2) it bloats the code (a bit) and 3) my IDE is now complaining about the self-assignment.

thw0rted commented 2 years ago

Jonas, what you want is a "type expectation". There are a number of libraries that provide an expectType function or a comment-based directive to generate an error when the type of the passed/decorated variable is not what you wanted. Look at

https://github.com/microsoft/dtslint https://github.com/SamVerschueren/tsd https://www.npmjs.com/package/eslint-plugin-expect-type?activeTab=readme

etc

sarimarton commented 1 year ago

I'm repeatedly coming back to this issue. @ts-expect-error doesn't make sense without specifying the error. I don't mind that @ts-ignore can't be narrowed down, eslint's default rules prohibit @ts-ignore anyway. The problem with @ts-ignore is the implicit cleanup condition. But @ts-expect-error makes a lot of sense a lot of times with narrowing it down to one specific error. Eslint expects me to add a comment as well. But expecting any error makes the flag unsafe. So what's the point then?

Having said this, all the places where I see a reaction from a TS representative, I see wrong arguing. Focusing on @ts-ignore, bringing in short names (why?)...

"We don't want people to get into a situation where upgrading from TS X.Y to X.Y+1 yields hundreds of new errors simply because we changed an error message code"

I see 3 problems with this (at least):

  1. It's arguing with extremes. Having hundreds of @ts-expect-error on one particular error type in a codebase feels like a fishy case, but quite uncommon at least. And even then, I don't see a problem with search-and-replacing those comments. Also how common is that TS changes an error code for the same error condition? Has it even ever happened?

  2. Also, narrowing down @ts-expect-error is the developer's responsibility with all the consequences.

  3. It's indeed counterintuitive that error codes are tied to the error text. Why is that? Nobody expects that errors are defined by their text. Everybody treats the text as prone to being changed over time. That's why there's a code. On the other hand, introducing a new code for the same error already adds inconsistency to the global TS knowledge - think about googling new error codes not matching the results for the old one; and then maintaining a code history on all the blog posts and help pages on the internet, which explains a particular error...

ThiagoMaia1 commented 1 year ago

Following

jove4015 commented 1 year ago

Just chiming in on this because I'm struggling to believe this doesn't exist already.

In python it's really simple, you just do:

some.variable = 1 # pylint: disable=no-member

Sometimes you just have to.

Over here in TS land, I'm working with a library (Prisma) that's really loose with strictNullChecks and it mixes undefineds and nulls everywhere. My code doesn't, but I can't control everything that happens in this library (without forking it and forever cordoning off my code from the rest of the internet). I also only get type errors in my unit tests when mocking dynamically generated functions, which is really not a top priority for type checking, especially when my tests work correctly and all my own code is doing exactly what it's supposed to do.

I don't want to disable all type checks in my unit tests. Just strict null checks, and just on specific lines where it can't be avoided.

It's unfortunate that the TS community thinks I should have to disable all type checking in the whole file over this. Seems like purity wanting for purpose.

thw0rted commented 1 year ago

Is there an open issue with Prisma about the "looseness" you describe? I just started using Prisma with Typescript and this hasn't bitten me yet. I have to believe there are better approaches to handling whatever null-ish values it throws around than just throwing in ts-ignores everywhere. If nothing else, maybe Prisma would take a PR to improve this behavior?

ETA: an example would be really helpful, maybe you could set something up on replit?

jove4015 commented 1 year ago

I'm not ust "throwing in ts-ignores everywhere". I'm putting them in 3 specific locations where problems occur. The problem happens due to an interaction between TRPC and Prisma - particularly where using zod's optional() behavior (for example, when you expect a type of z.number().or(z.array(z.number()).optional(), and an undefined value gets passed to a through this to a prisma method, it tries to coerce it to null. I haven't opened an issue, or bothered to spend an hour making a PR and/or a fix, because it frankly is not worth my time, and it's not my project.

Like I said - the purity of not having to do this is not worth it. Whether or not I opened an issue or wasted my time on a reproduction repo is not the issue. The issue is, I should be able to ignore a linter error when I know it's not relevant. The linter is there to help me, not hinder me.

thw0rted commented 1 year ago

I'm not trying to be overly pedantic here, but Typescript isn't a "linter", it's a type system. I asked for an example because, when you ts-ignore an error, 9 times out of 10 what you're doing under the hood is basically an any-cast -- you tried to do something that the type system doesn't allow, and by ignoring the error and doing it anyway you're telling TS not to enforce types on that line.

JonasDoe commented 1 year ago

... and if we could specify the kind of error to ignore, there would be some unwanted "any" casts less, right?