--- /dev/null
+"use strict";
+/**
+ * @license
+ * Copyright 2017 Palantir Technologies, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+Object.defineProperty(exports, "__esModule", { value: true });
+var tslib_1 = require("tslib");
+var tsutils_1 = require("tsutils");
+var ts = require("typescript");
+var error_1 = require("../error");
+var Lint = require("../index");
+// tslint:disable:no-bitwise
+var Rule = /** @class */ (function (_super) {
+ tslib_1.__extends(Rule, _super);
+ function Rule() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ Rule.FAILURE_STRING = function (value) {
+ return "Expression is always " + value + ".";
+ };
+ Rule.FAILURE_STRICT_PREFER_STRICT_EQUALS = function (value, isPositive) {
+ return "Use '" + (isPositive ? "===" : "!==") + " " + value + "' instead.";
+ };
+ Rule.prototype.applyWithProgram = function (sourceFile, program) {
+ if (!Lint.isStrictNullChecksEnabled(program.getCompilerOptions())) {
+ error_1.showWarningOnce("strict-type-predicates does not work without --strictNullChecks");
+ return [];
+ }
+ return this.applyWithFunction(sourceFile, walk, undefined, program.getTypeChecker());
+ };
+ /* tslint:disable:object-literal-sort-keys */
+ Rule.metadata = {
+ ruleName: "strict-type-predicates",
+ description: Lint.Utils.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["\n Warns for type predicates that are always true or always false.\n Works for 'typeof' comparisons to constants (e.g. 'typeof foo === \"string\"'), and equality comparison to 'null'/'undefined'.\n (TypeScript won't let you compare '1 === 2', but it has an exception for '1 === undefined'.)\n Does not yet work for 'instanceof'.\n Does *not* warn for 'if (x.y)' where 'x.y' is always truthy. For that, see strict-boolean-expressions.\n\n This rule requires `strictNullChecks` to work properly."], ["\n Warns for type predicates that are always true or always false.\n Works for 'typeof' comparisons to constants (e.g. 'typeof foo === \"string\"'), and equality comparison to 'null'/'undefined'.\n (TypeScript won't let you compare '1 === 2', but it has an exception for '1 === undefined'.)\n Does not yet work for 'instanceof'.\n Does *not* warn for 'if (x.y)' where 'x.y' is always truthy. For that, see strict-boolean-expressions.\n\n This rule requires \\`strictNullChecks\\` to work properly."]))),
+ optionsDescription: "Not configurable.",
+ options: null,
+ optionExamples: [true],
+ type: "functionality",
+ typescriptOnly: true,
+ requiresTypeInfo: true,
+ };
+ /* tslint:enable:object-literal-sort-keys */
+ Rule.FAILURE_STRING_BAD_TYPEOF = "Bad comparison for 'typeof'.";
+ return Rule;
+}(Lint.Rules.TypedRule));
+exports.Rule = Rule;
+function walk(ctx, checker) {
+ return ts.forEachChild(ctx.sourceFile, function cb(node) {
+ if (tsutils_1.isBinaryExpression(node)) {
+ var equals = Lint.getEqualsKind(node.operatorToken);
+ if (equals !== undefined) {
+ checkEquals(node, equals);
+ }
+ }
+ return ts.forEachChild(node, cb);
+ });
+ function checkEquals(node, _a) {
+ var isStrict = _a.isStrict, isPositive = _a.isPositive;
+ var exprPred = getTypePredicate(node, isStrict);
+ if (exprPred === undefined) {
+ return;
+ }
+ if (exprPred.kind === 2 /* TypeofTypo */) {
+ fail(Rule.FAILURE_STRING_BAD_TYPEOF);
+ return;
+ }
+ var exprType = checker.getTypeAtLocation(exprPred.expression);
+ // TODO: could use checker.getBaseConstraintOfType to help with type parameters, but it's not publicly exposed.
+ if (tsutils_1.isTypeFlagSet(exprType, ts.TypeFlags.Any | ts.TypeFlags.TypeParameter | ts.TypeFlags.Unknown)) {
+ return;
+ }
+ switch (exprPred.kind) {
+ case 0 /* Plain */: {
+ var predicate = exprPred.predicate, isNullOrUndefined = exprPred.isNullOrUndefined;
+ var value = getConstantBoolean(exprType, predicate);
+ // 'null'/'undefined' are the only two values *not* assignable to '{}'.
+ if (value !== undefined && (isNullOrUndefined || !isEmptyType(checker, exprType))) {
+ fail(Rule.FAILURE_STRING(value === isPositive));
+ }
+ break;
+ }
+ case 1 /* NonStructNullUndefined */: {
+ var result = testNonStrictNullUndefined(exprType);
+ if (result !== undefined) {
+ fail(typeof result === "boolean"
+ ? Rule.FAILURE_STRING(result === isPositive)
+ : Rule.FAILURE_STRICT_PREFER_STRICT_EQUALS(result, isPositive));
+ }
+ }
+ }
+ function fail(failure) {
+ ctx.addFailureAtNode(node, failure);
+ }
+ }
+}
+/** Detects a type predicate given `left === right`. */
+function getTypePredicate(node, isStrictEquals) {
+ var left = node.left, right = node.right;
+ var 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, right, isStrictEquals) {
+ switch (right.kind) {
+ case ts.SyntaxKind.TypeOfExpression:
+ var expression = right.expression;
+ if (!tsutils_1.isLiteralExpression(left)) {
+ if ((tsutils_1.isIdentifier(left) && left.text === "undefined") ||
+ left.kind === ts.SyntaxKind.NullKeyword ||
+ left.kind === ts.SyntaxKind.TrueKeyword ||
+ left.kind === ts.SyntaxKind.FalseKeyword) {
+ return { kind: 2 /* TypeofTypo */ };
+ }
+ return undefined;
+ }
+ var predicate = getTypePredicateForKind(left.text);
+ return predicate === undefined
+ ? { kind: 2 /* TypeofTypo */ }
+ : {
+ expression: expression,
+ isNullOrUndefined: left.text === "undefined",
+ kind: 0 /* Plain */,
+ predicate: predicate,
+ };
+ case ts.SyntaxKind.NullKeyword:
+ return nullOrUndefined(ts.TypeFlags.Null);
+ case ts.SyntaxKind.Identifier:
+ if (right.originalKeywordKind === ts.SyntaxKind.UndefinedKeyword) {
+ return nullOrUndefined(undefinedFlags);
+ }
+ return undefined;
+ default:
+ return undefined;
+ }
+ function nullOrUndefined(flags) {
+ return isStrictEquals
+ ? {
+ expression: left,
+ isNullOrUndefined: true,
+ kind: 0 /* Plain */,
+ predicate: flagPredicate(flags),
+ }
+ : { kind: 1 /* NonStructNullUndefined */, expression: left };
+ }
+}
+function isEmptyType(checker, type) {
+ return checker.typeToString(type) === "{}";
+}
+var undefinedFlags = ts.TypeFlags.Undefined | ts.TypeFlags.Void;
+function getTypePredicateForKind(kind) {
+ 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.
+ var allFlags_1 = ts.TypeFlags.Undefined |
+ ts.TypeFlags.Void |
+ ts.TypeFlags.BooleanLike |
+ ts.TypeFlags.NumberLike |
+ ts.TypeFlags.StringLike |
+ ts.TypeFlags.ESSymbol;
+ return function (type) { return !tsutils_1.isTypeFlagSet(type, allFlags_1) && !isFunction(type); };
+ default:
+ return undefined;
+ }
+}
+function flagPredicate(testedFlag) {
+ return function (type) { return tsutils_1.isTypeFlagSet(type, testedFlag); };
+}
+function isFunction(t) {
+ if (t.getConstructSignatures().length !== 0 || t.getCallSignatures().length !== 0) {
+ return true;
+ }
+ var symbol = t.getSymbol();
+ return symbol !== undefined && symbol.getName() === "Function";
+}
+/** Returns a boolean value if that should always be the result of a type predicate. */
+function getConstantBoolean(type, predicate) {
+ var anyTrue = false;
+ var anyFalse = false;
+ for (var _i = 0, _a = unionParts(type); _i < _a.length; _i++) {
+ var ty = _a[_i];
+ if (predicate(ty)) {
+ anyTrue = true;
+ }
+ else {
+ anyFalse = true;
+ }
+ if (anyTrue && anyFalse) {
+ return undefined;
+ }
+ }
+ return anyTrue;
+}
+/** Returns bool for always/never true, or a string to recommend strict equality. */
+function testNonStrictNullUndefined(type) {
+ var anyNull = false;
+ var anyUndefined = false;
+ var anyOther = false;
+ for (var _i = 0, _a = unionParts(type); _i < _a.length; _i++) {
+ var ty = _a[_i];
+ if (tsutils_1.isTypeFlagSet(ty, ts.TypeFlags.Null)) {
+ anyNull = true;
+ }
+ else if (tsutils_1.isTypeFlagSet(ty, undefinedFlags)) {
+ anyUndefined = true;
+ }
+ else {
+ anyOther = true;
+ }
+ }
+ return !anyOther
+ ? true
+ : anyNull && anyUndefined
+ ? undefined
+ : anyNull
+ ? "null"
+ : anyUndefined
+ ? "undefined"
+ : false;
+}
+function unionParts(type) {
+ return tsutils_1.isUnionType(type) ? type.types : [type];
+}
+var templateObject_1;