Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

strict boolean expressions #1820

Merged
merged 1 commit into from
Dec 8, 2016
Merged

Conversation

subash-a
Copy link
Contributor

@subash-a subash-a commented Dec 5, 2016

PR checklist

Introduced a rule to type check boolean expressions using type aware rules. This rule checks if the given binary expression, if statement, while statement or for statement comprises of correctly formed boolean expressions or literals. This is a first cut of the implementation and would like to get feedback on how to improve this and finally make this rule a part of the tslint rule set.
@myitcv tagging you here since we both discussed this rule offline, @adidahiya any feedback is welcome on how we can improve this.

@palantirtech
Copy link
Member

Thanks for your interest in palantir/tslint, @subash-a! Before we can accept your pull request, you need to sign our contributor license agreement - just visit https://cla.palantir.com/ and follow the instructions. Once you sign, I'll automatically update this pull request.

@subash-a subash-a changed the title Strict boolean exprs [WIP] strict boolean exprs Dec 5, 2016
@subash-a subash-a changed the title [WIP] strict boolean exprs [WIP] strict boolean expressions Dec 5, 2016
@subash-a subash-a force-pushed the strict-boolean-exprs branch from 67c6d3a to 6743e25 Compare December 5, 2016 08:57
@myitcv
Copy link
Contributor

myitcv commented Dec 5, 2016

Looks great to me 👍

Copy link
Contributor

@nchen63 nchen63 left a comment

Choose a reason for hiding this comment

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

Looks pretty good overall. Good tests. Needs some minor changes

@@ -0,0 +1,116 @@
import * as ts from "typescript";
Copy link
Contributor

Choose a reason for hiding this comment

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

license, metadata


