diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index f2418485d52..006ef82f515 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -147,6 +147,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string | | | :thought_balloon: | | [`@typescript-eslint/restrict-template-expressions`](./docs/rules/restrict-template-expressions.md) | Enforce template literal expressions to be of string type | | | :thought_balloon: | | [`@typescript-eslint/strict-boolean-expressions`](./docs/rules/strict-boolean-expressions.md) | Restricts the types allowed in boolean expressions | | | :thought_balloon: | +| [`@typescript-eslint/strict-type-predicates`](./docs/rules/strict-type-predicates.md) | Disallow always true (or false) type predicates | | | :thought_balloon: | | [`@typescript-eslint/triple-slash-reference`](./docs/rules/triple-slash-reference.md) | Sets preference level for triple slash directives versus ES6-style import declarations | :heavy_check_mark: | | | | [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/typedef`](./docs/rules/typedef.md) | Requires type annotations to exist | | | | diff --git a/packages/eslint-plugin/ROADMAP.md b/packages/eslint-plugin/ROADMAP.md index 20661b22ea0..88f55064ef0 100644 --- a/packages/eslint-plugin/ROADMAP.md +++ b/packages/eslint-plugin/ROADMAP.md @@ -97,7 +97,7 @@ It lists all TSLint rules along side rules from the ESLint ecosystem that are th | [`radix`] | 🌟 | [`radix`][radix] | | [`restrict-plus-operands`] | βœ… | [`@typescript-eslint/restrict-plus-operands`] | | [`strict-boolean-expressions`] | βœ… | [`@typescript-eslint/strict-boolean-expressions`] | -| [`strict-type-predicates`] | πŸ›‘ | N/A | +| [`strict-type-predicates`] | βœ… | [`@typescript-eslint/strict-type-predicates`] | | [`switch-default`] | 🌟 | [`default-case`][default-case] | | [`triple-equals`] | 🌟 | [`eqeqeq`][eqeqeq] | | [`typeof-compare`] | 🌟 | [`valid-typeof`][valid-typeof] | diff --git a/packages/eslint-plugin/docs/rules/strict-type-predicates.md b/packages/eslint-plugin/docs/rules/strict-type-predicates.md new file mode 100644 index 00000000000..bb84ae9f928 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/strict-type-predicates.md @@ -0,0 +1,56 @@ +# Disallow always true (or false) type predicates (`strict-type-predicates`) + +Warns for type predicates that are always true or always false. 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`](./strict-boolean-expressions.md). + +This rule requires `strictNullChecks` to be enabled. + +Examples of **incorrect** code for this rule: + +```ts +const numberOrNull: number | null = 0; +// Implicitly checks for `!== undefined`, which is always false. +if (numberOrNull != null) { + return; +} + +const numberOrUndefined: number | undefined = 0; +// Implicitly checks for `!== null`, which is always false. +if (numberOrNull != undefined) { + return; +} + +const number: number = 0; +// Always false. +if (typeof number === 'string') { + return; +} +``` + +Examples of **correct** code for this rule: + +```ts +const numberOrNull: number | null = 0; +if (numberOrNull !== null) { + return; +} + +const numberOrUndefined: number | undefined = 0; +if (numberOrNull !== undefined) { + return; +} + +const number: number = 0; +// Always false. +if (typeof number === 'number') { + return; +} +``` + +## Related To + +- TSLint: [strict-type-predicates](https://palantir.github.io/tslint/rules/strict-type-predicates) diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index 91380db1932..45a25ef5bd6 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -89,6 +89,7 @@ "space-before-function-paren": "off", "@typescript-eslint/space-before-function-paren": "error", "@typescript-eslint/strict-boolean-expressions": "error", + "@typescript-eslint/strict-type-predicates": "error", "@typescript-eslint/triple-slash-reference": "error", "@typescript-eslint/type-annotation-spacing": "error", "@typescript-eslint/typedef": "error", diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index ac12f27e8ba..feb33767f54 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -74,6 +74,7 @@ import returnAwait from './return-await'; import semi from './semi'; import spaceBeforeFunctionParen from './space-before-function-paren'; import strictBooleanExpressions from './strict-boolean-expressions'; +import strictTypePredicates from './strict-type-predicates'; import tripleSlashReference from './triple-slash-reference'; import typeAnnotationSpacing from './type-annotation-spacing'; import typedef from './typedef'; @@ -157,6 +158,7 @@ export default { semi: semi, 'space-before-function-paren': spaceBeforeFunctionParen, 'strict-boolean-expressions': strictBooleanExpressions, + 'strict-type-predicates': strictTypePredicates, 'triple-slash-reference': tripleSlashReference, 'type-annotation-spacing': typeAnnotationSpacing, typedef: typedef, diff --git a/packages/eslint-plugin/src/rules/strict-type-predicates.ts b/packages/eslint-plugin/src/rules/strict-type-predicates.ts new file mode 100644 index 00000000000..f6e748aa3ef --- /dev/null +++ b/packages/eslint-plugin/src/rules/strict-type-predicates.ts @@ -0,0 +1,346 @@ +import { TSESTree } from '@typescript-eslint/experimental-utils'; +import * as ts from 'typescript'; +import * as util from '../util'; +import { + isIdentifier, + isLiteralExpression, + isTypeFlagSet, + isUnionType, + isStrictCompilerOptionEnabled, +} from 'tsutils'; + +export default util.createRule({ + name: 'strict-type-predicates', + meta: { + type: 'suggestion', + docs: { + description: 'Disallow always true (or false) type predicates', + category: 'Best Practices', + recommended: false, + requiresTypeChecking: true, + }, + messages: { + expressionAlwaysFalse: 'Expression is always false.', + expressionAlwaysTrue: 'Expression is always true.', + badTypeof: "Bad comparison for 'typeof'.", + useStrictlyUndefined: "Use '=== undefined' instead.", + useStrictlyNotUndefined: "Use '=== undefined' instead.", + useStrictlyNull: "Use '=== null' instead.", + useStrictlyNotNull: "Use '!== null' instead.", + }, + schema: [], + }, + defaultOptions: [{}], + create(context) { + const parserServices = util.getParserServices(context); + const checker = parserServices.program.getTypeChecker(); + const compilerOptions = parserServices.program.getCompilerOptions(); + + function checkEquals( + node: ts.BinaryExpression, + esNode: TSESTree.Node, + { isStrict, isPositive }: EqualsKind, + ): void { + const exprPred = getTypePredicate(node, isStrict); + if (exprPred === undefined) { + return; + } + + if (exprPred.kind === TypePredicateKind.TypeofTypo) { + context.report({ + node: esNode, + messageId: 'badTypeof', + }); + return; + } + + const exprType = checker.getTypeAtLocation(exprPred.expression); + // TODO: could use checker.getBaseConstraintOfType to help with type parameters, but it's not publicly exposed. + if ( + isTypeFlagSet( + exprType, + ts.TypeFlags.Any | ts.TypeFlags.TypeParameter | ts.TypeFlags.Unknown, + ) + ) { + return; + } + + switch (exprPred.kind) { + case TypePredicateKind.Plain: { + const { predicate, isNullOrUndefined } = exprPred; + const value = getConstantBoolean(exprType, predicate); + // 'null'/'undefined' are the only two values *not* assignable to '{}'. + if ( + value !== undefined && + (isNullOrUndefined || !isEmptyType(checker, exprType)) + ) { + context.report({ + node: esNode, + messageId: + value === isPositive + ? 'expressionAlwaysTrue' + : 'expressionAlwaysFalse', + }); + } + break; + } + + case TypePredicateKind.NonStructNullUndefined: { + const result = testNonStrictNullUndefined(exprType); + if (result !== undefined) { + context.report({ + node: esNode, + messageId: + typeof result === 'boolean' + ? result === isPositive + ? 'expressionAlwaysTrue' + : 'expressionAlwaysFalse' + : result === 'null' + ? isPositive + ? 'useStrictlyNull' + : 'useStrictlyNotNull' + : isPositive + ? 'useStrictlyUndefined' + : 'useStrictlyNotUndefined', + }); + } + } + } + } + + /** Detects a type predicate given `left === right`. */ + function getTypePredicate( + node: ts.BinaryExpression, + isStrictEquals: boolean, + ): TypePredicate | undefined { + const { left, right } = node; + const lr = getTypePredicateOneWay(left, right, isStrictEquals); + return lr !== undefined + ? lr + : getTypePredicateOneWay(right, left, isStrictEquals); + } + + /** Only gets the type predicate if the expression is on the left. */ + function getTypePredicateOneWay( + left: ts.Expression, + right: ts.Expression, + isStrictEquals: boolean, + ): TypePredicate | undefined { + switch (right.kind) { + case ts.SyntaxKind.TypeOfExpression: { + const expression = (right as ts.TypeOfExpression).expression; + if (!isLiteralExpression(left)) { + if ( + (isIdentifier(left) && left.text === 'undefined') || + left.kind === ts.SyntaxKind.NullKeyword || + left.kind === ts.SyntaxKind.TrueKeyword || + left.kind === ts.SyntaxKind.FalseKeyword + ) { + return { kind: TypePredicateKind.TypeofTypo }; + } + return undefined; + } + const predicate = getTypePredicateForKind(left.text); + return predicate === undefined + ? { kind: TypePredicateKind.TypeofTypo } + : { + expression, + isNullOrUndefined: left.text === 'undefined', + kind: TypePredicateKind.Plain, + predicate, + }; + } + + case ts.SyntaxKind.NullKeyword: + return nullOrUndefined(ts.TypeFlags.Null); + + case ts.SyntaxKind.Identifier: + if ( + (right as ts.Identifier).originalKeywordKind === + ts.SyntaxKind.UndefinedKeyword + ) { + return nullOrUndefined(undefinedFlags); + } + return undefined; + default: + return undefined; + } + + function nullOrUndefined(flags: ts.TypeFlags): TypePredicate { + return isStrictEquals + ? { + expression: left, + isNullOrUndefined: true, + kind: TypePredicateKind.Plain, + predicate: flagPredicate(flags), + } + : { + kind: TypePredicateKind.NonStructNullUndefined, + expression: left, + }; + } + } + + return isStrictCompilerOptionEnabled(compilerOptions, 'strictNullChecks') + ? { + BinaryExpression(node: TSESTree.BinaryExpression): void { + const equals = getEqualsKind(node.operator); + if (equals !== undefined) { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + checkEquals(tsNode, node, equals); + } + }, + } + : // TODO: emit warning that strictNullChecks is required + {}; + }, +}); + +function isEmptyType(checker: ts.TypeChecker, type: ts.Type): boolean { + return checker.typeToString(type) === '{}'; +} + +const undefinedFlags = ts.TypeFlags.Undefined | ts.TypeFlags.Void; + +type TypePredicate = + | PlainTypePredicate + | NonStrictNullUndefinedPredicate + | { kind: TypePredicateKind.TypeofTypo }; +interface PlainTypePredicate { + kind: TypePredicateKind.Plain; + expression: ts.Expression; + predicate: Predicate; + isNullOrUndefined: boolean; +} + +/** For `== null` and the like. */ +interface NonStrictNullUndefinedPredicate { + kind: TypePredicateKind.NonStructNullUndefined; + expression: ts.Expression; +} +const enum TypePredicateKind { + Plain, + NonStructNullUndefined, + TypeofTypo, +} +type Predicate = (type: ts.Type) => boolean; + +export interface EqualsKind { + isPositive: boolean; // True for "===" and "==" + isStrict: boolean; // True for "===" and "!==" +} + +export function getEqualsKind(operator: string): EqualsKind | undefined { + switch (operator) { + case '==': + return { isPositive: true, isStrict: false }; + case '===': + return { isPositive: true, isStrict: true }; + case '!=': + return { isPositive: false, isStrict: false }; + case '!==': + return { isPositive: false, isStrict: true }; + default: + return undefined; + } +} + +function unionParts(type: ts.Type): ts.Type[] { + return isUnionType(type) ? type.types : [type]; +} + +function flagPredicate(testedFlag: ts.TypeFlags): Predicate { + return (type): boolean => isTypeFlagSet(type, testedFlag); +} + +function getTypePredicateForKind(kind: string): Predicate | undefined { + switch (kind) { + case 'undefined': + return flagPredicate(undefinedFlags); + case 'boolean': + return flagPredicate(ts.TypeFlags.BooleanLike); + case 'number': + return flagPredicate(ts.TypeFlags.NumberLike); + case 'string': + return flagPredicate(ts.TypeFlags.StringLike); + case 'symbol': + return flagPredicate(ts.TypeFlags.ESSymbol); + case 'function': + return isFunction; + case 'object': { + // It's an object if it's not any of the above. + const allFlags = + ts.TypeFlags.Undefined | + ts.TypeFlags.Void | + ts.TypeFlags.BooleanLike | + ts.TypeFlags.NumberLike | + ts.TypeFlags.StringLike | + ts.TypeFlags.ESSymbol; + return (type): boolean => + !isTypeFlagSet(type, allFlags) && !isFunction(type); + } + default: + return undefined; + } +} + +function isFunction(t: ts.Type): boolean { + if ( + t.getConstructSignatures().length !== 0 || + t.getCallSignatures().length !== 0 + ) { + return true; + } + const symbol = t.getSymbol(); + return symbol !== undefined && symbol.getName() === 'Function'; +} + +/** Returns bool for always/never true, or a string to recommend strict equality. */ +function testNonStrictNullUndefined( + type: ts.Type, +): boolean | 'null' | 'undefined' | undefined { + let anyNull = false; + let anyUndefined = false; + let anyOther = false; + for (const ty of unionParts(type)) { + if (isTypeFlagSet(ty, ts.TypeFlags.Null)) { + anyNull = true; + } else if (isTypeFlagSet(ty, undefinedFlags)) { + anyUndefined = true; + } else { + anyOther = true; + } + } + + return !anyOther + ? true + : anyNull && anyUndefined + ? undefined + : anyNull + ? 'null' + : anyUndefined + ? 'undefined' + : false; +} + +/** Returns a boolean value if that should always be the result of a type predicate. */ +function getConstantBoolean( + type: ts.Type, + predicate: (t: ts.Type) => boolean, +): boolean | undefined { + let anyTrue = false; + let anyFalse = false; + for (const ty of unionParts(type)) { + if (predicate(ty)) { + anyTrue = true; + } else { + anyFalse = true; + } + + if (anyTrue && anyFalse) { + return undefined; + } + } + + return anyTrue; +} diff --git a/packages/eslint-plugin/tests/rules/strict-type-predicates.test.ts b/packages/eslint-plugin/tests/rules/strict-type-predicates.test.ts new file mode 100644 index 00000000000..22728faa590 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/strict-type-predicates.test.ts @@ -0,0 +1,626 @@ +import path from 'path'; +import rule from '../../src/rules/strict-type-predicates'; +import { RuleTester } from '../RuleTester'; + +const rootDir = path.resolve(__dirname, '../fixtures/'); +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2015, + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('strict-type-predicates', rule, { + valid: [ + ` +declare function get(): T; + +// typeof undefined +typeof get() === "undefined"; +typeof get() === "undefined"; + +// typeof boolean +typeof get() === "boolean"; +typeof get<{}>() === "boolean"; + +// typeof string +typeof get<"abc" | undefined>() === "string"; + +// typeof symbol +typeof get() === "symbol"; + +// typeof function +{ + typeof get void)>() === "function"; + + // Works with union + class Foo { } + typeof get() === "function"; +} + +// typeof object +typeof get<{}>() === "object"; + +// === null / undefined +// get() === null; +// get() === undefined; +// get() == null; +// get() != undefined; + +// negation +get() !== null; +get() !== undefined; +get() !== null; +get() !== undefined; + +// type parameters +{ + function f(t: T) { + typeof t === "boolean"; + } + + // TODO: Would be nice to catch this. + function g(t: T) { + typeof t === "boolean"; + } + + function f(t: T) { + typeof t === "boolean"; + } +} + +// Detects bad typeof +{ + typeof get() === \`string\`; + let a: string, b: string; + typeof a === typeof b; + typeof a === b; + a === typeof b; +} + +// unknown +typeof get() === "undefined"; +typeof get() === "boolean"; +typeof get() === "number"; +typeof get() === "string"; +typeof get() === "symbol"; +typeof get() === "function"; +typeof get() === "object"; +"string" === typeof get(); +undefined === get(); + +// other +{ + const body: unknown = 'test'; + if (typeof body === 'object') + console.log('a'); + + let test: unknown = undefined; + if (test !== undefined) + console.log('b'); +} + `, + ], + + invalid: [ + // typeof undefined + { + code: ` +declare function get(): T; +typeof get() === "undefined";`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +typeof get() === "undefined";`, + errors: [ + { + messageId: 'expressionAlwaysTrue', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +// 'undefined' is not assignable to '{}' +typeof get<{}>() === "undefined";`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 4, + column: 1, + }, + ], + }, + + // typeof boolean + { + code: ` +declare function get(): T; +typeof get() === "boolean";`, + errors: [ + { + messageId: 'expressionAlwaysTrue', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +typeof get() === "boolean";`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + + // typeof number + { + code: ` +declare function get(): T; +enum E {} +typeof get() === "number";`, + errors: [ + { + messageId: 'expressionAlwaysTrue', + line: 4, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +typeof get() === "number";`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + + // typeof string + { + code: ` +declare function get(): T; +typeof get<"abc" | "def">() === "string";`, + errors: [ + { + messageId: 'expressionAlwaysTrue', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +typeof get() === "string";`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + + // typeof symbol + { + code: ` +declare function get(): T; +typeof get() === "symbol";`, + errors: [ + { + messageId: 'expressionAlwaysTrue', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +typeof get() === "symbol";`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + + // typeof function + { + code: ` +declare function get(): T; +typeof get<() => void>() === "function";`, + errors: [ + { + messageId: 'expressionAlwaysTrue', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +typeof get() === "function";`, + errors: [ + { + messageId: 'expressionAlwaysTrue', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +typeof get() === "function";`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +class X {} +typeof X === "function";`, + errors: [ + { + messageId: 'expressionAlwaysTrue', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +class X {} +typeof X === "object";`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + + // typeof object + { + code: ` +declare function get(): T; +typeof get void) | Function>() === "object";`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + + // === null / undefined + { + code: ` +declare function get(): T; +get() === null;`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +get() === undefined;`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + + // 'null' and 'undefined' are not assignable to '{}' + { + code: ` +declare function get(): T; +get<{}>() === null;`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +get<{}>() === undefined;`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +get() == null;`, + errors: [ + { + messageId: 'useStrictlyUndefined', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +get() == undefined;`, + errors: [ + { + messageId: 'useStrictlyNull', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +get() == null;`, + errors: [ + { + messageId: 'useStrictlyNull', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +get() == undefined;`, + errors: [ + { + messageId: 'useStrictlyUndefined', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +get() != null;`, + errors: [ + { + messageId: 'useStrictlyNotUndefined', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +get<{}>() == null;`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +get() == null;`, + errors: [ + { + messageId: 'expressionAlwaysTrue', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +get() != undefined;`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + + // negation + { + code: ` +declare function get(): T; +get() !== null;`, + errors: [ + { + messageId: 'expressionAlwaysTrue', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +get() !== undefined;`, + errors: [ + { + messageId: 'expressionAlwaysTrue', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +typeof get() !== "string";`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + + // reverse left/right + { + code: ` +declare function get(): T; +"string" === typeof get();`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +undefined === get();`, + errors: [ + { + messageId: 'expressionAlwaysTrue', + line: 3, + column: 1, + }, + ], + }, + + // Detects bad typeof + { + code: ` +declare function get(): T; +typeof get() === true;`, + errors: [ + { + messageId: 'badTypeof', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +typeof get() === "orbject";`, + errors: [ + { + messageId: 'badTypeof', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +typeof get() === \`stirng\`;`, + errors: [ + { + messageId: 'badTypeof', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +typeof get() === "unknown";`, + errors: [ + { + messageId: 'badTypeof', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +typeof a === undefined;`, + errors: [ + { + messageId: 'expressionAlwaysFalse', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +undefined === typeof a;`, + errors: [ + { + messageId: 'badTypeof', + line: 3, + column: 1, + }, + ], + }, + { + code: ` +declare function get(): T; +null === typeof b;`, + errors: [ + { + messageId: 'badTypeof', + line: 3, + column: 1, + }, + ], + }, + ], +});