fish-lsp
Version:
LSP implementation for fish/fish-shell
280 lines (279 loc) • 11.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ErrorNodeTypes = void 0;
exports.findErrorCause = findErrorCause;
exports.isExtraEnd = isExtraEnd;
exports.isZeroIndex = isZeroIndex;
exports.isSingleQuoteVariableExpansion = isSingleQuoteVariableExpansion;
exports.isAlias = isAlias;
exports.isUniversalDefinition = isUniversalDefinition;
exports.isSourceFilename = isSourceFilename;
exports.isDotSourceCommand = isDotSourceCommand;
exports.isTestCommandVariableExpansionWithoutString = isTestCommandVariableExpansionWithoutString;
exports.isConditionalStatement = isConditionalStatement;
exports.isFirstNodeInConditionalExecution = isFirstNodeInConditionalExecution;
exports.isConditionalWithoutQuietCommand = isConditionalWithoutQuietCommand;
exports.isVariableDefinitionWithExpansionCharacter = isVariableDefinitionWithExpansionCharacter;
exports.isMatchingCompleteOptionIsCommand = isMatchingCompleteOptionIsCommand;
exports.isMatchingAbbrFunction = isMatchingAbbrFunction;
exports.isAbbrDefinitionName = isAbbrDefinitionName;
exports.isArgparseWithoutEndStdin = isArgparseWithoutEndStdin;
exports.isFunctionWithEventHookCallback = isFunctionWithEventHookCallback;
exports.isFishLspDeprecatedVariableName = isFishLspDeprecatedVariableName;
exports.getDeprecatedFishLspMessage = getDeprecatedFishLspMessage;
const node_types_1 = require("../utils/node-types");
const tree_sitter_1 = require("../utils/tree-sitter");
const options_1 = require("../parsing/options");
const source_1 = require("../parsing/source");
const error_codes_1 = require("./error-codes");
const references_1 = require("../references");
exports.ErrorNodeTypes = {
['function']: 'end',
['while']: 'end',
['begin']: 'end',
['for']: 'end',
['if']: 'end',
['"']: '"',
["'"]: "'",
['{']: '}',
['[']: ']',
['(']: ')',
};
function isStartTokenType(str) {
return ['function', 'while', 'if', 'for', 'begin', '[', '{', '(', "'", '"'].includes(str);
}
function findErrorCause(children) {
const stack = [];
for (const node of children) {
if (isStartTokenType(node.type)) {
const expectedEndToken = exports.ErrorNodeTypes[node.type];
const matchIndex = stack.findIndex(item => item.type === expectedEndToken);
if (matchIndex !== -1) {
stack.splice(matchIndex, 1);
}
else {
stack.push({ node, type: expectedEndToken });
}
}
else if (Object.values(exports.ErrorNodeTypes).includes(node.type)) {
stack.push({ node, type: node.type });
}
}
return stack.length > 0 ? stack[0]?.node || null : null;
}
function isExtraEnd(node) {
return node.type === 'command' && node.text === 'end';
}
function isZeroIndex(node) {
return node.type === 'index' && node.text === '0';
}
function isSingleQuoteVariableExpansion(node) {
if (node.type !== 'single_quote_string') {
return false;
}
if (node.parent && (0, node_types_1.isCommandWithName)(node.parent, 'string')) {
return false;
}
const variableRegex = /(?<!\\)\$\w+/;
return variableRegex.test(node.text);
}
function isAlias(node) {
return (0, node_types_1.isCommandWithName)(node, 'alias');
}
function isUniversalDefinition(node) {
if (!(0, node_types_1.isOption)(node))
return false;
const parent = (0, node_types_1.findParentCommand)(node);
if (!parent)
return false;
if ((0, node_types_1.isCommandWithName)(parent, 'read', 'set')) {
const definitionName = parent
.childrenForFieldName('argument')
.find(c => !(0, node_types_1.isOption)(c) && (0, node_types_1.isVariableDefinitionName)(c));
if (!definitionName || !(0, tree_sitter_1.precedesRange)((0, tree_sitter_1.getRange)(node), (0, tree_sitter_1.getRange)(definitionName))) {
return false;
}
return (0, node_types_1.isMatchingOption)(node, options_1.Option.create('-U', '--universal'));
}
return false;
}
function isSourceFilename(node) {
if ((0, source_1.isSourceCommandArgumentName)(node)) {
const isExisting = (0, source_1.isExistingSourceFilenameNode)(node);
if (!isExisting) {
if (node.type === 'variable_expansion') {
return false;
}
if ((0, node_types_1.isString)(node)) {
return false;
}
return true;
}
return !isExisting;
}
return false;
}
function isDotSourceCommand(node) {
if (node.parent && (0, node_types_1.isCommandWithName)(node.parent, '.')) {
return node.parent.firstNamedChild?.equals(node) || false;
}
return false;
}
function isTestCommandVariableExpansionWithoutString(node) {
const parent = node.parent;
const previousSibling = node.previousSibling;
if (!parent || !previousSibling)
return false;
if (!(0, node_types_1.isCommandWithName)(parent, 'test', '['))
return false;
if ((0, node_types_1.isMatchingOption)(previousSibling, options_1.Option.short('-n'), options_1.Option.short('-z'))) {
return !(0, node_types_1.isString)(node) && !!parent.child(2) && parent.child(2).equals(node);
}
return false;
}
function isInsideStatementCondition(statement, node) {
const conditionNode = statement.childForFieldName('condition');
if (!conditionNode)
return false;
return (0, tree_sitter_1.isNodeWithinOtherNode)(node, conditionNode);
}
function isConditionalStatement(node) {
if (!node.isNamed)
return false;
if (['\n', ';'].includes(node?.previousSibling?.type || ''))
return false;
let curr = node.parent;
while (curr) {
if (curr.type === 'conditional_execution') {
curr = curr?.parent;
}
else if ((0, node_types_1.isIfOrElseIfConditional)(curr)) {
return isInsideStatementCondition(curr, node);
}
else {
break;
}
}
return false;
}
function checkConditionalStartsWith(node) {
if (node.type === 'conditional_execution') {
return node.text.startsWith('&&') || node.text.startsWith('||')
|| node.text.startsWith('and') || node.text.startsWith('or');
}
return false;
}
function isFirstNodeInConditionalExecution(node) {
if (!node.isNamed)
return false;
if (['\n', ';'].includes(node?.type || ''))
return false;
if (isConditionalStatement(node))
return false;
if (node.parent &&
node.parent.type === 'conditional_execution' &&
!checkConditionalStartsWith(node.parent)) {
return node.parent.firstNamedChild?.equals(node) || false;
}
const next = node.nextNamedSibling;
if (!next)
return false;
return next.type === 'conditional_execution' && checkConditionalStartsWith(next);
}
function hasCommandSubstitution(node) {
return node.childrenForFieldName('argument').filter(c => c.type === 'command_substitution').length > 0;
}
function isConditionalWithoutQuietCommand(node) {
if (!(0, node_types_1.isCommandWithName)(node, 'command', 'type', 'set', 'string', 'abbr', 'builtin', 'functions', 'jobs'))
return false;
if (!isConditionalStatement(node) && !isFirstNodeInConditionalExecution(node))
return false;
if ((0, node_types_1.isCommandWithName)(node, 'set') && hasCommandSubstitution(node)) {
return false;
}
const flags = node?.childrenForFieldName('argument')
.filter(n => (0, node_types_1.isMatchingOption)(n, options_1.Option.create('-q', '--quiet'))
|| (0, node_types_1.isMatchingOption)(n, options_1.Option.create('-q', '--query'))) || [];
return flags.length === 0;
}
function isVariableDefinitionWithExpansionCharacter(node, definedVariableExpansions = {}) {
if (!(0, node_types_1.isVariableDefinitionName)(node))
return false;
const parent = (0, node_types_1.findParentCommand)(node);
if (parent && (0, node_types_1.isCommandWithName)(parent, 'set', 'read')) {
if (!(0, node_types_1.isVariableDefinitionName)(node))
return false;
const name = node.text.startsWith('$') ? node.text.slice(1) : node.text;
if (!name || name.length === 0)
return false;
if (definedVariableExpansions[name] && definedVariableExpansions[name]?.some(scope => (0, tree_sitter_1.isNodeWithinOtherNode)(node, scope))) {
return false;
}
return node.type === 'variable_expansion' || node.text.startsWith('$');
}
return false;
}
function isMatchingCompleteOptionIsCommand(node) {
return (0, node_types_1.isMatchingOption)(node, options_1.Option.create('-n', '--condition').withValue())
|| (0, node_types_1.isMatchingOption)(node, options_1.Option.create('-a', '--arguments').withValue())
|| (0, node_types_1.isMatchingOption)(node, options_1.Option.create('-c', '--command').withValue());
}
function isMatchingAbbrFunction(node) {
return (0, node_types_1.isMatchingOption)(node, options_1.Option.create('-f', '--function').withValue());
}
function isAbbrDefinitionName(node) {
const parent = (0, node_types_1.findParentCommand)(node);
if (!parent)
return false;
if (!(0, node_types_1.isCommandWithName)(parent, 'abbr'))
return false;
const child = parent.childrenForFieldName('argument')
.filter(n => !(0, node_types_1.isOption)(n))
.find(n => n.type === 'word' && n.text !== '--' && !(0, node_types_1.isString)(n));
return child ? child.equals(node) : false;
}
function isArgparseWithoutEndStdin(node) {
if (!(0, node_types_1.isCommandWithName)(node, 'argparse'))
return false;
const endStdin = (0, tree_sitter_1.getChildNodes)(node).find(n => (0, node_types_1.isEndStdinCharacter)(n));
if (!endStdin)
return true;
return false;
}
function isFunctionWithEventHookCallback(doc, handler, allFunctions) {
const docType = doc.getAutoloadType();
return (node) => {
if (docType !== 'functions')
return false;
if (!(0, node_types_1.isFunctionDefinitionName)(node))
return false;
if (docType === 'functions' && handler.isCodeEnabledAtNode(error_codes_1.ErrorCodes.autoloadedFunctionWithEventHookUnused, node)) {
const funcSymbol = allFunctions.find(symbol => symbol.name === node.text);
if (funcSymbol && funcSymbol.hasEventHook()) {
const refs = (0, references_1.getReferences)(doc, funcSymbol.toPosition()).filter(ref => !funcSymbol.equalsLocation(ref) &&
!ref.uri.includes('completions/') &&
ref.uri !== doc.uri);
if (refs.length === 0)
return true;
}
}
return false;
};
}
function isFishLspDeprecatedVariableName(node) {
if ((0, node_types_1.isVariableDefinitionName)(node)) {
return node.text === 'fish_lsp_logfile';
}
if (node.type === 'variable_name') {
return node.text === 'fish_lsp_logfile';
}
return node.text === 'fish_lsp_logfile';
}
function getDeprecatedFishLspMessage(node) {
switch (node.text) {
case 'fish_lsp_logfile':
return `REPLACE \`${node.text}\` with \`fish_lsp_log_file\``;
default:
return '';
}
}