class StrictBooleanExpressionsRule extends Lint.ProgramAwareRuleWalker {
public visitBinaryExpression(node: ts.BinaryExpression): void {
let checker = this.getTypeChecker();
Copy link
Contributor

Choose a reason for hiding this comment

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

call this once. move to constructor

public visitBinaryExpression(node: ts.BinaryExpression): void {
let checker = this.getTypeChecker();
// check all Boolean and BooleanLiteral expressions for validity
let nodeType = checker.getTypeAtLocation(node);
Copy link
Contributor

Choose a reason for hiding this comment

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

move this and as much logic as possible inside of if statement for perf

}

class StrictBooleanExpressionsRule extends Lint.ProgramAwareRuleWalker {
public visitBinaryExpression(node: ts.BinaryExpression): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

we typically don't explicitly write the void return type

}

private isBooleanType(btype: ts.Type): boolean {
let boolType = /* tslint:disable:no-bitwise */(btype.flags & ts.TypeFlags.BooleanLike)/* tslint:enable:no-bitwise */;
Copy link
Contributor

Choose a reason for hiding this comment

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

use utils.isNodeFlagSet

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nchen63 this would not work because I am checking the type flags and not the node flags, there are two options available

  1. I can leave this line as is
  2. I can introduce a new function isTypeFlagsSet into language/utils.ts which can be used to check the type flags here
    which option do you suggest I go with?

Copy link
Contributor

Choose a reason for hiding this comment

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

Please add this to utils


private isBooleanType(btype: ts.Type): boolean {
let boolType = /* tslint:disable:no-bitwise */(btype.flags & ts.TypeFlags.BooleanLike)/* tslint:enable:no-bitwise */;
if (boolType === ts.TypeFlags.Boolean || boolType === ts.TypeFlags.BooleanLiteral || boolType === ts.TypeFlags.BooleanLike) {
Copy link
Contributor

Choose a reason for hiding this comment

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

don't need if statement, just return the condition

@@ -0,0 +1,184 @@
/* tslint:disable:no-unused-parameters */
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't need these disable comments

Copy link
Contributor

Choose a reason for hiding this comment

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

also this is not a tslint rule

this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.BINARY_EXPRESSION_ERROR));
}
}
super.visitBinaryExpression(node);
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't call super.visitBinaryExpression if a failure is detected at this node. Seems excessive to flag true && 1 && 1 twice since errors overlap

Copy link
Contributor

Choose a reason for hiding this comment

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

same with other super calls below

export class Rule extends Lint.Rules.TypedRule {
public static BINARY_EXPRESSION_ERROR = "Invalid Expression: Operands for the && or || operator should be of type boolean";
public static UNARY_EXPRESSION_ERROR = "Invalid Expression: Operand for the ! operator should be of type boolean";
public static IF_STATEMENT_ERROR = "Invalid Expression: If statement condition needs to be a boolean expression or literal";
Copy link
Contributor

Choose a reason for hiding this comment

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

Combine lines 7 to 11 into Statement is not a boolean expression. After that, combine some of the checker logic below to reduce code duplication.

import * as Lint from "../index";

export class Rule extends Lint.Rules.TypedRule {
public static BINARY_EXPRESSION_ERROR = "Invalid Expression: Operands for the && or || operator should be of type boolean";
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove Invalid Expression:

@subash-a subash-a force-pushed the strict-boolean-exprs branch from 6743e25 to b1a04bb Compare December 6, 2016 08:09
@subash-a
Copy link
Contributor Author

subash-a commented Dec 6, 2016

@nchen63 I have made all the changes suggested, but one of the comments had a problem with the implementation for which I have left you a note, will make changes based on your answer there. Thanks for the quick and detailed review

@subash-a subash-a force-pushed the strict-boolean-exprs branch from b1a04bb to 47072f1 Compare December 6, 2016 08:26
public visitIfStatement(node: ts.IfStatement) {
let hasError = this.checkStatement(node);
if (!hasError) {
super.visitIfStatement(node);
Copy link
Contributor

Choose a reason for hiding this comment

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

Won't this skip the block/body of the if statement?

if (!this.isBooleanType(expType)) {
switch (node.kind) {
case ts.SyntaxKind.IfStatement:
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), `If ${Rule.STATEMENT_ERROR}`));
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't necessarily need different error message for each type of conditional, but this works

let rhsExpression = node.right;
let rhsType = this.checker.getTypeAtLocation(rhsExpression);
if (!this.isBooleanType(lhsType) || !this.isBooleanType(rhsType)) {
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.BINARY_EXPRESSION_ERROR));
Copy link
Contributor

Choose a reason for hiding this comment

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

The failure should underline the offending operand, not the entire statement

@subash-a subash-a force-pushed the strict-boolean-exprs branch from 6c73667 to 815912a Compare December 7, 2016 09:26
@subash-a subash-a mentioned this pull request Dec 7, 2016
4 tasks
@subash-a
Copy link
Contributor Author

subash-a commented Dec 7, 2016

@nchen63 changed the code to point out only the offending expression and also changed the logic of checking statements even after error has been flagged to find further errors in the statement block.

@subash-a
Copy link
Contributor Author

subash-a commented Dec 7, 2016

@nchen63 However I had a question about the super.visit call for the various statements and expressions that I have in my code, is calling super.visitIfStatement (for example) mandatory after the error checking logic is executed? How does this affect the other rules, if it does at all, and would it stop errors from other rules showing up? Because if that is the case then what we are doing inside the visitBooleanExpression would stop other errors for the boolean expression to show up when there is an error in my rule and the other error would show up only after correcting the first error. Any suggestion on how we can structure this rule to avoid affecting other rules? perhaps @adidahiya and @jkillian can also pitch in with their thoughts on how best to do this.

@nchen63
Copy link
Contributor

nchen63 commented Dec 7, 2016

we have rules like this that don't call super. See noShadowedVariableRule.ts

@adidahiya
Copy link
Contributor

Rules are evaluated individually in isolation, so omitting a super call will not affect other rules. If such a call is omitted at a non-leaf node in the AST, the only effect is that the children in that subtree are not linted for that rule.

/*** If Statement ***/
/*** Invalid ***/
if (numType) { /* statements */ }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [If statement condition needs to be a boolean expression or literal]
Copy link
Contributor

Choose a reason for hiding this comment

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

underline the condition, not the entire if statement. Same with others

bwrapType || boolType;
~~~~~~~~~ [Operands for the && or || operator should be of type boolean]
objType || boolType || enumType;
~~~~~~~~~~~~~~~~~~~ [Operands for the && or || operator should be of type boolean]
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it makes sense to underline objType || boolType, objType should only be underlined. If we can avoid flagging this, we can go back to the way it was before using errorFlagged and call super since that address the issue of overlapping errors

@subash-a subash-a force-pushed the strict-boolean-exprs branch 3 times, most recently from 5a83f90 to 8cfc9d0 Compare December 8, 2016 06:12
This rule helps in enforcing usage of boolean expressions in if
statements and other constructs where boolean expressions are used
also restricts the && and || operators to work with boolean types so
that the operator behaves consistently without the need for type
coercion.
@subash-a subash-a force-pushed the strict-boolean-exprs branch from 8cfc9d0 to a984107 Compare December 8, 2016 06:16
@subash-a
Copy link
Contributor Author

subash-a commented Dec 8, 2016

Thank you @nchen63 and @adidahiya for that explanation, things are much clearer to me now, so based on the feedback left in the earlier comments I have made changes to eliminate the duplication of error messages and also narrow down the error message to point to the specific expression or identifier, please take a look and let me know if the changes are fine, this should work as good first cut for this rule and probably improved upon as we go. Please let me know what you guys think.

@subash-a subash-a changed the title [WIP] strict boolean expressions strict boolean expressions Dec 8, 2016
@nchen63 nchen63 merged commit 47212df into palantir:master Dec 8, 2016
@subash-a
Copy link
Contributor Author

subash-a commented Dec 8, 2016

@nchen63 and @adidahiya Thanks for the review, how do we get the documentation updated, i.e add the description about the new rule at https://palantir.github.io/tslint/rules/

@nchen63
Copy link
Contributor

nchen63 commented Dec 8, 2016

We don't update that site until a release is made. You can run npm run verify to generate the docs, but you don't need to

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants