Check out a free preview of the full Enterprise TypeScript course:
The "tsconfig Strictness" Lesson is part of the full, Enterprise TypeScript course featured in this preview video. Here's what you'd learn in this lesson:

Mike discusses various TypeScript compiler options and their implications, such as noImplicitAny, noImplicitThis, strictNullChecks, strictFunctionTypes, and more. Fixing TypeScript/ESLint errors in a test file by specifying the correct path for the declaration file is also demonstrated.

Get Unlimited Access Now

Transcript from the "tsconfig Strictness" Lesson

[00:00:00]
>> Before we move on to working in the chat app, I just wanna talk a little bit about what we did in our tsconfig, cuz we kind of glossed over that. And I'm gonna move a little bit quickly here cuz I think we already covered some of these to some extent.

[00:00:14] But I'll sort of pause on ones that I feel are particularly interesting and if you have questions about what we can do in ts.config or what I think best practices are, this would be a great time to ask. And of course noImplicitAny of this forces any typed value to be explicitly typed as such.

[00:00:35] I think that early on in a, when you're converting a JavaScript project to TypeScript, this makes a lot of sense to relax, where you'd say, sure, I can have implicit anys just from the start. But generally you wanna end up in a place where this is switched on, meaning implicit anys are forbidden.

[00:00:54] And there's always an escape hatch here, right? You can always explicitly type something as any so there's never really a, something that's in your way from enabling this role. There's something safe you can do that will allow you to sort of turn this role on and so you can hold the line and stop any new implicit anys from being added to the codebase.

[00:01:15] noIimplicitThis we talked about this a lot in the In the intermediate TypeScript course, when we were dealing with this types, it just relates to functions that require a special this type. And the classic example here is, if you do add event listener, we have a button element, we're adding an event listener for clicks.

[00:01:36] Note that this type in this function type, it's the HTML button element itself. If in here we're like saying this.class name, if you're gonna rely on this being a particular thing, you need to be explicit about it. You're not allowed to just reach into this and hope that it is what you wanted it to be.

[00:01:57] Not something you need to worry about in class methods. It's taken care of for you there AlwaysStrict, we don't need to talk about that. That's just JavaScript's use strict. strictBindCallApply, this is really for just three methods on function, bind, call and apply, and it ensures that they behave as you would hope they would behave in a strongly-typed world.

[00:02:21] Strict null checks, this is a big one. Without turning this one on, you're allowed to use null in any TypeScript type. Null is an allowed value everywhere, which is dangerous, right? This is one of the most low-level things that you hope TypeScript takes care of for you, where you have confidence whether something's there or not.

[00:02:40] So I, when I'm converting a JavaScript codebase in TypeScript, I turn this on pretty early. It's one of the things that I'm most eager to sort of root out any violators of this and get some bug fixes in there. strictFunctionTypes, we talked about this a lot in the last chapter of intermediate TypeScript where we covered covariance and contravariance, without turning this on, function types do not behave as you would hope.

[00:03:08] They are treated in a bivariate way, which means if you have a callback type that needs to operate on a base class of something, and it should be able to handle all subclasses of that base class. Without turning this on, you can do things like this, where we have an animal and dog and cats are subtypes of animals.

[00:03:36] This here, I think I have strictFunctionTypes on here, but if you had it disabled, you'd be able to make this assignment. You'd say the function that takes in a dog and assumes it's a dog, can be used in place for a function that should be able to take any kind of animal.

[00:03:53] This is gonna be a problem, right? You're gonna start getting cats passing into this function. TypeScript will not have warned you that this is a thing. So it's specifically this kind of undesired flexibility that strictFunctionTypes helps catch. And if you wanna learn more, the last chapter of intermediate TypeScript, I think it's called type parameters and variants, goes into this in some amount of detail, and I think it's a really interesting concept.

