Closed SimenB closed 5 years ago
I've done a first pass of no-mocks-import
, which passes all the tests.
So far, it seems to me like this'll be a bit teeth pully, as ts-estree
doesn't provide you with lots of nice type guards like TS does - as a result, I've started to make my own in tsUtils
, but am trying to be conservative for now, since I don't know ts-estree
well enough to predict how wild things might get.
I've also encapsulated the actual primary condition into a isMockImportLiteral
function - let me know if you're happy with how I've done that; I've never worked in the FB/jest domain, so I don't know what you guys prefer for coding in general :joy:
Finally, off the bat there's already room for improvement, which ties in to everything I've said so far: right now the rule is only checking actual string literals, which means template strings already won't work right off the bat (regardless of if they've got actual embedded expressions or not)
This could be improved to support them w/o expressions, but you could also take this further to try and provide basic support for expressions, since you might be able to do a basic search of surrounding nodes in an attempt to see if the expression is a constant, and if it is evaluate its value.
All in all, for now I've opted for doing the basics, w/ the idea that people can fill issues requesting improvements, just to keep things simple.
I've not made a PR since I saw your previous PRs were reverted following some breaking of peoples builds. Let me know if you're still wanting to go ahead with converting to TS.
For now I'm going to continue trying to pick off the easy rules - let me know any thoughts you have :)
@G-Rath thanks for investing time in this! What you've described sounds fine to me. @SimenB will probably want to kick off a second attempt at TS migration at a less busy time
Thanks @G-Rath! Yeah, it seems using the module from https://github.com/typescript-eslint/typescript-eslint/pull/425 requires consumers to use typescript as well, so I need to figure out how we can author rules in TS without consumers noticing at all. I'll try to get to this, but I'm still swamped at work... Feel free to dig into it if you don't mind!
I don't know enough about the eslint ecosystem to know if its worth using the @typescript-eslint/parser
over the current one, which is now my question - what is the advantage?
I'm happy to help write TS, regardless of what it's for, but yeah I think the decision that needs to be made is about converting to TS vs switching to the @typescript-eslint/parser
.
The former could be started on right now if you wanted, but as you've found out the latter is a breaking change, and thus comes down to pros vs cons which I obviously can't decide for you :)
I don't know enough about the eslint ecosystem to know if its worth using the
@typescript-eslint/parser
over the current one, which is now my question - what is the advantage?
We don't want to use the parser in our own rules - we want to use the utils which provide way better types than the ones from DefinitelyTyped as well as some nice utils for options etc. It's also stricter (also called opinionated š), forcing us to follow some good conventions (fixing both #201 and #202 at once).
We do however want to use the parser for linting our own code, but that should not be visible to consumers.
The dependency on typescript
was not on purpose, see https://github.com/typescript-eslint/typescript-eslint/pull/425#issuecomment-498302492
When a release is out, we can probably retry this (I'll revert my earlier revert). Will need to dig into #268 as well at that point.
@SimenB I'll pick up a rule, can you tell me where code migrated to typescript is maintained.
A new try is up here: #305.
@M4rk9696 thanks! A PR to that branch (reapply-ts
) is most welcome. Or if I'm able to land it, to master
@G-Rath would you be able to PR no-mocks-import
to the reapply-ts
branch of this repo? š
@SimenB I can but try ;P
Give me a few minutes (or an hour, depending on the work - I've not actually been following the activity š¬)
There is absolutely no rush š
@SimenB done in #309
It took a bit longer than a few minutes b/c of a standup meeting & having to update the code a bit (but nothing major).
All the tests are passing, so I think it's fine :)
I've been doing everything in a single generic branch, and have about a quarter of the rules converted.
My plan is to just cherry-pick at the end. There are a couple of that don't require tsUtils
that I can cherry-pick, which I'll try and do now.
Otherwise, I'll finish off the rest off the rules this evening/weekend :)
Sorry for all the failing tests - I'll check them out once I'm home, as I'm rushing to catch a train š¬ I have no idea what's up w/ the one about the git reference, and so don't know how to fix it either. I'm pretty sure it's related to my fork & updating it? Either way, the code is there.
Oh wow @G-Rath, that's awesome!
Don't worry about the commitlint check on CI - it's confused by the merge commit travis creates. As long as lint and test (with coverage) passes, you're good š
@SimenB just a heads up that the meta
property should be checked on all my PRs; I've been forgoing trying to figure out what the best values for the description, setting the correct "recommended" value, the "category", and the "type" in favor of converting the actual rules as I feel those are somewhat arbitrary.
I've been just copying the header for each rule as the description
:)
I've also not tested any of the fixes - I think they'll work fine but the only thing I'm worry about is what happens if you return an empty array..? I've not written eslint plugins before so don't know what the best practices are.
I'm happy to follow your lead on any changes you want made.
Ah, ok, good to know. I'll verify
The fixes are all covered by unit tests, so if the tests pass then we're good. If a violation is not fixable, we should not return a fixer (that way the "n error potentially fixable" message from eslint is not misleading)
I think we've nearly gotten all the low hanging fruit, so I'm going to look to tackle the converting of the exportCase
& other type guard util functions (like method
, method2
, etc).
I've actually already done exportCase
, but I've not pushed it yet b/c I need to explore them a bit more, as some of them are definitely guards, while others are not.
Once that's done, we'll be able to tackle all of the prefer-*
, as they're the main rules that consume those methods.
I've got a few more rules to convert & cherry pick first tho - I'm catching a flight in a day or so, so I might go dark for a bit, but at this rate I'd say we should easily be done by the end of month, if not by next week :D
Haha, this has been awesome, thank you so much for your help!
TS ESLint had a stable release, so I merged the PR to master. Also updated OP
I merged the PR to master
Well, this went about as well as I expected - I woke up to about 20 emails for this repo, half of which being semantic bot :joy:
but seriously it actually did go as expected: the bugs I've seen are pretty much what I expected, and the fixing PRs have been great, and will make everything more stable.
I'll look to power through getting the rest of the rules converted over the next couple of days.
Pretty much all the rules that are left to do I've got half converted, as I'm picking the ones w/ the least amount of imports from tsUtils
first.
Heh, yeah... It wasn't too bad, though!
@SimenB Just wanted to let you know I've not forgotten about this š
I've got 2 rules left to convert, and the new guards + expect parser is working a treat.
Life got a bit busy once I got back, but I hope to have a PR for review by midweek.
As an example of how nice the new expect stuff is: prefer-to-be-null
& prefer-to-be-undefined
took literally 5 minutes to convert, despite being the two rules that used those massive expect guards, as they were having to account for not
.
Now they're just:
const isNullEqualityMatcher = (
matcher: ParsedExpectMatcher,
): matcher is ParsedNullEqualityMatcher =>
EqualityMatcher.hasOwnProperty(matcher.name) &&
matcher.node.parent !== undefined &&
matcher.node.parent.type === AST_NODE_TYPES.CallExpression &&
hasOneArgument(matcher.node.parent) &&
isNullLiteral(matcher.node.parent.arguments[0]);
/* in create: */
if (!isExpectCall(node)) {
return;
}
const { matcher } = parseExpectCall(node);
if (matcher && isNullEqualityMatcher(matcher)) {
context.report({
fix(fixer) {
return [
fixer.replaceText(matcher.node.property, 'toBeNull'),
fixer.remove(matcher.node.parent.arguments[0]),
];
},
messageId: 'useToBeNull',
node: matcher.node.property,
});
}
This is super exciting š
Except I missed the fact that you can chain modifiers (i.e resolve.not
) š
Not a big deal since we only really check for them in valid-expect
, but means I have to code up a linked-list style loop to the parser which are always a bit tedious in TS
You can only have expect().not.toBe()
, expect().resolves.toBe()
, expect().rejects.toBe()
, expect().resolves.not.toBe()
and expect().rejects.not.toBe()
. So I don't think we necessarily need a linked list? If we can say a parsed expect
may or may not be not
, and it might be either resolves
or rejects
. Dunno, might make it hard to do .parent
etc?
Interesting. The typescript types allow it, but it fails at runtime: http://puu.sh/DYIn4/7278eb6827.png
When I say "linked list" it's not anything complex, just:
export const parseExpectCall = (expectCall: ExpectCall): Expectation => {
const expectation: Expectation = { expectCall };
if (!isExpectMember(expectCall.parent)) {
return expectation;
}
let node = expectCall.parent;
let previousModifier: { modifier?: ParsedExpectModifier } = expectation;
while (node.parent && isExpectMember(node.parent)) {
const member = parseExpectMember(node.parent);
if (!isParsedExpectModifier(member)) {
expectation.matcher = member;
break;
}
previousModifier = previousModifier.modifier = member;
node = node.parent;
}
return expectation;
};
It was more a bit of a shock b/c I'd not considered it, and so it wasn't in the output š¬
Doing it this way means I don't have to refactor anything else, and also has the bonus of requiring the previous modifier to be defined (otherwise parsedExpect.modifer !== undefined && parsedExpect.modifier2 !== undefined
)
That should probably be a type error, yeah. The Jest team doesn't maintain those typings, but Jest's internal typings are probably wrong in this regard as well. I'll make a point to fix it before releasing Jest 25
Ahh I see how it is - I had a proper read over everything, and yeah that loop while nice is overkill (plus the types are murder):
describe('each', () => {
it.each([
expect(true).not.resolves, // true
expect(true).not.rejects, // true
expect(true).resolves.not, // false
expect(true).rejects.not // false
])('should be undefined', (result) => {
expect(result).toBe(undefined);
});
});
So the allowed modifiers are:
not
resolves
rejects
resolves.not
rejects.not
However, valid-expect
doesn't actually check for that. In fact, it has those in its tests as valid.
In fact, the majority of its not
valid tests have not
first.
I'll avoid "fixing" that if possible currently, and instead look into it after I've gotten the remaining rules converted & merged.
But in light of this I think the typings can be a lot simpler. I have a few ideas on a couple of different ways it could be typed, and will have a play around to find out what works best :)
------------------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
------------------------------|----------|----------|----------|----------|-------------------|
All files | 96.26 | 92.62 | 89.54 | 96.4 | |
src | 100 | 100 | 100 | 100 | |
index.js | 100 | 100 | 100 | 100 | |
src/processors | 100 | 100 | 100 | 100 | |
snapshot-processor.js | 100 | 100 | 100 | 100 | |
src/rules | 96.21 | 92.55 | 89.26 | 96.35 | |
consistent-test-it.ts | 100 | 100 | 100 | 100 | |
expect-expect.ts | 100 | 100 | 100 | 100 | |
expectGuards.ts | 72.73 | 47.27 | 53.33 | 72.73 |... 17,424,425,450 |
final.ts | 92.54 | 83.87 | 84 | 92.42 |... 43,365,367,433 |
lowercase-name.ts | 100 | 100 | 100 | 100 | |
no-alias-methods.ts | 100 | 100 | 100 | 100 | |
no-commented-out-tests.ts | 100 | 100 | 100 | 100 | |
no-disabled-tests.ts | 100 | 100 | 100 | 100 | |
no-duplicate-hooks.ts | 100 | 100 | 100 | 100 | |
no-empty-title.ts | 100 | 100 | 100 | 100 | |
no-export.ts | 100 | 100 | 100 | 100 | |
no-focused-tests.ts | 100 | 100 | 100 | 100 | |
no-hooks.ts | 100 | 100 | 100 | 100 | |
no-identical-title.ts | 100 | 100 | 100 | 100 | |
no-if.ts | 100 | 100 | 100 | 100 | |
no-jasmine-globals.ts | 100 | 100 | 100 | 100 | |
no-jest-import.ts | 100 | 100 | 100 | 100 | |
no-large-snapshots.js | 100 | 100 | 100 | 100 | |
no-mocks-import.ts | 100 | 100 | 100 | 100 | |
no-standalone-expect.ts | 100 | 100 | 100 | 100 | |
no-test-callback.ts | 100 | 100 | 100 | 100 | |
no-test-prefixes.ts | 100 | 100 | 100 | 100 | |
no-test-return-statement.ts | 100 | 100 | 100 | 100 | |
no-truthy-falsy.ts | 100 | 100 | 100 | 100 | |
no-try-expect.ts | 100 | 100 | 100 | 100 | |
prefer-called-with.ts | 100 | 100 | 100 | 100 | |
prefer-expect-assertions.ts | 100 | 100 | 100 | 100 | |
prefer-inline-snapshots.ts | 100 | 100 | 100 | 100 | |
prefer-spy-on.ts | 100 | 100 | 100 | 100 | |
prefer-strict-equal.ts | 100 | 100 | 100 | 100 | |
prefer-to-be-null.ts | 100 | 100 | 100 | 100 | |
prefer-to-be-undefined.ts | 100 | 100 | 100 | 100 | |
prefer-to-contain.ts | 100 | 100 | 100 | 100 | |
prefer-to-have-length.ts | 100 | 100 | 100 | 100 | |
prefer-todo.ts | 100 | 100 | 100 | 100 | |
require-tothrow-message.ts | 100 | 100 | 100 | 100 | |
tsUtils.ts | 89.86 | 75.38 | 73.08 | 89.71 |... 42,145,166,178 |
util.js | 65 | 0 | 22.22 | 70.59 | 7,10,15,20,29 |
valid-describe.ts | 100 | 100 | 100 | 100 | |
valid-expect-in-promise.ts | 100 | 100 | 100 | 100 | |
valid-expect.ts | 100 | 98.75 | 100 | 100 | 274 |
------------------------------|----------|----------|----------|----------|-------------------|
Guess who got valid-expect
typesafe last night ;)
~I've just got no-large-snapshots
to convert, which will be interesting b/c it uses babel-eslint
- hopefully it'll have TS typings, but if not I might have to leave that one to you @SimenB~
I've now finished no-large-snapshots
, resulting in babel-eslint
not being needed anymore, as all the tests have been merged into the RuleTester
(meaning the .snap
is also gone).
I expect to have a PR sometime this weekend š
(of course after saying that I realised I should re-review the guards for tests, hooks & describe :joy:)
This is done! Thank you to everyone who contributed, and especially @G-Rath who did all the hard work š
Now that https://github.com/typescript-eslint/typescript-eslint/pull/425 has been released, it should be relatively straightforward to migrate.
I think porting one and one rule makes the most sense. Feel free to pick one š
consistent-test-it
- #327expect-expect
- #325lowercase-name
- #258no-alias-methods
no-commented-out-tests
- #305no-disabled-tests
- #315no-duplicate-hooks
- #318no-empty-title
no-export
- #323no-focused-tests
- #314no-hooks
- #322no-identical-title
no-if
- #324no-jasmine-globals
- #315no-jest-import
- #259no-large-snapshots
no-mocks-import
- #309no-test-callback
- #321no-test-prefixes
- #328no-test-return-statement
- #320no-truthy-falsy
prefer-called-with
prefer-expect-assertions
prefer-inline-snapshots
- #319prefer-spy-on
- #326prefer-strict-equal
- #329prefer-to-be-null
prefer-to-be-undefined
prefer-to-contain
prefer-to-have-length
prefer-todo
- #335require-tothrow-message
valid-describe
- #308valid-expect-in-promise
valid-expect
- #333