eslint-plugin-jest
Version:
ESLint rules for Jest
104 lines (100 loc) • 3.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isBuiltinSymbolLike = isBuiltinSymbolLike;
exports.isThenableType = isThenableType;
let SymbolFlags;
let TypeFlags;
function loadTypeScriptFlags() {
// eslint-disable-next-line @typescript-eslint/no-require-imports
({
TypeFlags,
SymbolFlags
} = require('typescript'));
}
function isSymbolFromDefaultLibrary(program, symbol) {
/* istanbul ignore next */
const declarations = symbol.getDeclarations() ?? [];
for (const declaration of declarations) {
const sourceFile = declaration.getSourceFile();
/* istanbul ignore else */
if (program.isSourceFileDefaultLibrary(sourceFile)) {
return true;
}
}
/* istanbul ignore next */
return false;
}
function isBuiltinSymbolLike(program, type, symbolName) {
loadTypeScriptFlags();
return isBuiltinSymbolLikeRecurser(program, type, subType => {
const symbol = subType.getSymbol();
if (!symbol) {
return false;
}
const actualSymbolName = symbol.getName();
if (actualSymbolName === symbolName && isSymbolFromDefaultLibrary(program, symbol)) {
return true;
}
return null;
});
}
/**
* Checks if the given type is thenable: it has a `then` method that accepts
* both a fulfillment and a rejection callback, mirroring the check performed
* by typescript-eslint's `no-floating-promises` rule.
*/
function isThenableType(program, node, type) {
loadTypeScriptFlags();
const checker = program.getTypeChecker();
return isBuiltinSymbolLikeRecurser(program, type, subType => {
const then = subType.getProperty('then');
if (!then) {
return false;
}
return unionParts(checker.getTypeOfSymbolAtLocation(then, node)).some(thenType => thenType.getCallSignatures().some(signature => {
const [onFulfilled, onRejected] = signature.getParameters();
return onFulfilled !== undefined && onRejected !== undefined && isFunctionParam(checker, node, onFulfilled) && isFunctionParam(checker, node, onRejected);
}));
});
}
function isFunctionParam(checker, node, param) {
const paramType = checker.getApparentType(checker.getTypeOfSymbolAtLocation(param, node));
return unionParts(paramType).some(subType => subType.getCallSignatures().length > 0);
}
function unionParts(type) {
return type.isUnion() ? type.types : [type];
}
function isBuiltinSymbolLikeRecurser(program, type, predicate) {
if (type.isIntersection()) {
return type.types.some(t => isBuiltinSymbolLikeRecurser(program, t, predicate));
}
if (type.isUnion()) {
return type.types.every(t => isBuiltinSymbolLikeRecurser(program, t, predicate));
}
if (isTypeParameter(type)) {
const t = type.getConstraint();
if (t) {
return isBuiltinSymbolLikeRecurser(program, t, predicate);
}
return false;
}
const predicateResult = predicate(type);
if (typeof predicateResult === 'boolean') {
return predicateResult;
}
const symbol = type.getSymbol();
if (symbol && symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
const checker = program.getTypeChecker();
for (const baseType of checker.getBaseTypes(type)) {
if (isBuiltinSymbolLikeRecurser(program, baseType, predicate)) {
return true;
}
}
}
return false;
}
function isTypeParameter(type) {
return (type.flags & TypeFlags.TypeParameter) !== 0;
}