-
Notifications
You must be signed in to change notification settings - Fork 16
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
Add rules that require type information #250
Conversation
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.
Below is an explanation of the rules I've added, and the reasoning behind some of them.
'@typescript-eslint/no-unsafe-argument': 'off', | ||
'@typescript-eslint/no-unsafe-assignment': 'off', | ||
'@typescript-eslint/no-unsafe-call': 'off', | ||
'@typescript-eslint/no-unsafe-member-access': 'off', | ||
'@typescript-eslint/no-unsafe-return': 'off', |
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 have to do with the use of any
. While I strongly prefer not using it, we do use it in some areas, so these rules are turned off.
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 would really like to enable these rules, but saving them for a future release might be a good idea anyway, to reduce the disruption these other rules will cause. I've created an issue to track this: #251
packages/typescript/src/index.js
Outdated
'@typescript-eslint/no-unsafe-call': 'off', | ||
'@typescript-eslint/no-unsafe-member-access': 'off', | ||
'@typescript-eslint/no-unsafe-return': 'off', | ||
'@typescript-eslint/restrict-template-expressions': 'off', |
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 rule prevents the use of non-string
types in template expressions. We make use of this in certain places, like key-tree
using bip32:${number}
for derivation paths.
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.
Hmm, interesting. I like the idea of requiring explicit type casting (e.g. bip32:{String(number)}
). It's not too inconvenient, and can catch cases where you don't realize it's not a 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.
The thing is that we only want to accept numbers in those cases.
type PathSegment = `bip32:${number}`;
const foo: PathSegment = 'bip32:foo'; // Invalid
const bar: PathSegment = 'bip32:123'; // Valid
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.
Ohhh I misunderstood, that was a string template type not an actual string template with a variable called number
.
Yeah that's a great point. Agreed with turning this off.
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.
Is it actually used for complex types, or only primitives? If the latter, maybe consider allow-number
etc so we can eat the cake and have it without [object Object]
s?
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.
Primitives and other types like this:
type Hex = `0x${string}`;
type Foo = `foo-${Hex}`;
I'm not sure how this rule handles the second case, I'll give it a try.
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.
Ah, I misunderstood the rule slightly. I think it's fine if we use allowNumber
, allowBoolean
, maybe allowAny
?
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.
Hmm. Would we benefit of allowAny
? The other two make sense.
Though either way, having this rule enabled with allowAny
would still be an improvement compared to today, so I don't feel strongly about us not using allowAny
.
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 vote for allowNumber
and allowBoolean
. Those make the most sense to me.
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.
Removed allowAny
.
'@typescript-eslint/restrict-template-expressions': 'off', | ||
|
||
// Our rules that require type information | ||
'@typescript-eslint/consistent-type-exports': 'error', |
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 for debate if we want to enable this one. It enforces import type { ... }
when importing types. If there are multiple imports from the same location, the imports would be split up in two, e.g.:
// foo.ts
export type Foo = string;
export const bar = 'baz';
// bar.ts
import type { Foo } from './foo';
import { bar } from './foo';
I think personally it makes it more clear if something is just a type or not.
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, this seems great to me.
packages/typescript/src/index.js
Outdated
'@typescript-eslint/naming-convention': [ | ||
'error', | ||
{ | ||
selector: 'default', | ||
format: ['camelCase'], | ||
leadingUnderscore: 'forbid', | ||
trailingUnderscore: 'forbid', | ||
}, | ||
{ | ||
selector: 'enumMember', | ||
format: ['PascalCase'], | ||
}, | ||
{ | ||
selector: 'variable', | ||
format: ['camelCase', 'UPPER_CASE', 'PascalCase'], | ||
}, | ||
{ | ||
selector: 'typeLike', | ||
format: ['PascalCase'], | ||
}, | ||
{ | ||
selector: 'interface', | ||
format: ['PascalCase'], | ||
custom: { | ||
regex: '^I[A-Z]', | ||
match: false, | ||
}, | ||
}, | ||
], |
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.
Also up for debate. This enforces consistent naming for different kinds of identifiers. Currently it's configured ilke:
- By default we use
camelCase
. This includes things like function names, args, etc. - Enum members use
PascalCase
, e.g.:enum Foo { Bar, Baz }
- Variables can use
camelCase
,UPPER_CASE
, orPascalCase
. Reasoning for this is that in certain cases we want to useUPPER_CASE
, like for constants, and in other cases we want to usePascalCase
, like for structs. - "Type like"-identifiers use
PascalCase
. - Interfaces use
PascalCase
, and cannot start withI
.
@brad-decker You might have an opinion on this too.
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've added object literal properties and methods, which can be camelCase
, UPPER_CASE
, and PascalCase
as well. This is useful in situations where we don't control the naming (i.e., external libraries).
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.
Also changed it so we allow leading underscores in front of variables. We use this occasionally to signal that a variable is unused.
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 feel like using PascalCase
for variables is not very common. You mentioned that utils
uses it for structs, but is that the only case? If so I wonder whether that should be an override.
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.
We do it in snaps-monorepo
as well, also for structs. I don't know if there's an easy way to override these rules without copying the entire rule?
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 if the convention is Enums will use PascalCase, then I am in favor of using that. The rules as stated make sense to me.
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.
Okay, no problem.
packages/typescript/src/index.js
Outdated
}, | ||
}, | ||
], | ||
'@typescript-eslint/no-confusing-void-expression': 'error', |
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 can catch some bugs, like trying to log a function that returns 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.
This is causing a lot of errors in snaps-monorepo
, especially in functions that do something like return fn()
where fn
returns void
. I think we should not enable this rule.
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.
Ah, that makes sense.
I can see how this rule might improve readability, but it would be more verbose, and might make it easier to forget to return
.
'@typescript-eslint/prefer-includes': 'error', | ||
'@typescript-eslint/prefer-nullish-coalescing': 'error', | ||
'@typescript-eslint/prefer-readonly': 'error', | ||
'@typescript-eslint/prefer-reduce-type-parameter': 'error', |
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 makes sure we use the generic parameter of Array.prototype.reduce
, rather than casting the initial value to something.
const foo = [];
foo.reduce<Bar>(...); // Valid
foo.reduce(reducer, defaultValue as Bar); // Invalid
'@typescript-eslint/prefer-nullish-coalescing': 'error', | ||
'@typescript-eslint/prefer-readonly': 'error', | ||
'@typescript-eslint/prefer-reduce-type-parameter': 'error', | ||
'@typescript-eslint/prefer-string-starts-ends-with': 'error', |
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 prefers using String.prototype.startsWith
and String.prototype.endsWith
over other alternatives, like checking a substring from index 0 to n.
'@typescript-eslint/prefer-readonly': 'error', | ||
'@typescript-eslint/prefer-reduce-type-parameter': 'error', | ||
'@typescript-eslint/prefer-string-starts-ends-with': 'error', | ||
'@typescript-eslint/promise-function-async': 'error', |
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 makes sure that functions that return a Promise
result are always marked as async functions. That way we cannot throw both regular errors and Promise
rejections from the same 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.
This might be my favorite rule
'@typescript-eslint/prefer-reduce-type-parameter': 'error', | ||
'@typescript-eslint/prefer-string-starts-ends-with': 'error', | ||
'@typescript-eslint/promise-function-async': 'error', | ||
'@typescript-eslint/switch-exhaustiveness-check': 'error', |
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 makes sure that all the possible values of a union are covered in a switch statement that does not have a default
case.
// This enables support for linting rules that require type information. We | ||
// assume that the project has a `tsconfig.json` file in the directory where | ||
// ESLint is being run. | ||
tsconfigRootDir: process.cwd(), |
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 assume that we always run eslint
from the root project here, but that may not always be the case, especially in monorepos. We may have to configure this on a per-project basis.
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.
Tested this in snaps-monorepo
, and it does not work out of the box with this option. All monorepo packages need to have their own .eslintrc
, and specify the tsconfigRootDir
. Otherwise ESLint will complain that the files are not included in tsconfig.json
(since they aren't in the root config).
I did some quick testing in |
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.
LGTM! Very excited to see this land.
Can we leave this open a few days to allow others to comment? Since there are a few potentially controversial rules here. i.e. maybe we can merge it on Tuesday.
Missed comment about testing; I will help test these rules out before approving again.
packages/typescript/src/index.js
Outdated
'@typescript-eslint/no-unsafe-member-access': 'off', | ||
'@typescript-eslint/no-unsafe-return': 'off', | ||
'@typescript-eslint/restrict-template-expressions': 'off', | ||
'@typescript-eslint/unbound-method': 'off', |
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 rule is causing a lot of confusing errors like this:
14:36 error Avoid referencing unbound methods which may cause unintentional scoping of `this`.
If your function does not access `this`, you can annotate it with `this: void`, or consider using an arrow function instead @typescript-eslint/unbound-method
I don't think this is actually an issue, so I have disabled this rule.
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.
You may be aware but it's an easy enough mistake to do:
class Foo {
public logThis() {
console.log(this);
}
}
const foo = new Foo();
foo.logThis(); // logs foo
window.setTimeout(foo.logThis); // logs global
window.setTimeout(foo.logThis.bind()); // do this
window.setTimeout(() => foo.logThis()); // or this
Is it worth silencing individually where it's actually desired?
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 problem is that it causes errors in situations like this:
expect(Math.abs).toStrictEqual(rootRealmGlobal.Math.abs);
Which we happen to do quite a bit in snaps-monorepo
. We could just disable it for that repo, but I would guess that there are others with a similar problem.
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.
Hmm. Are examples like that widespread? We can disable the rule for specific files/lines. It does seem like what the rule is suggesting is what we are already doing in most places.
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.
It seemed to happen quite a bit in snaps-monorepo
. Not sure about other repos.
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 guess that this can be revisited if/when at least Typescript stdlib is annotating consistently to make those functions work. Or it can be enabled by default and disabled on a per-need (lines or repo) basis.
https://www.typescriptlang.org/docs/handbook/2/functions.html#declaring-this-in-a-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.
I've re-enabled it. If it becomes an issue for certain repos, we can disable it for those repos.
a12f8a7
to
e3829a7
Compare
Looks good to me, minus the bit about getting this to work with |
3911d8e
to
2d0011d
Compare
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.
LGTM!
Closes #2.
This adds rules that require type information to the TypeScript ESLint config. I've set it to extend the default configuration, and added some other rules that are not included in the default.