Skip to content

Conversation

@G-Rath
Copy link
Collaborator

@G-Rath G-Rath commented Jul 18, 2019

Converts a couple of isX utility methods into their TS variants, so that it's easier to convert rules :)

node.type === AST_NODE_TYPES.FunctionExpression ||
node.type === AST_NODE_TYPES.ArrowFunctionExpression;

export const isHook = (node: TSESTree.CallExpression): boolean => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can make this (and the others) into a type guard. Sort of like what I've done in

type JestFunctionName = 'it' | 'test' | 'describe';
interface JestFunctionIdentifier extends TSESTree.Identifier {
name: JestFunctionName;
}
interface TestFunctionCallExpression extends TSESTree.CallExpression {
callee: JestFunctionIdentifier;
}
type ArgumentLiteral = TSESTree.Literal | TSESTree.TemplateLiteral;
interface FirstArgumentStringCallExpression extends TSESTree.CallExpression {
arguments: [ArgumentLiteral];
}
type CallExpressionWithCorrectCalleeAndArguments = TestFunctionCallExpression &
FirstArgumentStringCallExpression;
const isItTestOrDescribeFunction = (
node: TSESTree.CallExpression,
): node is TestFunctionCallExpression => {
const { callee } = node;
if (callee.type !== AST_NODE_TYPES.Identifier) {
return false;
}
const { name } = callee;
return name === 'it' || name === 'test' || name === 'describe';
};
const hasStringAsFirstArgument = (
node: TSESTree.CallExpression,
): node is FirstArgumentStringCallExpression =>
node.arguments &&
node.arguments[0] &&
(node.arguments[0].type === AST_NODE_TYPES.Literal ||
node.arguments[0].type === AST_NODE_TYPES.TemplateLiteral);
const isJestFunctionWithLiteralArg = (
node: TSESTree.CallExpression,
): node is CallExpressionWithCorrectCalleeAndArguments =>
isItTestOrDescribeFunction(node) && hasStringAsFirstArgument(node);

It narrows down the types as we go

(might wanna move the ones I linked to in here as well?)

Copy link
Collaborator Author

@G-Rath G-Rath Jul 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd advise against that - it's actually something I've recently been talking about with a few different people.

The problem is that type guards imply if something is or is not a given type, which will cause massive problems if you start putting business logic in your guards.

For example:

const isPositiveNumber = (n: unknown): n is number => typeof n === 'number' && n > 0;

const fn = (p: number | string): string => {
  if(isPositiveNumber(p)) {
    return p.toString();
  } //        ^ type: number

  return p.toUpperCase();
  //        ^ type: string | !number
}

fn(1);         // fine
fn('hello'); // also fine
fn(-1);       // error, no method toUpperCase on Number!

Another way (and maybe more accurate) to say is it that type guards don't just narrow, they also negate.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However in looking over your code, I think you might already know that, and has accounted for it :)

If you're happy with it, I'm happy with it 😂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, they should definitely be logic free, just checking the type of things, not if they pass some constraint. But e.g. verifying the name of a function and the type of its arguments (or at least, say, the first n arguments) is really useful as we then don't have to use type casts to please the compiler. As long as we're careful to just do narrowing that types can verify, I think we're fine

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah totally - your code I think is perfect; it wouldn't work w/ the number example (that needs this), but it should be totally spot on for this.

The only thing I think is that the enums will need to be strings:

enum DescribeAlias {
  'describe' = 'describe',
  'fdescribe' = 'fdescribe',
  'xdescribe' = 'xdescribe',
}

Otherwise, we can't use them as both type values and as enums, since keyof DescribeAlias currently returns 0,1,2,etc :(

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine 🙂 Not sure what the cleanest is there, but easy enough to change if a better alternative comes up at some point

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pushed up an implementation, which I am actually really happy w/.

Despite how it might first appear, I think it shuffles around the current learning curve rather than adding to it, while making it super quick to implement scoping on rules to target specific jest functions :D

Let me know what you think - I've applied it to lowercase-name as a showcase, but can cherry-pick that into another PR if you'd like.

Copy link
Member

@SimenB SimenB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woo, thanks! I left a comment that I'd love to see resolved, but we can also do that as a follow-up

@SimenB
Copy link
Member

SimenB commented Jul 18, 2019

Hmm, CI also fails since these helpers are not used... Can add an istanbul ignore comment above until we use them

): node is CallExpressionWithCorrectCalleeAndArguments =>
isItTestOrDescribeFunction(node) && hasStringAsFirstArgument(node);
(isTestCase(node) || isDescribe(node)) &&
['it', 'test', 'describe'].includes(node.callee.name) &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't (isTestCase(node) || isDescribe(node)) cover this line?

Copy link
Collaborator Author

@G-Rath G-Rath Jul 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to ask you about that :)

Previously the check was just for these three functions, and the description & tests for the rule only focus on those three:

Enforce it, test and describe to have descriptions that begin with a lowercase letter. This provides more readable test failures.

I think it should be fine to remove that line, but wanted to implement the original behaviour + ask first, in case that was part of the point of that rule that the community requested (or something)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah right, since we'd now get xtest, ftest etc? I think it's fine to detect all - they're just modifiers on the same functions

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do that in a follow-up, though

@G-Rath
Copy link
Collaborator Author

G-Rath commented Jul 18, 2019

btw I've had to remove the commitlint hook from package.json, as it just refuses to work properly.

Could you confirm for me if it's working on your end, and what OS you're using?

I'm on Win10 w/ WSL - I've tried removing & reinstalling from both cmd & wsl, plus updating husky, replacing just the commitmsg hook w/ the latest version from husky while still usingv1, etc all to no success :(

@G-Rath
Copy link
Collaborator Author

G-Rath commented Jul 19, 2019

I've started converting the expectCase methods too.

Let me know if you'd like them in a separate PR; otherwise I'll push them up to this one soon :)

I'm applying the typeguard patten where possible, renaming such methods to "is" and leaving an exported const alias in place of the old version w/ a @deprecated link.

@SimenB SimenB merged commit bab5788 into jest-community:reapply-ts Jul 19, 2019
@G-Rath G-Rath deleted the ts-migration/convert-is-utils-fns branch July 19, 2019 08:10
SimenB pushed a commit that referenced this pull request Jul 20, 2019
SimenB pushed a commit that referenced this pull request Jul 20, 2019
SimenB pushed a commit that referenced this pull request Jul 21, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants