fish-lsp
Version:
LSP implementation for fish/fish-shell
214 lines (213 loc) • 7.56 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findUnreachableCode = findUnreachableCode;
const node_types_1 = require("../utils/node-types");
const tree_sitter_1 = require("../utils/tree-sitter");
function isTerminalStatement(node) {
if ((0, node_types_1.isReturn)(node))
return true;
if ((0, node_types_1.isCommand)(node)) {
const commandName = node.firstNamedChild?.text;
return commandName === 'exit' || commandName === 'break' || commandName === 'continue';
}
if (node.type === 'break' || node.type === 'continue' || node.type === 'exit' || node.type === 'return') {
return true;
}
return false;
}
function conditionalExecutionTerminates(conditionalNode) {
for (const child of conditionalNode.namedChildren) {
if (isTerminalStatement(child)) {
return true;
}
}
return false;
}
function sequenceFormsTerminatingAndOrChain(nodes, startIndex) {
if (startIndex + 2 >= nodes.length)
return false;
const first = nodes[startIndex];
const second = nodes[startIndex + 1];
const third = nodes[startIndex + 2];
if (!first || !second || !third)
return false;
const isCommandSequence = ((0, node_types_1.isCommand)(first) || (0, node_types_1.isConditionalCommand)(first)) &&
(0, node_types_1.isConditionalCommand)(second) &&
(0, node_types_1.isConditionalCommand)(third);
if (!isCommandSequence)
return false;
const secondTerminates = conditionalExecutionTerminates(second);
const thirdTerminates = conditionalExecutionTerminates(third);
return secondTerminates && thirdTerminates;
}
function containsDirectTerminalStatement(node) {
for (const child of node.namedChildren) {
if (isTerminalStatement(child)) {
return true;
}
}
return false;
}
function caseContainsTerminalStatement(caseNode) {
let skipPattern = true;
for (const child of caseNode.namedChildren) {
if (skipPattern) {
skipPattern = false;
continue;
}
if (isTerminalStatement(child)) {
return true;
}
if (containsDirectTerminalStatement(child) || hasTerminalInNestedBlocks(child)) {
return true;
}
}
return false;
}
function hasTerminalInNestedBlocks(node) {
for (const child of node.namedChildren) {
if (isTerminalStatement(child)) {
return true;
}
if (hasTerminalInNestedBlocks(child)) {
return true;
}
}
return false;
}
function allPathsTerminate(ifNode) {
let hasElse = false;
let ifBodyTerminates = false;
let elseBodyTerminates = false;
let skipCondition = true;
for (const child of ifNode.namedChildren) {
if (skipCondition && (child.type === 'command' || child.type === 'test_command' || child.type === 'command_substitution')) {
continue;
}
skipCondition = false;
if (child.type === 'else_clause') {
hasElse = true;
if (containsDirectTerminalStatement(child) || hasTerminalInNestedBlocks(child)) {
elseBodyTerminates = true;
}
}
else if (child.type !== 'else_if_clause' && !ifBodyTerminates) {
if (isTerminalStatement(child) || hasTerminalInNestedBlocks(child)) {
ifBodyTerminates = true;
}
}
}
return ifBodyTerminates && hasElse && elseBodyTerminates;
}
function allSwitchPathsTerminate(switchNode) {
let hasDefault = false;
let allCasesTerminate = true;
for (const child of switchNode.namedChildren) {
if ((0, node_types_1.isCaseClause)(child)) {
const casePattern = child.firstNamedChild?.text;
if (casePattern === '*' || casePattern === '"*"' || casePattern === "'*'" || casePattern === '\\*') {
hasDefault = true;
}
if (!caseContainsTerminalStatement(child)) {
allCasesTerminate = false;
}
}
}
return hasDefault && allCasesTerminate;
}
function getUnreachableStatementsInSequence(nodes) {
const unreachable = [];
let foundTerminal = false;
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if ((0, node_types_1.isComment)(node)) {
continue;
}
if (foundTerminal) {
unreachable.push(node);
continue;
}
if (isTerminalStatement(node)) {
foundTerminal = true;
continue;
}
if ((0, node_types_1.isIfStatement)(node) && allPathsTerminate(node)) {
foundTerminal = true;
continue;
}
if ((0, node_types_1.isSwitchStatement)(node) && allSwitchPathsTerminate(node)) {
foundTerminal = true;
continue;
}
if (sequenceFormsTerminatingAndOrChain(nodes, i)) {
foundTerminal = true;
i += 2;
continue;
}
}
return unreachable;
}
function findUnreachableInFunction(functionNode) {
const unreachable = [];
const functionBodyNodes = [];
let foundFunctionKeyword = false;
let foundFunctionName = false;
for (const child of functionNode.namedChildren) {
if (!foundFunctionKeyword && child.type === 'word' && child.text === 'function') {
foundFunctionKeyword = true;
continue;
}
if (foundFunctionKeyword && !foundFunctionName && child.type === 'word') {
foundFunctionName = true;
continue;
}
if ((0, node_types_1.isComment)(child)) {
continue;
}
functionBodyNodes.push(child);
}
unreachable.push(...getUnreachableStatementsInSequence(functionBodyNodes));
return unreachable;
}
function findUnreachableInBlock(blockNode) {
const blockBodyNodes = [];
if ((0, node_types_1.isIfStatement)(blockNode)) {
let skipCondition = true;
for (const child of blockNode.namedChildren) {
if (skipCondition && (child.type === 'command' || child.type === 'test_command' || child.type === 'command_substitution')) {
continue;
}
skipCondition = false;
if (child.type !== 'else_clause' && child.type !== 'else_if_clause') {
blockBodyNodes.push(child);
}
}
}
else if ((0, node_types_1.isForLoop)(blockNode)) {
let skipForParts = true;
for (const child of blockNode.namedChildren) {
if (skipForParts && (child.type === 'variable_name' || child.type === 'word' || child.type === 'command_substitution' || child.type === 'concatenation')) {
continue;
}
skipForParts = false;
blockBodyNodes.push(child);
}
}
else {
blockBodyNodes.push(...blockNode.namedChildren);
}
return getUnreachableStatementsInSequence(blockBodyNodes);
}
function findUnreachableCode(root) {
const unreachable = [];
const allNodes = (0, tree_sitter_1.getChildNodes)(root);
for (const node of allNodes) {
if ((0, node_types_1.isFunctionDefinition)(node)) {
unreachable.push(...findUnreachableInFunction(node));
}
else if ((0, node_types_1.isIfStatement)(node) || (0, node_types_1.isForLoop)(node)) {
unreachable.push(...findUnreachableInBlock(node));
}
}
return unreachable;
}