eslint-plugin-sonarjs
Version:
187 lines (186 loc) • 8.66 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isFunctionLikeDeclaration = isFunctionLikeDeclaration;
exports.followCallToDeclaration = followCallToDeclaration;
exports.followReferenceToDeclaration = followReferenceToDeclaration;
exports.followCallToImplementation = followCallToImplementation;
exports.followTypeScriptCallToImplementation = followTypeScriptCallToImplementation;
const typescript_1 = __importDefault(require("typescript"));
const type_js_1 = require("./type.js");
function normalizeToFunctionLikeDeclaration(declaration) {
if (declaration === undefined) {
return undefined;
}
if (typescript_1.default.isFunctionLike(declaration)) {
return declaration;
}
if (typescript_1.default.isVariableDeclaration(declaration) &&
isConstVariableDeclaration(declaration) &&
declaration.initializer !== undefined &&
typescript_1.default.isFunctionLike(declaration.initializer)) {
return declaration.initializer;
}
if (typescript_1.default.isPropertyAssignment(declaration) &&
declaration.initializer !== undefined &&
typescript_1.default.isFunctionLike(declaration.initializer)) {
return declaration.initializer;
}
return undefined;
}
function isConstVariableDeclaration(declaration) {
return typescript_1.default.isVariableDeclarationList(declaration.parent)
? (declaration.parent.flags & typescript_1.default.NodeFlags.Const) !== 0
: false;
}
function hasBody(declaration) {
return declaration !== undefined && isFunctionLikeDeclaration(declaration) && !!declaration.body;
}
function isFunctionLikeDeclaration(declaration) {
return [
typescript_1.default.SyntaxKind.FunctionDeclaration,
typescript_1.default.SyntaxKind.FunctionExpression,
typescript_1.default.SyntaxKind.ArrowFunction,
typescript_1.default.SyntaxKind.MethodDeclaration,
typescript_1.default.SyntaxKind.Constructor,
typescript_1.default.SyntaxKind.GetAccessor,
typescript_1.default.SyntaxKind.SetAccessor,
].includes(declaration.kind);
}
/**
* Resolves a call expression to the function-like declaration it targets, using the TypeScript
* type checker's `getResolvedSignature`. Returns `undefined` when the callee does not resolve to
* a known function declaration (e.g. built-ins, overloads with no single declaration, or
* cross-package symbols without source).
*
* Only one hop is followed: the directly called function. Deeper call chains are not resolved.
*/
function followCallToDeclaration(call, services) {
return normalizeToFunctionLikeDeclaration((0, type_js_1.getSignatureFromCallee)(call, services)?.declaration);
}
/**
* Resolves an identifier reference to the directly referenced function-like declaration.
*
* Only one indirection is followed: a direct function declaration or a variable whose initializer
* is a function expression / arrow function. Aliases such as `const cb = setup; describe(..., cb)`
* intentionally return `undefined`.
*/
function followReferenceToDeclaration(node, services) {
return followSymbolToDeclaration((0, type_js_1.getSymbolAtLocation)(node, services), services.program.getTypeChecker());
}
function followMemberPropertyToDeclaration(call, services) {
if (call.callee.type !== 'MemberExpression' ||
call.callee.computed ||
call.callee.property.type !== 'Identifier') {
return undefined;
}
return (followObjectLiteralMemberToDeclaration(call.callee.object, call.callee.property.name, services) ?? followReferenceToDeclaration(call.callee.property, services));
}
function followObjectLiteralMemberToDeclaration(object, propertyName, services) {
if (object.type !== 'Identifier') {
return undefined;
}
return followObjectLiteralSymbolMemberToDeclaration((0, type_js_1.getSymbolAtLocation)(object, services), propertyName, services.program.getTypeChecker());
}
function getAliasedSymbol(symbol, checker) {
if (symbol === undefined) {
return undefined;
}
if ((symbol.flags & typescript_1.default.SymbolFlags.Alias) !== 0) {
return checker.getAliasedSymbol(symbol);
}
return symbol;
}
function followSymbolToDeclaration(symbol, checker) {
const aliasedSymbol = getAliasedSymbol(symbol, checker);
if (aliasedSymbol?.declarations?.length !== 1) {
return undefined;
}
return normalizeToFunctionLikeDeclaration(aliasedSymbol.declarations[0]);
}
function followObjectLiteralSymbolMemberToDeclaration(symbol, propertyName, checker) {
const aliasedSymbol = getAliasedSymbol(symbol, checker);
if (aliasedSymbol?.declarations?.length !== 1) {
return undefined;
}
return followObjectLiteralDeclarationMemberToDeclaration(aliasedSymbol.declarations[0], propertyName);
}
function followObjectLiteralDeclarationMemberToDeclaration(declaration, propertyName) {
if (!typescript_1.default.isVariableDeclaration(declaration) ||
!isConstVariableDeclaration(declaration) ||
!typescript_1.default.isIdentifier(declaration.name) ||
declaration.initializer === undefined ||
!typescript_1.default.isObjectLiteralExpression(declaration.initializer) ||
hasPropertyWrite(declaration.name.text, propertyName, declaration.getSourceFile())) {
return undefined;
}
return normalizeToFunctionLikeDeclaration(declaration.initializer.properties.find(property => property.name?.getText() === propertyName));
}
function followTypeScriptReferenceToDeclaration(node, checker) {
return followSymbolToDeclaration(checker.getSymbolAtLocation(node), checker);
}
function followTypeScriptObjectLiteralMemberToDeclaration(expression, propertyName, checker) {
return followObjectLiteralSymbolMemberToDeclaration(checker.getSymbolAtLocation(expression), propertyName, checker);
}
function followTypeScriptCalleeToDeclaration(call, checker) {
const { expression } = call;
if (typescript_1.default.isIdentifier(expression)) {
return followTypeScriptReferenceToDeclaration(expression, checker);
}
if (typescript_1.default.isPropertyAccessExpression(expression)) {
return (followTypeScriptObjectLiteralMemberToDeclaration(expression.expression, expression.name.text, checker) ?? followTypeScriptReferenceToDeclaration(expression.name, checker));
}
return undefined;
}
function hasPropertyWrite(objectName, propertyName, sourceFile) {
let found = false;
const visit = (node) => {
if (found) {
return;
}
if (typescript_1.default.isBinaryExpression(node) &&
node.operatorToken.kind === typescript_1.default.SyntaxKind.EqualsToken &&
isPropertyAccess(node.left, objectName, propertyName)) {
found = true;
return;
}
node.forEachChild(visit);
};
sourceFile.forEachChild(visit);
return found;
}
function isPropertyAccess(node, objectName, propertyName) {
return (typescript_1.default.isPropertyAccessExpression(node) &&
typescript_1.default.isIdentifier(node.expression) &&
node.expression.text === objectName &&
node.name.text === propertyName);
}
/**
* Resolves a call expression to executable code when possible. TypeScript may resolve a typed
* function value call to its call signature rather than to the assigned implementation. In that
* case, fallback to the callee's value declaration for direct identifiers and object members.
*/
function followCallToImplementation(call, services) {
const declaration = followCallToDeclaration(call, services);
if (hasBody(declaration)) {
return declaration;
}
if (call.callee.type === 'Identifier') {
return followReferenceToDeclaration(call.callee, services) ?? declaration;
}
return followMemberPropertyToDeclaration(call, services) ?? declaration;
}
/**
* Resolves a TypeScript call expression to executable code without requiring an ESTree mapping.
* This is used while recursively visiting declarations from other files, where
* `tsNodeToESTreeNodeMap` only covers the originally parsed file.
*/
function followTypeScriptCallToImplementation(call, checker) {
const declaration = normalizeToFunctionLikeDeclaration(checker.getResolvedSignature(call)?.declaration);
if (hasBody(declaration)) {
return declaration;
}
return followTypeScriptCalleeToDeclaration(call, checker) ?? declaration;
}