diff --git a/src/rules/strictComparisonsRule.ts b/src/rules/strictComparisonsRule.ts index 66955d35695..9e0728c4f4d 100644 --- a/src/rules/strictComparisonsRule.ts +++ b/src/rules/strictComparisonsRule.ts @@ -127,12 +127,19 @@ function walk(ctx: Lint.WalkContext, program: ts.Program) { return ts.forEachChild(sourceFile, function cb(node: ts.Node): void { if (isBinaryExpression(node) && isComparisonOperator(node)) { + const isEquality = isEqualityOperator(node); const leftType = checker.getTypeAtLocation(node.left); const rightType = checker.getTypeAtLocation(node.right); + if ( + (containsNullOrUndefined(leftType) || containsNullOrUndefined(rightType)) && + isEquality + ) { + return; + } + const leftKinds: TypeKind[] = getTypes(leftType); const rightKinds: TypeKind[] = getTypes(rightType); - const operandKind = getStrictestComparableType(leftKinds, rightKinds); if (operandKind === undefined) { @@ -143,7 +150,6 @@ function walk(ctx: Lint.WalkContext, program: ts.Program) { operandKind, node.operatorToken.getText(), ); - const isEquality = isEqualityOperator(node); if (isEquality) { // Check !=, ==, !==, === switch (operandKind) { @@ -186,6 +192,13 @@ function walk(ctx: Lint.WalkContext, program: ts.Program) { }); } +function containsNullOrUndefined(type: ts.Type) { + return ( + (type as ts.IntrinsicType).intrinsicName === "null" || + (type as ts.IntrinsicType).intrinsicName === "undefined" + ); +} + function getTypes(types: ts.Type): TypeKind[] { // Compatibility for TypeScript pre-2.4, which used EnumLiteralType instead of LiteralType const baseType = ((types as any) as { baseType: ts.LiteralType }).baseType; diff --git a/test/rules/strict-comparisons/allow-object-equal-comparison/test.ts.lint b/test/rules/strict-comparisons/allow-object-equal-comparison/test.ts.lint index 39bbd7e2f9a..1a939a87458 100644 --- a/test/rules/strict-comparisons/allow-object-equal-comparison/test.ts.lint +++ b/test/rules/strict-comparisons/allow-object-equal-comparison/test.ts.lint @@ -8,48 +8,45 @@ if (2 === 1) {} if (2 != 1) {} if (2 !== 1) {} if (2 > undefined) {} - ~~~~~~~~~~~~~ [Cannot compare type 'number' to type 'undefined'.] + ~~~~~~~~~~~~~ [CANNOT_COMPARE % ("number", "undefined")] if (undefined === 1) {} - ~~~~~~~~~~~~~~~ [Cannot compare type 'undefined' to type 'number'.] if (2 > undefined) {} - ~~~~~~~~~~~~~ [Cannot compare type 'number' to type 'undefined'.] -if (undefined === 1) {} - ~~~~~~~~~~~~~~~ [Cannot compare type 'undefined' to type 'number'.] + ~~~~~~~~~~~~~ [CANNOT_COMPARE % ("number", "undefined")] if (true) {} if (true > false) {} - ~~~~~~~~~~~~ [Cannot use '>' comparator for type 'boolean'.] + ~~~~~~~~~~~~ [CANNOT_USE % (">", "boolean")] if (true < false) {} - ~~~~~~~~~~~~ [Cannot use '<' comparator for type 'boolean'.] + ~~~~~~~~~~~~ [CANNOT_USE % ("<", "boolean")] if (true >= false) {} - ~~~~~~~~~~~~~ [Cannot use '>=' comparator for type 'boolean'.] + ~~~~~~~~~~~~~ [CANNOT_USE % (">=", "boolean")] if (true <= false) {} - ~~~~~~~~~~~~~ [Cannot use '<=' comparator for type 'boolean'.] + ~~~~~~~~~~~~~ [CANNOT_USE % ("<=", "boolean")] if (true == false) {} if (true === false) {} if (true != false) {} if (true !== false) {} if ('') {} if ('' > '') {} - ~~~~~~~ [Cannot use '>' comparator for type 'string'.] + ~~~~~~~ [CANNOT_USE % (">", "string")] if ('' < '') {} - ~~~~~~~ [Cannot use '<' comparator for type 'string'.] + ~~~~~~~ [CANNOT_USE % ("<", "string")] if ('' >= '') {} - ~~~~~~~~ [Cannot use '>=' comparator for type 'string'.] + ~~~~~~~~ [CANNOT_USE % (">=", "string")] if ('' <= '') {} - ~~~~~~~~ [Cannot use '<=' comparator for type 'string'.] + ~~~~~~~~ [CANNOT_USE % ("<=", "string")] if ('' == '') {} if ('' === '') {} if ('' != '') {} if ('' !== '') {} if ({}) {} if ({} > {}) {} - ~~~~~~~ [Cannot use '>' comparator for type 'object'.] + ~~~~~~~ [CANNOT_USE % (">", "object")] if ({} < {}) {} - ~~~~~~~ [Cannot use '<' comparator for type 'object'.] + ~~~~~~~ [CANNOT_USE % ("<", "object")] if ({} >= {}) {} - ~~~~~~~~ [Cannot use '>=' comparator for type 'object'.] + ~~~~~~~~ [CANNOT_USE % (">=", "object")] if ({} <= {}) {} - ~~~~~~~~ [Cannot use '<=' comparator for type 'object'.] + ~~~~~~~~ [CANNOT_USE % ("<=", "object")] if ({} == {}) {} if ({} === {}) {} if ({} != {}) {} @@ -58,11 +55,11 @@ if ([] === []) {} if (3 > 2 || 2 > 1 && true === true) {} if ('' > '' || 2 > 1 || {} > {}) {} - ~~~~~~~ [Cannot use '>' comparator for type 'string'.] - ~~~~~~~ [Cannot use '>' comparator for type 'object'.] + ~~~~~~~ [CANNOT_USE % (">", "string")] + ~~~~~~~ [CANNOT_USE % (">", "object")] if ('' > '' && 2 > 1 && {} > {}) {} - ~~~~~~~ [Cannot use '>' comparator for type 'string'.] - ~~~~~~~ [Cannot use '>' comparator for type 'object'.] + ~~~~~~~ [CANNOT_USE % (">", "string")] + ~~~~~~~ [CANNOT_USE % (">", "object")] if ({} === null) {} if (null === {}) {} @@ -134,9 +131,9 @@ const g1: TestStringEnum = TestStringEnum.One if (g1 === TestStringEnum.Two) {} if (TestStringEnum.Two === g1) {} if (g1 > TestStringEnum.Two) {} - ~~~~~~~~~~~~~~~~~~~~~~~ [Cannot use '>' comparator for type 'string'.] + ~~~~~~~~~~~~~~~~~~~~~~~ [CANNOT_USE % (">", "string")] if (TestStringEnum.Two > g1) {} - ~~~~~~~~~~~~~~~~~~~~~~~ [Cannot use '>' comparator for type 'string'.] + ~~~~~~~~~~~~~~~~~~~~~~~ [CANNOT_USE % (">", "string")] #endif const h1: string | number = Math.random() > 0.5 ? 'text' : 5; @@ -147,3 +144,39 @@ if (h2 > h1) {} ~~~~~~~ [Cannot use '>' comparator for type 'string'.] if (h1 === h2) {} if (h2 === h1) {} + +if (undefined === null) {} +if (null !== undefined) {} + +if (1 > null) {} + ~~~~~~~~ [CANNOT_COMPARE % ("number", "null")] +if (null > 1) {} + ~~~~~~~~ [CANNOT_COMPARE % ("null", "number")] +if (1 >= undefined) {} + ~~~~~~~~~~~~~~ [CANNOT_COMPARE % ("number", "undefined")] +if (undefined >= 1) {} + ~~~~~~~~~~~~~~ [CANNOT_COMPARE % ("undefined", "number")] +if (undefined <= null) {} + ~~~~~~~~~~~~~~~~~ [CANNOT_COMPARE % ("undefined", "null")] +if (null >= undefined) {} + ~~~~~~~~~~~~~~~~~ [CANNOT_COMPARE % ("null", "undefined")] + +const func = (param?: boolean): boolean => { + return param === undefined ? false : param; +} +const func = (param?: boolean): boolean => { + return param === null ? false : true; +} +const func = (param: number): number => { + return param + 1; +} +const func = (arg1: number, arg2: number): boolean => { + return arg1 === arg2; +} +const func = (param: string | null = null) => { + if (param !== null) {} + if (param !== undefined) {} +} + +[CANNOT_USE]: Cannot use '%s' comparator for type '%s'. +[CANNOT_COMPARE]: Cannot compare type '%s' to type '%s'. diff --git a/test/rules/strict-comparisons/allow-string-order-comparison/test.ts.lint b/test/rules/strict-comparisons/allow-string-order-comparison/test.ts.lint index 6785829b3e4..30c5405f0d5 100644 --- a/test/rules/strict-comparisons/allow-string-order-comparison/test.ts.lint +++ b/test/rules/strict-comparisons/allow-string-order-comparison/test.ts.lint @@ -8,22 +8,19 @@ if (2 === 1) {} if (2 != 1) {} if (2 !== 1) {} if (2 > undefined) {} - ~~~~~~~~~~~~~ [Cannot compare type 'number' to type 'undefined'.] + ~~~~~~~~~~~~~ [CANNOT_COMPARE % ("number", "undefined")] if (undefined === 1) {} - ~~~~~~~~~~~~~~~ [Cannot compare type 'undefined' to type 'number'.] if (2 > undefined) {} - ~~~~~~~~~~~~~ [Cannot compare type 'number' to type 'undefined'.] -if (undefined === 1) {} - ~~~~~~~~~~~~~~~ [Cannot compare type 'undefined' to type 'number'.] + ~~~~~~~~~~~~~ [CANNOT_COMPARE % ("number", "undefined")] if (true) {} if (true > false) {} - ~~~~~~~~~~~~ [Cannot use '>' comparator for type 'boolean'.] + ~~~~~~~~~~~~ [CANNOT_USE % (">", "boolean")] if (true < false) {} - ~~~~~~~~~~~~ [Cannot use '<' comparator for type 'boolean'.] + ~~~~~~~~~~~~ [CANNOT_USE % ("<", "boolean")] if (true >= false) {} - ~~~~~~~~~~~~~ [Cannot use '>=' comparator for type 'boolean'.] + ~~~~~~~~~~~~~ [CANNOT_USE % (">=", "boolean")] if (true <= false) {} - ~~~~~~~~~~~~~ [Cannot use '<=' comparator for type 'boolean'.] + ~~~~~~~~~~~~~ [CANNOT_USE % ("<=", "boolean")] if (true == false) {} if (true === false) {} if (true != false) {} @@ -39,42 +36,38 @@ if ('' != '') {} if ('' !== '') {} if ({}) {} if ({} > {}) {} - ~~~~~~~ [Cannot use '>' comparator for type 'object'.] + ~~~~~~~ [CANNOT_USE % (">", "object")] if ({} < {}) {} - ~~~~~~~ [Cannot use '<' comparator for type 'object'.] + ~~~~~~~ [CANNOT_USE % ("<", "object")] if ({} >= {}) {} - ~~~~~~~~ [Cannot use '>=' comparator for type 'object'.] + ~~~~~~~~ [CANNOT_USE % (">=", "object")] if ({} <= {}) {} - ~~~~~~~~ [Cannot use '<=' comparator for type 'object'.] + ~~~~~~~~ [CANNOT_USE % ("<=", "object")] if ({} == {}) {} - ~~~~~~~~ [Cannot use '==' comparator for type 'object'.] + ~~~~~~~~ [CANNOT_USE % ("==", "object")] if ({} === {}) {} - ~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] + ~~~~~~~~~ [CANNOT_USE % ("===", "object")] if ({} != {}) {} - ~~~~~~~~ [Cannot use '!=' comparator for type 'object'.] + ~~~~~~~~ [CANNOT_USE % ("!=", "object")] if ({} !== {}) {} - ~~~~~~~~~ [Cannot use '!==' comparator for type 'object'.] + ~~~~~~~~~ [CANNOT_USE % ("!==", "object")] if ([] === []) {} - ~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] + ~~~~~~~~~ [CANNOT_USE % ("===", "object")] if (3 > 2 || 2 > 1 && true === true) {} if ('' > '' || 2 > 1 || {} > {}) {} - ~~~~~~~ [Cannot use '>' comparator for type 'object'.] + ~~~~~~~ [CANNOT_USE % (">", "object")] if ('' > '' && 2 > 1 && {} > {}) {} - ~~~~~~~ [Cannot use '>' comparator for type 'object'.] + ~~~~~~~ [CANNOT_USE % (">", "object")] if ({} === null) {} - ~~~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] if (null === {}) {} - ~~~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] if ({} === undefined) {} - ~~~~~~~~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] if (undefined === {}) {} - ~~~~~~~~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] function sameObject(a: T, b: T): boolean { return a === b; - ~~~~~~~ [Cannot use '===' comparator for type 'object'.] + ~~~~~~~ [CANNOT_USE % ("===", "object")] } function sameObject(a: any, b: any): boolean { @@ -100,9 +93,9 @@ const c1: myObject = {} const c2: myObject = {} if (c1 === c2) {} - ~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] + ~~~~~~~~~ [CANNOT_USE % ("===", "object")] if (c2 === c1) {} - ~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] + ~~~~~~~~~ [CANNOT_USE % ("===", "object")] const d1: any = 'string' const d2: any = 2 @@ -147,3 +140,39 @@ if (h1 > h2) {} if (h2 > h1) {} if (h1 === h2) {} if (h2 === h1) {} + +if (undefined === null) {} +if (null !== undefined) {} + +if (1 > null) {} + ~~~~~~~~ [CANNOT_COMPARE % ("number", "null")] +if (null > 1) {} + ~~~~~~~~ [CANNOT_COMPARE % ("null", "number")] +if (1 >= undefined) {} + ~~~~~~~~~~~~~~ [CANNOT_COMPARE % ("number", "undefined")] +if (undefined >= 1) {} + ~~~~~~~~~~~~~~ [CANNOT_COMPARE % ("undefined", "number")] +if (undefined <= null) {} + ~~~~~~~~~~~~~~~~~ [CANNOT_COMPARE % ("undefined", "null")] +if (null >= undefined) {} + ~~~~~~~~~~~~~~~~~ [CANNOT_COMPARE % ("null", "undefined")] + +const func = (param?: boolean): boolean => { + return param === undefined ? false : param; +} +const func = (param?: boolean): boolean => { + return param === null ? false : true; +} +const func = (param: number): number => { + return param + 1; +} +const func = (arg1: number, arg2: number): boolean => { + return arg1 === arg2; +} +const func = (param: string | null = null) => { + if (param !== null) {} + if (param !== undefined) {} +} + +[CANNOT_USE]: Cannot use '%s' comparator for type '%s'. +[CANNOT_COMPARE]: Cannot compare type '%s' to type '%s'. diff --git a/test/rules/strict-comparisons/default/test.ts.lint b/test/rules/strict-comparisons/default/test.ts.lint index 412e09f8eea..8a1a7a83744 100644 --- a/test/rules/strict-comparisons/default/test.ts.lint +++ b/test/rules/strict-comparisons/default/test.ts.lint @@ -8,79 +8,72 @@ if (2 === 1) {} if (2 != 1) {} if (2 !== 1) {} if (2 > undefined) {} - ~~~~~~~~~~~~~ [Cannot compare type 'number' to type 'undefined'.] + ~~~~~~~~~~~~~ [CANNOT_COMPARE % ("number", "undefined")] if (undefined === 1) {} - ~~~~~~~~~~~~~~~ [Cannot compare type 'undefined' to type 'number'.] if (2 > undefined) {} - ~~~~~~~~~~~~~ [Cannot compare type 'number' to type 'undefined'.] -if (undefined === 1) {} - ~~~~~~~~~~~~~~~ [Cannot compare type 'undefined' to type 'number'.] + ~~~~~~~~~~~~~ [CANNOT_COMPARE % ("number", "undefined")] if (true) {} if (true > false) {} - ~~~~~~~~~~~~ [Cannot use '>' comparator for type 'boolean'.] + ~~~~~~~~~~~~ [CANNOT_USE % (">", "boolean")] if (true < false) {} - ~~~~~~~~~~~~ [Cannot use '<' comparator for type 'boolean'.] + ~~~~~~~~~~~~ [CANNOT_USE % ("<", "boolean")] if (true >= false) {} - ~~~~~~~~~~~~~ [Cannot use '>=' comparator for type 'boolean'.] + ~~~~~~~~~~~~~ [CANNOT_USE % (">=", "boolean")] if (true <= false) {} - ~~~~~~~~~~~~~ [Cannot use '<=' comparator for type 'boolean'.] + ~~~~~~~~~~~~~ [CANNOT_USE % ("<=", "boolean")] if (true == false) {} if (true === false) {} if (true != false) {} if (true !== false) {} if ('') {} if ('' > '') {} - ~~~~~~~ [Cannot use '>' comparator for type 'string'.] + ~~~~~~~ [CANNOT_USE % (">", "string")] if ('' < '') {} - ~~~~~~~ [Cannot use '<' comparator for type 'string'.] + ~~~~~~~ [CANNOT_USE % ("<", "string")] if ('' >= '') {} - ~~~~~~~~ [Cannot use '>=' comparator for type 'string'.] + ~~~~~~~~ [CANNOT_USE % (">=", "string")] if ('' <= '') {} - ~~~~~~~~ [Cannot use '<=' comparator for type 'string'.] + ~~~~~~~~ [CANNOT_USE % ("<=", "string")] if ('' == '') {} if ('' === '') {} if ('' != '') {} if ('' !== '') {} if ({}) {} if ({} > {}) {} - ~~~~~~~ [Cannot use '>' comparator for type 'object'.] + ~~~~~~~ [CANNOT_USE % (">", "object")] if ({} < {}) {} - ~~~~~~~ [Cannot use '<' comparator for type 'object'.] + ~~~~~~~ [CANNOT_USE % ("<", "object")] if ({} >= {}) {} - ~~~~~~~~ [Cannot use '>=' comparator for type 'object'.] + ~~~~~~~~ [CANNOT_USE % (">=", "object")] if ({} <= {}) {} - ~~~~~~~~ [Cannot use '<=' comparator for type 'object'.] + ~~~~~~~~ [CANNOT_USE % ("<=", "object")] if ({} == {}) {} - ~~~~~~~~ [Cannot use '==' comparator for type 'object'.] + ~~~~~~~~ [CANNOT_USE % ("==", "object")] if ({} === {}) {} - ~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] + ~~~~~~~~~ [CANNOT_USE % ("===", "object")] if ({} != {}) {} - ~~~~~~~~ [Cannot use '!=' comparator for type 'object'.] + ~~~~~~~~ [CANNOT_USE % ("!=", "object")] if ({} !== {}) {} - ~~~~~~~~~ [Cannot use '!==' comparator for type 'object'.] + ~~~~~~~~~ [CANNOT_USE % ("!==", "object")] if ([] === []) {} - ~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] + ~~~~~~~~~ [CANNOT_USE % ("===", "object")] if (3 > 2 || 2 > 1 && true === true) {} if ('' > '' || 2 > 1 || {} > {}) {} - ~~~~~~~ [Cannot use '>' comparator for type 'string'.] - ~~~~~~~ [Cannot use '>' comparator for type 'object'.] + ~~~~~~~ [CANNOT_USE % (">", "string")] + ~~~~~~~ [CANNOT_USE % (">", "object")] if ('' > '' && 2 > 1 && {} > {}) {} - ~~~~~~~ [Cannot use '>' comparator for type 'string'.] - ~~~~~~~ [Cannot use '>' comparator for type 'object'.] + ~~~~~~~ [CANNOT_USE % (">", "string")] + ~~~~~~~ [CANNOT_USE % (">", "object")] if ({} === null) {} - ~~~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] if (null === {}) {} - ~~~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] if ({} === undefined) {} - ~~~~~~~~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] if (undefined === {}) {} - ~~~~~~~~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] function sameObject(a: T, b: T): boolean { return a === b; - ~~~~~~~ [Cannot use '===' comparator for type 'object'.] + ~~~~~~~ [CANNOT_USE % ("===", "object")] } function sameObject(a: any, b: any): boolean { @@ -106,9 +99,9 @@ const c1: myObject = {} const c2: myObject = {} if (c1 === c2) {} - ~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] + ~~~~~~~~~ [CANNOT_USE % ("===", "object")] if (c2 === c1) {} - ~~~~~~~~~ [Cannot use '===' comparator for type 'object'.] + ~~~~~~~~~ [CANNOT_USE % ("===", "object")] const d1: any = 'string' const d2: any = 2 @@ -146,16 +139,52 @@ const g1: TestStringEnum = TestStringEnum.One if (g1 === TestStringEnum.Two) {} if (TestStringEnum.Two === g1) {} if (g1 > TestStringEnum.Two) {} - ~~~~~~~~~~~~~~~~~~~~~~~ [Cannot use '>' comparator for type 'string'.] + ~~~~~~~~~~~~~~~~~~~~~~~ [CANNOT_USE % (">", "string")] if (TestStringEnum.Two > g1) {} - ~~~~~~~~~~~~~~~~~~~~~~~ [Cannot use '>' comparator for type 'string'.] + ~~~~~~~~~~~~~~~~~~~~~~~ [CANNOT_USE % (">", "string")] #endif const h1: string | number = Math.random() > 0.5 ? 'text' : 5; const h2: string | number = Math.random() > 0.5 ? 'test' : 2; if (h1 > h2) {} - ~~~~~~~ [Cannot use '>' comparator for type 'string'.] + ~~~~~~~ [CANNOT_USE % (">", "string")] if (h2 > h1) {} - ~~~~~~~ [Cannot use '>' comparator for type 'string'.] + ~~~~~~~ [CANNOT_USE % (">", "string")] if (h1 === h2) {} if (h2 === h1) {} + +if (undefined === null) {} +if (null !== undefined) {} + +if (1 > null) {} + ~~~~~~~~ [CANNOT_COMPARE % ("number", "null")] +if (null > 1) {} + ~~~~~~~~ [CANNOT_COMPARE % ("null", "number")] +if (1 >= undefined) {} + ~~~~~~~~~~~~~~ [CANNOT_COMPARE % ("number", "undefined")] +if (undefined >= 1) {} + ~~~~~~~~~~~~~~ [CANNOT_COMPARE % ("undefined", "number")] +if (undefined <= null) {} + ~~~~~~~~~~~~~~~~~ [CANNOT_COMPARE % ("undefined", "null")] +if (null >= undefined) {} + ~~~~~~~~~~~~~~~~~ [CANNOT_COMPARE % ("null", "undefined")] + +const func = (param?: boolean): boolean => { + return param === undefined ? false : param; +} +const func = (param?: boolean): boolean => { + return param === null ? false : true; +} +const func = (param: number): number => { + return param + 1; +} +const func = (arg1: number, arg2: number): boolean => { + return arg1 === arg2; +} +const func = (param: string | null = null) => { + if (param !== null) {} + if (param !== undefined) {} +} + +[CANNOT_USE]: Cannot use '%s' comparator for type '%s'. +[CANNOT_COMPARE]: Cannot compare type '%s' to type '%s'.