refactor(tsetse): optimize equals-nan rule `getText()` should be avoid as it as an expensive call; favor node 'isXYZ' checks and direct `text` property access. simplify rule registration method overloads uses type property directly to remove the need for specialized overloads for each TS node type cleanup ban-expect-truthy-promise rule Closes #346 PiperOrigin-RevId: 225020913
diff --git a/internal/tsetse/checker.ts b/internal/tsetse/checker.ts index 5e8b6d3..72fdbaa 100644 --- a/internal/tsetse/checker.ts +++ b/internal/tsetse/checker.ts
@@ -47,36 +47,8 @@ * `nodeKind` node in `nodeHandlersMap` map. After all rules register their * handlers, the source file AST will be traversed. */ - on(nodeKind: ts.SyntaxKind.BinaryExpression, - handlerFunction: (checker: Checker, node: ts.BinaryExpression) => void, - code: number): void; - on(nodeKind: ts.SyntaxKind.ConditionalExpression, - handlerFunction: - (checker: Checker, node: ts.ConditionalExpression) => void, - code: number): void; - on(nodeKind: ts.SyntaxKind.WhileStatement, - handlerFunction: (checker: Checker, node: ts.WhileStatement) => void, - code: number): void; - on(nodeKind: ts.SyntaxKind.IfStatement, - handlerFunction: (checker: Checker, node: ts.IfStatement) => void, - code: number): void; - on(nodeKind: ts.SyntaxKind.CallExpression, - handlerFunction: (checker: Checker, node: ts.CallExpression) => void, - code: number): void; - on(nodeKind: ts.SyntaxKind.PropertyDeclaration, - handlerFunction: (checker: Checker, node: ts.PropertyDeclaration) => void, - code: number): void; - on(nodeKind: ts.SyntaxKind.ElementAccessExpression, - handlerFunction: (checker: Checker, node: ts.ElementAccessExpression) => void, - code: number): void; - on(nodeKind: ts.SyntaxKind.ImportDeclaration, - handlerFunction: (checker: Checker, node: ts.ImportDeclaration) => void, - code: number): void; - on(nodeKind: ts.SyntaxKind, - handlerFunction: (checker: Checker, node: ts.Node) => void, - code: number): void; on<T extends ts.Node>( - nodeKind: ts.SyntaxKind, + nodeKind: T['kind'], handlerFunction: (checker: Checker, node: T) => void, code: number) { const newHandler: Handler = {handlerFunction, code}; const registeredHandlers: Handler[]|undefined =
diff --git a/internal/tsetse/rules/ban_expect_truthy_promise_rule.ts b/internal/tsetse/rules/ban_expect_truthy_promise_rule.ts index 6f852ae..fbbefd9 100644 --- a/internal/tsetse/rules/ban_expect_truthy_promise_rule.ts +++ b/internal/tsetse/rules/ban_expect_truthy_promise_rule.ts
@@ -21,23 +21,27 @@ } } -function checkForTruthy(checker: Checker, node: ts.Node) { - const tc = checker.typeChecker; - - if (!tsutils.isPropertyAccessExpression(node)) { +function checkForTruthy(checker: Checker, node: ts.PropertyAccessExpression) { + if (node.name.text !== 'toBeTruthy') { return; } - if (node.name.getText() !== 'toBeTruthy') { - return; - } - - const expectCallNode = getLeftmostNode(tc, node); + const expectCallNode = getLeftmostNode(node); if (!ts.isCallExpression(expectCallNode)) { return; } - const signature = checker.typeChecker.getResolvedSignature(expectCallNode); + if (!ts.isIdentifier(expectCallNode.expression) || expectCallNode.expression.text !== 'expect') { + return; + } + + if (expectCallNode.arguments.length === 0 || ts.isAwaitExpression(expectCallNode.arguments[0])) { + return; + } + + const tc = checker.typeChecker; + + const signature = tc.getResolvedSignature(expectCallNode); if (signature === undefined) { return; } @@ -48,13 +52,11 @@ } // Only look for methods named expect that return a Matchers - if (!((symbol.name === 'Matchers') && - expectCallNode.expression.getText() === 'expect')) { + if (symbol.name !== 'Matchers') { return; } - if (!tsutils.isThenableType(tc, expectCallNode.arguments[0]) || - tsutils.isAwaitExpression(expectCallNode.arguments[0])) { + if (!tsutils.isThenableType(tc, expectCallNode.arguments[0])) { return; } @@ -67,8 +69,7 @@ `\n\tSee http://tsetse.info/ban-expect-truthy-promise`); } -function getLeftmostNode( - tc: ts.TypeChecker, node: ts.PropertyAccessExpression) { +function getLeftmostNode(node: ts.PropertyAccessExpression) { let current: ts.LeftHandSideExpression|undefined = node; while (ts.isPropertyAccessExpression(current)) { current = current.expression;
diff --git a/internal/tsetse/rules/equals_nan_rule.ts b/internal/tsetse/rules/equals_nan_rule.ts index da8f7fe..38b01d5 100644 --- a/internal/tsetse/rules/equals_nan_rule.ts +++ b/internal/tsetse/rules/equals_nan_rule.ts
@@ -20,19 +20,41 @@ } function checkBinaryExpression(checker: Checker, node: ts.BinaryExpression) { - if (node.left.getText() === 'NaN' || node.right.getText() === 'NaN') { - const operator = node.operatorToken; - if (operator.kind == ts.SyntaxKind.EqualsEqualsToken || - operator.kind === ts.SyntaxKind.EqualsEqualsEqualsToken) { + const isLeftNaN = ts.isIdentifier(node.left) && node.left.text === 'NaN'; + const isRightNaN = ts.isIdentifier(node.right) && node.right.text === 'NaN'; + if (!isLeftNaN && !isRightNaN) { + return; + } + + // We avoid calling getText() on the node.operatorToken because it's slow. + // Instead, manually map back from the kind to the string form of the operator + switch (node.operatorToken.kind) { + case ts.SyntaxKind.EqualsEqualsToken: checker.addFailureAtNode( - node, - `x ${operator.getText()} NaN is always false; use isNaN(x) instead`); - } - if (operator.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken || - operator.kind === ts.SyntaxKind.ExclamationEqualsToken) { + node, + `x == NaN is always false; use isNaN(x) instead`, + ); + break; + case ts.SyntaxKind.EqualsEqualsEqualsToken: checker.addFailureAtNode( - node, - `x ${operator.getText()} NaN is always true; use !isNaN(x) instead`); - } + node, + `x === NaN is always false; use isNaN(x) instead`, + ); + break; + case ts.SyntaxKind.ExclamationEqualsToken: + checker.addFailureAtNode( + node, + `x != NaN is always true; use !isNaN(x) instead`, + ); + break; + case ts.SyntaxKind.ExclamationEqualsEqualsToken: + checker.addFailureAtNode( + node, + `x !== NaN is always true; use !isNaN(x) instead`, + ); + break; + default: + // We don't care about other operators acting on NaN + break; } }