UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

214 lines (213 loc) 7.56 kB
"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; }