Skip to content

Commit

Permalink
chore: convert utils to type guards (#310)
Browse files Browse the repository at this point in the history
  • Loading branch information
G-Rath authored and SimenB committed Jul 20, 2019
1 parent c315740 commit fd64554
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 52 deletions.
39 changes: 20 additions & 19 deletions src/rules/__tests__/lowercase-name.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TSESLint } from '@typescript-eslint/experimental-utils';
import rule from '../lowercase-name';
import { DescribeAlias, TestCaseName } from '../tsUtils';

const ruleTester = new TSESLint.RuleTester({
parserOptions: {
Expand Down Expand Up @@ -54,7 +55,7 @@ ruleTester.run('lowercase-name', rule, {
errors: [
{
messageId: 'unexpectedLowercase',
data: { method: 'it' },
data: { method: TestCaseName.it },
column: 1,
line: 1,
},
Expand All @@ -66,7 +67,7 @@ ruleTester.run('lowercase-name', rule, {
errors: [
{
messageId: 'unexpectedLowercase',
data: { method: 'it' },
data: { method: TestCaseName.it },
column: 1,
line: 1,
},
Expand All @@ -78,7 +79,7 @@ ruleTester.run('lowercase-name', rule, {
errors: [
{
messageId: 'unexpectedLowercase',
data: { method: 'it' },
data: { method: TestCaseName.it },
column: 1,
line: 1,
},
Expand All @@ -90,7 +91,7 @@ ruleTester.run('lowercase-name', rule, {
errors: [
{
messageId: 'unexpectedLowercase',
data: { method: 'test' },
data: { method: TestCaseName.test },
column: 1,
line: 1,
},
Expand All @@ -102,7 +103,7 @@ ruleTester.run('lowercase-name', rule, {
errors: [
{
messageId: 'unexpectedLowercase',
data: { method: 'test' },
data: { method: TestCaseName.test },
column: 1,
line: 1,
},
Expand All @@ -114,7 +115,7 @@ ruleTester.run('lowercase-name', rule, {
errors: [
{
messageId: 'unexpectedLowercase',
data: { method: 'test' },
data: { method: TestCaseName.test },
column: 1,
line: 1,
},
Expand All @@ -126,7 +127,7 @@ ruleTester.run('lowercase-name', rule, {
errors: [
{
messageId: 'unexpectedLowercase',
data: { method: 'describe' },
data: { method: DescribeAlias.describe },
column: 1,
line: 1,
},
Expand All @@ -138,7 +139,7 @@ ruleTester.run('lowercase-name', rule, {
errors: [
{
messageId: 'unexpectedLowercase',
data: { method: 'describe' },
data: { method: DescribeAlias.describe },
column: 1,
line: 1,
},
Expand All @@ -150,7 +151,7 @@ ruleTester.run('lowercase-name', rule, {
errors: [
{
messageId: 'unexpectedLowercase',
data: { method: 'describe' },
data: { method: DescribeAlias.describe },
column: 1,
line: 1,
},
Expand All @@ -162,7 +163,7 @@ ruleTester.run('lowercase-name', rule, {
errors: [
{
messageId: 'unexpectedLowercase',
data: { method: 'describe' },
data: { method: DescribeAlias.describe },
column: 1,
line: 1,
},
Expand All @@ -175,15 +176,15 @@ ruleTester.run('lowercase-name with ignore=describe', rule, {
valid: [
{
code: "describe('Foo', function () {})",
options: [{ ignore: ['describe'] }],
options: [{ ignore: [DescribeAlias.describe] }],
},
{
code: 'describe("Foo", function () {})',
options: [{ ignore: ['describe'] }],
options: [{ ignore: [DescribeAlias.describe] }],
},
{
code: 'describe(`Foo`, function () {})',
options: [{ ignore: ['describe'] }],
options: [{ ignore: [DescribeAlias.describe] }],
},
],
invalid: [],
Expand All @@ -193,15 +194,15 @@ ruleTester.run('lowercase-name with ignore=test', rule, {
valid: [
{
code: "test('Foo', function () {})",
options: [{ ignore: ['test'] }],
options: [{ ignore: [TestCaseName.test] }],
},
{
code: 'test("Foo", function () {})',
options: [{ ignore: ['test'] }],
options: [{ ignore: [TestCaseName.test] }],
},
{
code: 'test(`Foo`, function () {})',
options: [{ ignore: ['test'] }],
options: [{ ignore: [TestCaseName.test] }],
},
],
invalid: [],
Expand All @@ -211,15 +212,15 @@ ruleTester.run('lowercase-name with ignore=it', rule, {
valid: [
{
code: "it('Foo', function () {})",
options: [{ ignore: ['it'] }],
options: [{ ignore: [TestCaseName.it] }],
},
{
code: 'it("Foo", function () {})',
options: [{ ignore: ['it'] }],
options: [{ ignore: [TestCaseName.it] }],
},
{
code: 'it(`Foo`, function () {})',
options: [{ ignore: ['it'] }],
options: [{ ignore: [TestCaseName.it] }],
},
],
invalid: [],
Expand Down
45 changes: 17 additions & 28 deletions src/rules/lowercase-name.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,29 @@
import {
AST_NODE_TYPES,
TSESLint,
TSESTree,
} from '@typescript-eslint/experimental-utils';
import { createRule } from './tsUtils';

type JestFunctionName = 'it' | 'test' | 'describe';

interface JestFunctionIdentifier extends TSESTree.Identifier {
name: JestFunctionName;
}

interface TestFunctionCallExpression extends TSESTree.CallExpression {
callee: JestFunctionIdentifier;
}
import {
DescribeAlias,
JestFunctionCallExpression,
JestFunctionName,
TestCaseName,
createRule,
isDescribe,
isTestCase,
} from './tsUtils';

type ArgumentLiteral = TSESTree.Literal | TSESTree.TemplateLiteral;

interface FirstArgumentStringCallExpression extends TSESTree.CallExpression {
arguments: [ArgumentLiteral];
}

type CallExpressionWithCorrectCalleeAndArguments = TestFunctionCallExpression &
type CallExpressionWithCorrectCalleeAndArguments = JestFunctionCallExpression<
TestCaseName.it | TestCaseName.test | DescribeAlias.describe
> &
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 =>
Expand All @@ -48,7 +35,9 @@ const hasStringAsFirstArgument = (
const isJestFunctionWithLiteralArg = (
node: TSESTree.CallExpression,
): node is CallExpressionWithCorrectCalleeAndArguments =>
isItTestOrDescribeFunction(node) && hasStringAsFirstArgument(node);
(isTestCase(node) || isDescribe(node)) &&
['it', 'test', 'describe'].includes(node.callee.name) &&
hasStringAsFirstArgument(node);

const testDescription = (argument: ArgumentLiteral): string | null => {
if (argument.type === AST_NODE_TYPES.Literal) {
Expand Down Expand Up @@ -142,7 +131,7 @@ export default createRule({
// guaranteed by jestFunctionName
const description = testDescription(firstArg)!;

const rangeIgnoringQuotes: [number, number] = [
const rangeIgnoringQuotes: TSESLint.AST.Range = [
firstArg.range[0] + 1,
firstArg.range[1] - 1,
];
Expand Down
65 changes: 60 additions & 5 deletions src/rules/tsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,38 @@ export const createRule = ESLintUtils.RuleCreator(name => {
return `${REPO_URL}/blob/v${version}/docs/rules/${ruleName}.md`;
});

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

export enum TestCaseName {
'fit' = 'fit',
'it' = 'it',
'test' = 'test',
'xit' = 'xit',
'xtest' = 'xtest',
}

export enum HookName {
'beforeAll' = 'beforeAll',
'beforeEach' = 'beforeEach',
'afterAll' = 'afterAll',
'afterEach' = 'afterEach',
}

export type JestFunctionName = DescribeAlias | TestCaseName | HookName;

interface JestFunctionIdentifier<FunctionName extends JestFunctionName>
extends TSESTree.Identifier {
name: FunctionName;
}

export interface JestFunctionCallExpression<
FunctionName extends JestFunctionName = JestFunctionName
> extends TSESTree.CallExpression {
callee: JestFunctionIdentifier<FunctionName>;
}

export type FunctionExpression =
Expand All @@ -28,7 +56,34 @@ export const isFunction = (node: TSESTree.Node): node is FunctionExpression =>
node.type === AST_NODE_TYPES.FunctionExpression ||
node.type === AST_NODE_TYPES.ArrowFunctionExpression;

export const isDescribe = (node: TSESTree.CallExpression): boolean => {
/* istanbul ignore next */
export const isHook = (
node: TSESTree.CallExpression,
): node is JestFunctionCallExpression<HookName> => {
return (
(node.callee.type === AST_NODE_TYPES.Identifier &&
node.callee.name in HookName) ||
(node.callee.type === AST_NODE_TYPES.MemberExpression &&
node.callee.object.type === AST_NODE_TYPES.Identifier &&
node.callee.object.name in HookName)
);
};

export const isTestCase = (
node: TSESTree.CallExpression,
): node is JestFunctionCallExpression<TestCaseName> => {
return (
(node.callee.type === AST_NODE_TYPES.Identifier &&
node.callee.name in TestCaseName) ||
(node.callee.type === AST_NODE_TYPES.MemberExpression &&
node.callee.object.type === AST_NODE_TYPES.Identifier &&
node.callee.object.name in TestCaseName)
);
};

export const isDescribe = (
node: TSESTree.CallExpression,
): node is JestFunctionCallExpression<DescribeAlias> => {
return (
(node.callee.type === AST_NODE_TYPES.Identifier &&
node.callee.name in DescribeAlias) ||
Expand Down

0 comments on commit fd64554

Please sign in to comment.