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

strict-type-predicates: warn if strictNullChecks is not enabled #2786

Merged
merged 4 commits into from May 31, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/language/utils.ts
Expand Up @@ -425,3 +425,8 @@ export function getEqualsKind(node: ts.BinaryOperatorToken): EqualsKind | undefi
return undefined;
}
}

export function isStrictNullChecksEnabled(options: ts.CompilerOptions): boolean {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

is this the right file for this function?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's okay. We'll probably have to split apart this file some time in the future

return options.strictNullChecks === true ||
(options.strict === true && options.strictNullChecks !== false);
}
2 changes: 1 addition & 1 deletion src/rules/strictBooleanExpressionsRule.ts
Expand Up @@ -74,7 +74,7 @@ export class Rule extends Lint.Rules.TypedRule {
};

public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
const options = parseOptions(this.ruleArguments, program.getCompilerOptions().strictNullChecks === true);
const options = parseOptions(this.ruleArguments, Lint.isStrictNullChecksEnabled(program.getCompilerOptions()));
return this.applyWithFunction(sourceFile, (ctx: Lint.WalkContext<Options>) => walk(ctx, program.getTypeChecker()), options);
}
}
Expand Down
9 changes: 8 additions & 1 deletion src/rules/strictTypePredicatesRule.ts
Expand Up @@ -18,6 +18,7 @@
import { isBinaryExpression, isUnionType } from "tsutils";

import * as ts from "typescript";
import { showWarningOnce } from "../error";
import * as Lint from "../index";

// tslint:disable:no-bitwise
Expand All @@ -31,7 +32,9 @@ export class Rule extends Lint.Rules.TypedRule {
Works for 'typeof' comparisons to constants (e.g. 'typeof foo === "string"'), and equality comparison to 'null'/'undefined'.
(TypeScript won't let you compare '1 === 2', but it has an exception for '1 === undefined'.)
Does not yet work for 'instanceof'.
Does *not* warn for 'if (x.y)' where 'x.y' is always truthy. For that, see strict-boolean-expressions.`,
Does *not* warn for 'if (x.y)' where 'x.y' is always truthy. For that, see strict-boolean-expressions.

This rule requires \`strictNullChecks\` to work properly.`,
optionsDescription: "Not configurable.",
options: null,
optionExamples: [true],
Expand All @@ -52,6 +55,10 @@ export class Rule extends Lint.Rules.TypedRule {
}

public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
if (!Lint.isStrictNullChecksEnabled(program.getCompilerOptions())) {
showWarningOnce("strict-type-predicates does not work without strictNullChecks");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please suggest a better warning message.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's fine. Would probably be more clear if it said tsc option --strictNullChecks, but if you search for strictNullChecks, the first hit is correct

return [];
}
return this.applyWithFunction(sourceFile, (ctx) => walk(ctx, program.getTypeChecker()));
}
}
Expand Down
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"module": "commonjs"
"strict": true,
"strictNullChecks": false
}
}
@@ -1,5 +1,5 @@
{
"compilerOptions": {
"module": "commonjs"
"strictNullChecks": false
}
}
156 changes: 156 additions & 0 deletions test/rules/strict-type-predicates/no-strict-null-checks/test.ts.lint
@@ -0,0 +1,156 @@
declare function get<T>(): T;

// typeof undefined
{
typeof get<boolean>() === "undefined";

typeof get<void>() === "undefined";

typeof get<boolean | undefined>() === "undefined";

declare const c: any;
typeof get<any>() === "undefined";

// 'undefined' is not assignable to '{}'
typeof get<{}>() === "undefined";
}

// typeof boolean
{
declare const a: boolean;
typeof get<boolean>() === "boolean";

typeof get<Boolean>() === "boolean";

typeof get<boolean | undefined>() === "boolean";

typeof get<{}>() === "boolean";
}

// typeof number
{
enum E {}

typeof get<E>() === "number";

typeof get<Number>() === "number";
}

// typeof string
{
typeof get<"abc" | "def">() === "string";

typeof get<String>() === "string";

typeof get<"abc" | undefined>() === "string";
}

// typeof symbol
{
typeof get<symbol>() === "symbol";

typeof get<string>() === "symbol";

typeof get<symbol | string>() === "symbol";
}

// typeof function
{
typeof get<() => void>() === "function";

typeof get<Function>() === "function";


typeof get<number>() === "function";

typeof get<number | (() => void)>() === "function";

class X {}
typeof X === "function";
typeof X === "object";

// Works with union
class Foo { }
typeof get<typeof Foo | object> === "function";
}

// typeof object
{
typeof get<boolean | number | string | symbol | (() => void) | Function>() === "object";

typeof get<{}> === "object";
}

// === null / undefined
{
get<number | undefined>() === null;

get<number | null>() === null;

get<number | null>() === undefined;

get<number | undefined>() === undefined;

// 'null' and 'undefined' are not assignable to '{}'

get<{}>() === null;

get<{}>() === undefined;

get<string | undefined>() == null;

get<string | null>() == undefined;

get<string | null>() == null;

get<string | undefined>() == undefined;

get<string | undefined>() != null;

get<{}>() == null;

get<string | null | undefined>() == null;
get<string | null | undefined>() != undefined;

get<null|undefined>() == null;
get<null|undefined>() != undefined;
}

// negation
{
get<number | undefined>() !== null;

get<number | null>() !== null;

get<number | null>() !== undefined;

get<number | undefined>() !== undefined;

typeof get<string>() !== "string";
}

// reverse left/right
{
"string" === typeof get<number>();

undefined === get<void>();
}

// type parameters
{
function f<T>(t: T) {
typeof t === "boolean";
}

// TODO: Would be nice to catch this.
function g<T extends string>(t: T) {
typeof t === "boolean";
}
}

// Detects bad typeof
{
typeof get<boolean>() === true;

typeof get<any>() === "orbject";
}
@@ -0,0 +1,5 @@
{
"compilerOptions": {
// strictNullChecks not enabled
}
}
@@ -0,0 +1,5 @@
{
"rules": {
"strict-type-predicates": true
}
}