-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat: strict TypeScript configuration #2010
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: strict TypeScript configuration #2010
Conversation
@robertsLando : I already resolved many issues but this is taking some time to fix so I will continue in the upcoming days. There are a few cases where I elected to put an |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a few comments just to give more insight on what I did so far.
import { pipeline } from 'stream' | ||
|
||
const helpMe = help({ | ||
dir: path.join(__dirname, '../../', 'help'), | ||
dir: path.join(import.meta.dirname, '../../', 'help'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not strictly necessary but enables migration to ESM.
import publish from './pub.js' | ||
import subscribe from './sub.js' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Best practice to avoid confusion between folders and files, there are also other more specific requirements from Node.js and browsers if using .mjs
extension.
parsedArgs['will'] = {} | ||
parsedArgs['will'].topic = parsedArgs['will-topic'] | ||
parsedArgs['will'].payload = parsedArgs['will-message'] | ||
parsedArgs['will'].qos = parsedArgs['will-qos'] | ||
parsedArgs['will'].retain = parsedArgs['will-retain'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of these, and every access made that way can be easily resolved by using typeguards in a later PR.
interface WritevChunksParameters { | ||
chunk: unknown | ||
encoding: string | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I elected to create this very simple interface to limit cognitive load on function signature. It also simplified later typing.
@@ -62,11 +80,15 @@ export class BufferedDuplex extends Duplex { | |||
}) | |||
} | |||
|
|||
_read(size?: number): void { | |||
public override _read(size?: number): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The override
keyword is mandatory here to indicate we are overriding the behaviour of a method that was already defined in the parent class. This improves maintainability.
} | ||
|
||
private clear() { | ||
if (this.timerId) { | ||
if (this.timerId !== undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not strictly a TypeScript issue, but ESlint will complain if we use a strict configuration, as nullish comparisons can lead to problematic bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if timerId is null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
timerId
cannot be null
in this code.
It is declared line 9 as:
private timerId: number | undefined
It is then used from lines 50 to 55 here:
private clear() {
if (this.timerId !== undefined) {
this.timer.clear(this.timerId)
this.timerId = undefined
}
}
It's last reference is line 101 with:
this.timerId = this.timer.set(() => {
// this should never happen, but just in case
if (this.destroyed) {
return
}
this.counter += 1
// after keepalive seconds, send a pingreq
if (this.counter === 2) {
this.client.sendPing()
} else if (this.counter > 2) {
this.client.onKeepaliveTimeout()
}
}, this._intervalEvery)
The Timer.prototype.set
method can only return a number
:
This means timerId
can only be either a number
or undefined
.
// @TODO: this._keepalive may be undefined. This operation will result in NaN. | ||
const keepAliveTimeout = Math.ceil(this._keepalive * 1.5) | ||
|
||
this._keepaliveTimeoutTimestamp = Date.now() + keepAliveTimeout | ||
// @TODO: this._keepalive may be undefined. This operation will result in NaN. | ||
this._intervalEvery = Math.ceil(this._keepalive / 2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A dangerous code that was revealed by using a strict TypeScript configuration. You have two very clear cases where those properties can unexpectedly end up as NaN
.
// @TODO: @robertsLando We need to handle this situation as if data is null, Buffer.from(null) will throw an Error | ||
if (data === null) { | ||
throw new Error('') | ||
} | ||
|
||
if (data instanceof ArrayBuffer) { | ||
data = Buffer.from(data) | ||
} else { | ||
data = Buffer.from(data, 'utf8') | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here again a strict TypeScript configuration reveals a potential error that will be uncaught and thus bubble back to our end users code. I left this new Error('')
blank for now since I still need to understand better what to do in this situation. I'm not completely familiar with this code base.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not completely familiar with this code base.
The problem with this is I never had a way to test this out, the user using this are very few and sincerely I dunno if it works or not. Same for wx, when I did the TS refactor I just focused on converting it to TS without worry too much about it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a plan BTW to drop them and put them on a separated repo, same for pub/sub bin but giving my limited time I'm just focusing on fixing bugs for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries, every code base has problems, even if you have 100% coverage, 100% stryker score and 0 SonarQube issue you have problems that are not clearly identified. I'm just highlighting the potential benefits of a stricter configuration.
This is actually really positive as we can improve this part since we know it has a potential issue here. 👍
@@ -132,8 +132,13 @@ | |||
"@esm-bundle/chai": "^4.3.4", | |||
"@release-it/conventional-changelog": "^7.0.2", | |||
"@types/chai": "^4.3.20", | |||
"@types/concat-stream": "^2.0.3", | |||
"@types/debug": "^4.1.12", | |||
"@types/minimist": "^1.2.5", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could drop minimis in favor of nodejs parseArgs but maybe we can do that in another PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, just make sure that this does not break browsers implementation. I think there is a global dependency review to be done on the overall code. Let's add this to a roadmap if there is one. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just make sure that this does not break browsers implementation
Minimist isn't used on browser side but just on /bin
files that can only be run from nodejs :)
|
||
export default class KeepaliveManager { | ||
// @ts-expect-error - @TODO: @robertsLando: You have a situation where _keepalive can be undefined which will lead to cascading problems due to _keepalive being always used directly as a number. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could this throw if so?
@@ -584,7 +575,8 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac | |||
options.protocolVersion === 5 && options.customHandleAcks | |||
? options.customHandleAcks | |||
: (...args) => { | |||
args[3](null, 0) | |||
// @TODO: This would really need to be made easier to understand. It is difficult to know why the first parameter is Error | number. | |||
args[3](0, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if using 0 instead of null here may lead to some errors elsewhere? I would stick to undefined and fix the type here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. This section of the code is treacherous. I think the best course of action would be to refactor it so it is simpler to understand. I am trying to limit the changes I am making to the bare minimum to have a strict TypeScript configuration so the review is easier. I'll see what I can do make this more stable.
@@ -722,6 +718,7 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac | |||
* @return the auth packet to be returned to the broker | |||
* @api public | |||
*/ | |||
// @ts-expect-error - packet is unused. @TODO: This method is seemingly never called. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
never called? It's actually used AFAIK
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For some reason, VSCode failed to detect the call within auth.ts
.
After having a look, here is the full method declaration:
/**
* @param packet the packet received by the broker
* @return the auth packet to be returned to the broker
* @api public
*/
// @ts-expect-error - packet is unused. @TODO: This method is seemingly never called.
public handleAuth(packet: IAuthPacket, callback: PacketCallback) {
callback()
}
The only place I find it used is here:
client.handleAuth(
packet,
(err: ErrorWithReasonCode, packet2: IAuthPacket) => {
if (err) {
client.emit('error', err)
return
}
if (rc === 24) {
client.reconnecting = false
client['_sendPacket'](packet2)
} else {
const error = new ErrorWithReasonCode(
`Connection refused: ${ReasonCodes[rc]}`,
rc,
)
client.emit('error', error)
}
},
)
If we look at the code, we can see that both arguments err
and packet2
will both be undefined
as the handleAuth
implementation passes no argument to the callback
argument invocation.
From what I can see, the code that is really being ran is:
if (rc === 24) {
client.reconnecting = false
client['_sendPacket'](undefined) // packet2 is undefined so I replaced it by its constant value
} else {
const error = new ErrorWithReasonCode(
`Connection refused: ${ReasonCodes[rc]}`,
rc,
)
client.emit('error', error)
}
I haven't had a look at auth.ts
so I can't tell if this is intended.
@@ -734,6 +731,7 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac | |||
* @param Function callback call when finished | |||
* @api public | |||
*/ | |||
// @ts-expect-error - packet is unused. @TODO: This method is seemingly never called. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is never used but can be overridden by user in order to handle messages with backpressure on stream (instead of using message
event that doesn't provide backpressure)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I don't have the entire code in mind, I'll remove the @TODO
here since I can't really tell how an end-user would use this.
// @ts-expect-error - @TODO: @robertsLando this is a dangerous situation as this.options.password can never be a Buffer but was forced as a Buffer | ||
password: this.options.password, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know, there was a PR on mqtt-packet to fix this type don't remember right now if it has been merged or not but it was safe to force it to buffer especially because almost everyone passes it as string
if (this.options.clientId !== undefined) { | ||
connectPacket.clientId = this.options.clientId | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clientId should never be undefined, we generate a random one in case it is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lines 576 to 579 in 1720b64
this.options.clientId = | |
typeof options.clientId === 'string' | |
? options.clientId | |
: MqttClient.defaultId() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, but in that case, the corresponding interface IClientOptions
should reflect that and forbid the property from being undefined
.
@@ -1121,15 +1127,15 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac | |||
// force re-subscribe on reconnect. This is only true | |||
// when provided `topicObject` is `this._resubscribeTopics` | |||
let resubscribe = false | |||
let topicsList = [] | |||
let topicsList: Array<string> = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not string[]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a habit that I have, it's not even conscious at this point.
There is actually a good reason to this.
In TypeScript, []
can be used to defined two very different but related structures.
string[]
is equivalent to Array<string>
. This means an array of length 0 or more.
However, you can also use TypeScript tuples, which are specific arrays, for example:
[string, ...Array<string>]
which means : "An array of strings with a minimum length of 1".
I've seen many developers quickly get confused when getting this kind of typing (I was even slightly confused while typing it):
const parsedValue: [[string, ...string[]], [string, ...string[]]];
I usually write:
const parsedValue: [[string, ...Array<string>], [string, ...Array<string]];
This helps with readability and isolates []
use to tuples only to reduce cognitive load.
Sub-typed structures also have a common structure in TypeScript:
const bar: Record<string, unknown> = {};
class Foo<T> {
public setSomething(value: string): void {}
}
const myFoo: Foo<string> = new Foo<string>();
type FooSetSomething = Parameters<Foo<string>['setSomething']>;
const myMap: Map<string, string> = new Map<string, string>();
I find it more consistent to respect this syntax for every structure instead of creating an exception for arrays, especially if you end up with this kind of type:
const multiTypesArray: (number|string|string[])[] = [];
This is just an opinion. If you want to switch to string[]
just let me know, it's not a problem.
if (version === 5) { | ||
defaultOpts.nl = false | ||
defaultOpts.rap = false | ||
defaultOpts.rh = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this has been removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code was set here, and then immediately reaffected under the similar condition here:
if (version === 5) {
currentOpts.nl = reconciledOptions.nl ?? false
currentOpts.rap = reconciledOptions.rap ?? false
currentOpts.rh = reconciledOptions.rh ?? 0
// use opts.properties
currentOpts.properties = properties
}
So I simply moved it in one place. This resolved some typing issues since the interface indicates that nl
, rap
and rh
can be undefined
.
"module": "NodeNext", | ||
"moduleResolution": "NodeNext", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dunno why I didn't do it here but I usually extend node default tsconfig here, like https://www.npmjs.com/package/@tsconfig/node20.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Up to you if you want to use a base, I'm providing the strictest configuration I currently am aware of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Starting with the strictest config is not a smart idea imho, it will bring too many changes in a single PR. I'd proceed way more cautious since this library is literally used by millions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand the concerns. Then it might be impossible to switch to a strict ESlint configuration because there are hundreds of rules, which would take extremely long to implement rule by rule.
I'll start over with one rule at a time...
I'd proceed more carefully on this and on a step by step basis. Maybe initially just adding "noImplicitAny" rule and merging less changes, then adding stricter properties like strict step by step in different PRs. Also I'd change the "module" in a different PR. Just my thoughts. This lib is used by millions, this PR imho is waaaaay to big. |
We could do it that way if that makes people more comfortable. Let me know how we shall proceed and I will adapt the process accordingly. |
I just pushed a commit with many new issues fixed. I haven't cleaned up the previously discussed one for now, I'll do a complete clean up at the end to avoid having to go back and forth. 225 errors left to resolve. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I resolved many typing issues again and added some comments to help with the review.
this.outgoing[+messageId]?.volatile && | ||
typeof this.outgoing[+messageId]?.cb === 'function' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explicit but discreet cast to number.
@@ -1749,17 +1754,19 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac | |||
|
|||
if (topic.length === 0) { | |||
// restore topic from alias | |||
if (typeof alias === 'undefined') { | |||
if (alias === undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The syntax:
typeof x === 'undefined'
Is no longer useful as ECMAScript made the undefined
global symbol read-only many years ago. This was a necessity for everything prior ES5.
@@ -1799,6 +1806,7 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac | |||
if ( | |||
!this.disconnecting && | |||
!this.reconnectTimer && | |||
// @ts-expect-error - reconnectPeriod can be undefined, but changing it without prior refactoring may lead to problems. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here, what I think is happening, is that we want to use the same instantiation interface and definition interface, which creates these situations where we constantly have to check if anything is undefined.
// @ts-expect-error - This situation is really problematic. clone() returns a function, not an object, so this code needs to be looked at. Is this used and tested? | ||
storePacket = clone(packet) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very likely that this code is never ran as storePacket
ends up being the cloning function and not the cloned packet. clone()
returns the cloning function so the correct syntax would be clone()(packet)
. This is because the developers of this function used a purely functional approach. (which is not inherently bad but requires care when used to avoid this kind of situation)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure. The export of default
is:
module.exports = require('./index.js')()
So I think this is actually working
*/ | ||
private _sendPacket( | ||
public _sendPacket( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is definitely public, as it is called from other files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same for _onConnect
... dunno what's best to do
const messageId: number | undefined = storePacket?.messageId | ||
|
||
if ( | ||
messageId !== undefined && | ||
this.outgoing[messageId] !== undefined | ||
) { | ||
// @ts-expect-error - cb and this.outgoing[messageId].cb have incompatible types. | ||
cb = this.outgoing[messageId].cb | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I think that we should refactor all properties to actually use explicit (and not magic!) getters to guarantee types and avoid the perpetual pain of having possibly undefined values everywhere. It would considerably reduce the size of the code as well.
*/ | ||
private _onConnect(packet: IConnackPacket) { | ||
public _onConnect(packet: IConnackPacket) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely public as it is called from other files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah kind of, in fact we don't really want this to be called by users but we need to call it in our handlers, they used to be inside client.ts but I moved them away as I would like to split it as there was too much code inside that single class
const results: RegExpExecArray | null = | ||
/^(?<username>.+):(?<password>.+)$/.exec(opts.auth) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RegExp.prototype.exec
is the recommended way to use regular expressions nowadays. I also converted this to use named groups to make the intent clear.
// @robertsLando: This is a dangerous evaluation of brokerUrl as typeof null === 'object' will return true | ||
if (typeof brokerUrl === 'object' && opts === undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like this example in fact as it clearly highlights why I always ban null
in all code base that I work with except for JSON serialisation.
The following code:
console.debug(typeof null === 'object');
Will output true
.
This is because null
is not actually null. This means that it is indeed an object.
The two programming concepts you usually want to use is null value which exists only in the form:
const myValue: unknown = undefined; // This is a true reference to null value
And null reference, which can be observed that way:
interface ExampleInterface
{
possibleNullReference?: string;
}
const example1: ExampleInterface = {
possibleNullReference: "foo"
}
console.debug(Object.hasOwn(example1, "possibleNullReference")); // true
const example2: ExampleInterface = {};
console.debug(Object.hasOwn(example2, "possibleNullReference")); // false
Which is also why those two syntax:
interface ExampleInterface {
foo?: string
}
and
interface ExampleInterface {
foo: string | undefined
}
are not equivalent.
Additionally, you can even find even weirder situations such as:
const foo = Object.create(null);
foo.bar = "This property exists";
console.debug(typeof foo); // "object" so Javascript clearly identifies this as an object type
console.debug(foo instanceof Object); // false because this object does not have the Object prototype, it's a void-prototype object
console.debug(foo.bar); // "This property exists" => It is still a functioning object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep I know that and I agree about having more strict types for this reason
// @TODO: This is very heavy on the runtime, especially when working with CJS, as you may be living in a cache-busted environment which will result in significant IOPS because of the repeated require() | ||
// Consider either migrating to ESM, which is immutable and thus immune to cache busting, or simply requiring each file once. | ||
// Note: Those situations are very rare, but considering how widespread MQTT is within the IoT world, it might be worth considering | ||
if (protocols === undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, require
is actually a rather simple module import implementation based on a capsule.
A CJS module actually is something like:
((__dirname, __filename, module) => {
// Your CJS code
})()
The problem is that CJS is mutable because it is not built-in Javascript. So if you were to do this:
global.require = (path) => {
const result = require(path);
Object.keys(require.cache).forEach((key) => {
delete require.cache[key]
})
return result
}
Then the require
cache would cease to exist, which will result in an extreme amount of IOPS when constantly calling require
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
considering that this happens only on client creation I don't think it's a real bottle neck for performances BTW I'm open for improvements :)
// @TODO: Defaulting localhost has a few minor pitfalls. Notably because localhost triggers DNS resolution which might not map to 127.0.0.1 and create problems. | ||
scopedOpts.hostname = opts?.hostname ?? opts?.host ?? 'localhost' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW using 127.0.0.1 here may cause other compatibility issues I think?
if (rc === 0) { | ||
client.reconnecting = false | ||
client['_onConnect'](packet) | ||
client._onConnect(packet) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see my comment
if (done !== undefined) { | ||
done() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about assing noop to done when not defined? This would remove some useless code
@SmashingQuasar Giving the growing amount of changes this PR is introducing I agree with @DavideViolante that a better and safer approach could be to split this into smaller PR where in each one you enable 1/2 strict rules of TS till we reach our goal. |
Alright, though this is getting very painful for me as I will have to start over. I'll do it with the minimal number of rules possible to avoid having to redo the entire work again. |
Closing this PR to reopen it one rule at a time. |
Description
Following the discussion from #2006, this PR aims to implement a strict TypeScript configuration.