blob: bb543a3588d50b6a14460dce7502a1d333a2fd58 [file] [log] [blame]
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getContextualType = exports.isUnsafeAssignment = exports.isAnyOrAnyArrayTypeDiscriminated = exports.isTypeUnknownArrayType = exports.isTypeAnyArrayType = exports.isTypeAnyType = exports.isTypeUnknownType = exports.getTypeArguments = exports.getEqualsKind = exports.getTokenAtPosition = exports.getSourceFileOfNode = exports.typeIsOrHasBaseType = exports.isTypeFlagSet = exports.getTypeFlags = exports.getDeclaration = exports.isNullableType = exports.getConstrainedTypeAtLocation = exports.getTypeName = exports.containsAllTypesByName = exports.isTypeArrayTypeOrUnionOfArrayTypes = void 0;
const debug_1 = __importDefault(require("debug"));
const tsutils_1 = require("tsutils");
const ts = __importStar(require("typescript"));
const log = debug_1.default('typescript-eslint:eslint-plugin:utils:types');
/**
* Checks if the given type is either an array type,
* or a union made up solely of array types.
*/
function isTypeArrayTypeOrUnionOfArrayTypes(type, checker) {
for (const t of tsutils_1.unionTypeParts(type)) {
if (!checker.isArrayType(t)) {
return false;
}
}
return true;
}
exports.isTypeArrayTypeOrUnionOfArrayTypes = isTypeArrayTypeOrUnionOfArrayTypes;
/**
* @param type Type being checked by name.
* @param allowedNames Symbol names checking on the type.
* @returns Whether the type is, extends, or contains all of the allowed names.
*/
function containsAllTypesByName(type, allowAny, allowedNames) {
if (isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
return !allowAny;
}
if (tsutils_1.isTypeReference(type)) {
type = type.target;
}
const symbol = type.getSymbol();
if (symbol && allowedNames.has(symbol.name)) {
return true;
}
if (tsutils_1.isUnionOrIntersectionType(type)) {
return type.types.every(t => containsAllTypesByName(t, allowAny, allowedNames));
}
const bases = type.getBaseTypes();
return (typeof bases !== 'undefined' &&
bases.length > 0 &&
bases.every(t => containsAllTypesByName(t, allowAny, allowedNames)));
}
exports.containsAllTypesByName = containsAllTypesByName;
/**
* Get the type name of a given type.
* @param typeChecker The context sensitive TypeScript TypeChecker.
* @param type The type to get the name of.
*/
function getTypeName(typeChecker, type) {
// It handles `string` and string literal types as string.
if ((type.flags & ts.TypeFlags.StringLike) !== 0) {
return 'string';
}
// If the type is a type parameter which extends primitive string types,
// but it was not recognized as a string like. So check the constraint
// type of the type parameter.
if ((type.flags & ts.TypeFlags.TypeParameter) !== 0) {
// `type.getConstraint()` method doesn't return the constraint type of
// the type parameter for some reason. So this gets the constraint type
// via AST.
const symbol = type.getSymbol();
const decls = symbol === null || symbol === void 0 ? void 0 : symbol.getDeclarations();
const typeParamDecl = decls === null || decls === void 0 ? void 0 : decls[0];
if (ts.isTypeParameterDeclaration(typeParamDecl) &&
typeParamDecl.constraint != null) {
return getTypeName(typeChecker, typeChecker.getTypeFromTypeNode(typeParamDecl.constraint));
}
}
// If the type is a union and all types in the union are string like,
// return `string`. For example:
// - `"a" | "b"` is string.
// - `string | string[]` is not string.
if (type.isUnion() &&
type.types
.map(value => getTypeName(typeChecker, value))
.every(t => t === 'string')) {
return 'string';
}
// If the type is an intersection and a type in the intersection is string
// like, return `string`. For example: `string & {__htmlEscaped: void}`
if (type.isIntersection() &&
type.types
.map(value => getTypeName(typeChecker, value))
.some(t => t === 'string')) {
return 'string';
}
return typeChecker.typeToString(type);
}
exports.getTypeName = getTypeName;
/**
* Resolves the given node's type. Will resolve to the type's generic constraint, if it has one.
*/
function getConstrainedTypeAtLocation(checker, node) {
const nodeType = checker.getTypeAtLocation(node);
const constrained = checker.getBaseConstraintOfType(nodeType);
return constrained !== null && constrained !== void 0 ? constrained : nodeType;
}
exports.getConstrainedTypeAtLocation = getConstrainedTypeAtLocation;
/**
* Checks if the given type is (or accepts) nullable
* @param isReceiver true if the type is a receiving type (i.e. the type of a called function's parameter)
*/
function isNullableType(type, { isReceiver = false, allowUndefined = true, } = {}) {
const flags = getTypeFlags(type);
if (isReceiver && flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
return true;
}
if (allowUndefined) {
return (flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0;
}
else {
return (flags & ts.TypeFlags.Null) !== 0;
}
}
exports.isNullableType = isNullableType;
/**
* Gets the declaration for the given variable
*/
function getDeclaration(checker, node) {
var _a;
const symbol = checker.getSymbolAtLocation(node);
if (!symbol) {
return null;
}
const declarations = symbol.getDeclarations();
return (_a = declarations === null || declarations === void 0 ? void 0 : declarations[0]) !== null && _a !== void 0 ? _a : null;
}
exports.getDeclaration = getDeclaration;
/**
* Gets all of the type flags in a type, iterating through unions automatically
*/
function getTypeFlags(type) {
let flags = 0;
for (const t of tsutils_1.unionTypeParts(type)) {
flags |= t.flags;
}
return flags;
}
exports.getTypeFlags = getTypeFlags;
/**
* Checks if the given type is (or accepts) the given flags
* @param isReceiver true if the type is a receiving type (i.e. the type of a called function's parameter)
*/
function isTypeFlagSet(type, flagsToCheck, isReceiver) {
const flags = getTypeFlags(type);
if (isReceiver && flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
return true;
}
return (flags & flagsToCheck) !== 0;
}
exports.isTypeFlagSet = isTypeFlagSet;
/**
* @returns Whether a type is an instance of the parent type, including for the parent's base types.
*/
function typeIsOrHasBaseType(type, parentType) {
const parentSymbol = parentType.getSymbol();
if (!type.getSymbol() || !parentSymbol) {
return false;
}
const typeAndBaseTypes = [type];
const ancestorTypes = type.getBaseTypes();
if (ancestorTypes) {
typeAndBaseTypes.push(...ancestorTypes);
}
for (const baseType of typeAndBaseTypes) {
const baseSymbol = baseType.getSymbol();
if (baseSymbol && baseSymbol.name === parentSymbol.name) {
return true;
}
}
return false;
}
exports.typeIsOrHasBaseType = typeIsOrHasBaseType;
/**
* Gets the source file for a given node
*/
function getSourceFileOfNode(node) {
while (node && node.kind !== ts.SyntaxKind.SourceFile) {
node = node.parent;
}
return node;
}
exports.getSourceFileOfNode = getSourceFileOfNode;
function getTokenAtPosition(sourceFile, position) {
const queue = [sourceFile];
let current;
while (queue.length > 0) {
current = queue.shift();
// find the child that contains 'position'
for (const child of current.getChildren(sourceFile)) {
const start = child.getFullStart();
if (start > position) {
// If this child begins after position, then all subsequent children will as well.
return current;
}
const end = child.getEnd();
if (position < end ||
(position === end && child.kind === ts.SyntaxKind.EndOfFileToken)) {
queue.push(child);
break;
}
}
}
return current;
}
exports.getTokenAtPosition = getTokenAtPosition;
function getEqualsKind(operator) {
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;
}
}
exports.getEqualsKind = getEqualsKind;
function getTypeArguments(type, checker) {
var _a;
// getTypeArguments was only added in TS3.7
if (checker.getTypeArguments) {
return checker.getTypeArguments(type);
}
return (_a = type.typeArguments) !== null && _a !== void 0 ? _a : [];
}
exports.getTypeArguments = getTypeArguments;
/**
* @returns true if the type is `unknown`
*/
function isTypeUnknownType(type) {
return isTypeFlagSet(type, ts.TypeFlags.Unknown);
}
exports.isTypeUnknownType = isTypeUnknownType;
/**
* @returns true if the type is `any`
*/
function isTypeAnyType(type) {
if (isTypeFlagSet(type, ts.TypeFlags.Any)) {
if (type.intrinsicName === 'error') {
log('Found an "error" any type');
}
return true;
}
return false;
}
exports.isTypeAnyType = isTypeAnyType;
/**
* @returns true if the type is `any[]`
*/
function isTypeAnyArrayType(type, checker) {
return (checker.isArrayType(type) &&
isTypeAnyType(
// getTypeArguments was only added in TS3.7
getTypeArguments(type, checker)[0]));
}
exports.isTypeAnyArrayType = isTypeAnyArrayType;
/**
* @returns true if the type is `unknown[]`
*/
function isTypeUnknownArrayType(type, checker) {
return (checker.isArrayType(type) &&
isTypeUnknownType(
// getTypeArguments was only added in TS3.7
getTypeArguments(type, checker)[0]));
}
exports.isTypeUnknownArrayType = isTypeUnknownArrayType;
/**
* @returns `AnyType.Any` if the type is `any`, `AnyType.AnyArray` if the type is `any[]` or `readonly any[]`,
* otherwise it returns `AnyType.Safe`.
*/
function isAnyOrAnyArrayTypeDiscriminated(node, checker) {
const type = checker.getTypeAtLocation(node);
if (isTypeAnyType(type)) {
return 0 /* Any */;
}
if (isTypeAnyArrayType(type, checker)) {
return 1 /* AnyArray */;
}
return 2 /* Safe */;
}
exports.isAnyOrAnyArrayTypeDiscriminated = isAnyOrAnyArrayTypeDiscriminated;
/**
* Does a simple check to see if there is an any being assigned to a non-any type.
*
* This also checks generic positions to ensure there's no unsafe sub-assignments.
* Note: in the case of generic positions, it makes the assumption that the two types are the same.
*
* @example See tests for examples
*
* @returns false if it's safe, or an object with the two types if it's unsafe
*/
function isUnsafeAssignment(type, receiver, checker) {
var _a, _b;
if (isTypeAnyType(type)) {
// Allow assignment of any ==> unknown.
if (isTypeUnknownType(receiver)) {
return false;
}
if (!isTypeAnyType(receiver)) {
return { sender: type, receiver };
}
}
if (tsutils_1.isTypeReference(type) && tsutils_1.isTypeReference(receiver)) {
// TODO - figure out how to handle cases like this,
// where the types are assignable, but not the same type
/*
function foo(): ReadonlySet<number> { return new Set<any>(); }
// and
type Test<T> = { prop: T }
type Test2 = { prop: string }
declare const a: Test<any>;
const b: Test2 = a;
*/
if (type.target !== receiver.target) {
// if the type references are different, assume safe, as we won't know how to compare the two types
// the generic positions might not be equivalent for both types
return false;
}
const typeArguments = (_a = type.typeArguments) !== null && _a !== void 0 ? _a : [];
const receiverTypeArguments = (_b = receiver.typeArguments) !== null && _b !== void 0 ? _b : [];
for (let i = 0; i < typeArguments.length; i += 1) {
const arg = typeArguments[i];
const receiverArg = receiverTypeArguments[i];
const unsafe = isUnsafeAssignment(arg, receiverArg, checker);
if (unsafe) {
return { sender: type, receiver };
}
}
return false;
}
return false;
}
exports.isUnsafeAssignment = isUnsafeAssignment;
/**
* Returns the contextual type of a given node.
* Contextual type is the type of the target the node is going into.
* i.e. the type of a called function's parameter, or the defined type of a variable declaration
*/
function getContextualType(checker, node) {
const parent = node.parent;
if (!parent) {
return;
}
if (tsutils_1.isCallExpression(parent) || tsutils_1.isNewExpression(parent)) {
if (node === parent.expression) {
// is the callee, so has no contextual type
return;
}
}
else if (tsutils_1.isVariableDeclaration(parent) ||
tsutils_1.isPropertyDeclaration(parent) ||
tsutils_1.isParameterDeclaration(parent)) {
return parent.type ? checker.getTypeFromTypeNode(parent.type) : undefined;
}
else if (tsutils_1.isJsxExpression(parent)) {
return checker.getContextualType(parent);
}
else if (tsutils_1.isPropertyAssignment(parent) && tsutils_1.isIdentifier(node)) {
return checker.getContextualType(node);
}
else if (![ts.SyntaxKind.TemplateSpan, ts.SyntaxKind.JsxExpression].includes(parent.kind)) {
// parent is not something we know we can get the contextual type of
return;
}
// TODO - support return statement checking
return checker.getContextualType(node);
}
exports.getContextualType = getContextualType;
//# sourceMappingURL=types.js.map