[00:04:22] strictPropertyInitialization, so this is mainly class fields that you get yelled at if you didn't certainly assign these fields of value by the time the constructor finishes being invoked. Again, we cover this in intermediate TypeScript where we talk about a rare case where, you, in a promise executor, the callback you pass the promise constructor.

[00:04:47] If you are inside of that or initializing class fields on the outer class. That's where you'd use the definite assignment operator because that function is invoked synchronously, by the time you get the instance of the promise, that promise executor is for sure done invoking. But this is, very much related to that definite assignment concept.

[00:05:10] So I would call these, these are really, what do I call them? It's the basic, yeah, this is just sort of strict mode. This is when you say strict true, this is what you get. If we take things to the next level, we can have TypeScript be the thing in our project that warns us about unused local variables and unused parameters.

[00:05:31] I really like using TypeScript for this. You can set up ES lint to do it too. But it lets you have this convention where you can prepend something with an underscore. And TypeScript will say, all right, you know it's an unused parameter. And you've named the variable as such.

[00:05:47] And this is super easy, super important when you have a callback. Where you take the first argument, you ignore the second and then you take the third and you can just instead of doing ES lint disabled stuff which will, that I'll ignore the whole line. For a particular rule, this is just a very precise way of doing this and it's a well understood convention that spans multiple programming languages.

[00:06:11] Prepended_ means either private or I'm not using this. noImplicitReturns, this is not necessarily about being explicit about functions returning a specific type, but it's about all code paths returning nothing or something. So if you had a function you're calling and you had an if, and inside that if you return, I don't know, an array of numbers.

[00:06:38] And then, you just leave it there, right? There's no else. There's no return at the bottom. This would yell at you about that. You either have to return explicitly, even if it's just return with nothing after it, right? You're saying return nothing. You have to either do that on all code paths, or it's a purely side effect function that is never returning anything.

[00:06:59] This captures a lot of errors because sometimes you just forgot to do something. And whenever you're returning that's an indication that someone wants to consume what you're returning. And so often, this is a good chance to return a null instead of undefined, cuz you're it's not the absence of a return, you want something there.

[00:07:16] I told you about this one being disabled as my preference. I'm okay with fall through cases and switch. We can move on there. We talked about types, we talked about strip internal. exactOptionalPropertyTypes is interesting here. And what this stops you from doing is this. So on this object here, we have an optional volume property, which is a number.

[00:07:45] And we're setting it to undefined, which you would think you could do, right? You can certainly find an undefined value by accessing this property, if it's not set, right? It's optional. It could be undefined. But this is not a great practice because you're not getting rid of this property.

[00:08:06] You are providing it with a value, and that value is undefined. And so if you did, object.keys on this thing, it would say, yeah, I have something for volume or if you did object.hasOwnProperty for this. Do you have a volume property? It certainly does, and it's containing an undefined.

[00:08:25] This is the better practice if you really wanna get rid of something on an optional field like this. So I think this is a very common sense thing to turn on. Anytime you see this here, is a good indication you wanna move down here. But again, someone might be depending on that.keys having a particular key in there.

[00:08:44] This is not one that you could sort of say, I know for sure I'll never break anybody by making this change. You are changing some things but what I would tend to do is say I would give a prompt attention to any kind of patterns like this. Yeah, here are the violators.

[00:09:04] Yeah, also a let key in, a for in loop will also iterate over this volume property because something's there, right? noUncheckedIndexAccess, in a previous version of my TypeScript courses I used to say when you're operating on a dictionary type. Make sure you account for the fact that under a given key in a dictionary, you might find nothing, right?

[00:09:31] If it's a dictionary of address book entries, If you just type it as index signature:t, you're kind off saying that no matter what key you look up here, you will always get a type back, and that's just not the case with dictionaries. So I used to say make sure it's t or undefined.

[00:09:59] But we don't have to do that anymore. So what we can see here, we have a little bit of a combination of an index signature here with some known properties. So these will be present, for sure. Name, home phone, cell phone, but you could have some other loosely structured details about this phonebook entry, and they could be there or they could not be there.

