tslint
Version:
An extensible static analysis linter for the TypeScript language
249 lines (248 loc) • 10.7 kB
JavaScript
;
/**
* @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 });
exports.Rule = void 0;
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;