UNPKG

eslint-plugin-sonarjs

Version:
187 lines (186 loc) 8.66 kB
"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; }