[00:10:28] We don't know for sure. So you can see when we access name, we get the string. Office phone, we get string or undefined. So I don't have to put or undefined right here. There's a compiler setting that says whenever there is an index signature, no uncheckedIndexedAccess. The typescript will for you, indicate the possibility correctly that something might not be there.

[00:11:01] All right, this is a very related compiler setting that I think is quite nice. It forces you to use .notation like this, for accessing known properties, right? We know this is here, right? But it's gonna want you to right square bracket notation to access object office phone. And this, it's a great improvement to readability.

[00:11:32] So that you when you're just looking at this code, maybe you import that interface from another file and so you're not paying too much attention to what's the definitely here thing versus what's the little extra thing that could be there, could not be there. And if you have square bracket, you can do this to excess properties, right?

[00:11:52] You don't have to use .notation. You can just treat it like an associative array almost and pass the string in as the key. npImplicitOverride, we covered this in TypeScript fundamentals, the override keyword, it's relatively new to TypeScript. And it it sort of enforces that when you're trying to shadow a base classe's method, you must use the override keyword and this checks and catches all sorts of refactoring problems.

[00:12:23] Where you change the name of a method. But something else like you didn't change the subclass like this. You have the override keyword. This will, it'll catch it. And specifically what this rule does is it, whenever you are shadowing a method of a subclass, it will say, put the override method here.

[00:12:43] You should indicate you're overriding a base class, and then in the future, someone comes along and they change one or the other. And you're not in that shadowing situation again, where you have two methods of the same name on the base class and subclass, it'll bust you on it.

[00:12:59] It'll let you know that like, look, you should refactor both or neither. I talked a little bit about viral options, where if you're publishing a library, enabling any of these will effectively force all consumers of your library to enable these as well. These effectively let you check your types a little bit differently, but they will be the same types that were bugging you before when these settings were turned off.

[00:13:31] So your declaration files that your library spits out, it's quite possible they wouldn't have worked unless you turned these flags on. And then once you turn the flags on, those declaration files still have to work with every consumer of your library. That's what makes them viral. Meaning if one of your dependencies uses these flags and needs these flags turned on to compile every, you have to turn it on for your whole project.

[00:14:03] We talked about this in one of the early courses, right? This option makes it so that caught things are unknown, this prevents you from assuming that they're errors without doing an instance of check, which is great.
>> I'm getting a bunch of TypeScript slash ESLint errors in my editor in the test file now probably because we changed the stringify error to beta, how would you fix this?

[00:14:33]
>> Yeah that's a good question. I can show you exactly how I'd fix this. So let's verify that that's exactly what we're getting. Hey, look at that, no exported methods stringify error. This is because let's see if this takes me to the declaration file. It does now, these are the types that I'm consuming.

[00:14:53] This is the standard lib.d.ts file. So in this tsconfig, what I can do is say sorry, it's in the compiler options, paths. ''chat-stdlib'', I can say let's see. ['' ../dist/chat-stdlib-beta.d.ts''] Something like that. So this is what a beta consumer of your library would have to do. It would be a slightly different path, right?

[00:15:33] They'd be looking into their node modules folder. Be like Nodemodules//chat-stdlib/dist/give me the beta one, I wanna use the beta types for this. So what you're saying here is TypeScript shouldn't just try to be clever and resolve the types the way everybody else would get them. What you're saying is for this library, use that declaration file specifically.

[00:16:01] This is what I mean by explicit opt-in to the beta. I can tell there's no more red title here. There's my stringify error function coming through. So I've opted into the beta. This would be a great place where, say you wanna use the private stuff for your tests.

[00:16:17] But you have control over it. It's not just sort of you get everything. And then, I prefer to have my tests sort of, test a library from the outside. But through the beta surface would be fine. Gotta have tests for your beta stuff.