fish-lsp
Version:
LSP implementation for fish/fish-shell
209 lines (208 loc) • 9.1 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.findAllMissingArgparseFlags = findAllMissingArgparseFlags;
const analyze_1 = require("../analyze");
const logger_1 = require("../logger");
const tree_sitter_1 = require("../utils/tree-sitter");
const complete_1 = require("../parsing/complete");
const flatten_1 = require("../utils/flatten");
const Locations = __importStar(require("../utils/locations"));
const translation_1 = require("../utils/translation");
const code_action_handler_1 = require("../code-actions/code-action-handler");
function findAllMissingArgparseFlags(document) {
const fishSymbols = [];
const completionSymbols = [];
const diagnostics = [];
if (document.isFunction()) {
const result = findMissingArgparseFlagsWithExistingCompletions(document);
completionSymbols.push(...result);
}
if (document.isAutoloadedWithPotentialCompletions()) {
const result = findMissingCompletionsWithExistingArgparse(document);
fishSymbols.push(...result);
}
if (completionSymbols.length === 0 && fishSymbols.length === 0) {
logger_1.logger.debug(`No missing argparse flags found in document: ${document.uri}`);
return [];
}
if (completionSymbols.length > 0) {
for (const completionGroup of completionSymbols) {
const diag = createCompletionDiagnostic(completionGroup, analyze_1.analyzer.getFlatDocumentSymbols(document.uri));
if (diag) {
const toAdd = diag.filter(d => !diagnostics.some(existing => (0, code_action_handler_1.equalDiagnostics)(existing, d)));
diagnostics.push(...toAdd);
}
}
}
if (fishSymbols.length > 0) {
for (const symbol of fishSymbols) {
const diag = createArgparseDiagnostic(symbol, document);
if (diag) {
const toAdd = diag.filter(d => !diagnostics.some(existing => (0, code_action_handler_1.equalDiagnostics)(existing, d)));
diagnostics.push(...toAdd);
}
}
}
return diagnostics;
}
function findMissingArgparseFlagsWithExistingCompletions(document) {
const missingCompletions = [];
const symbols = analyze_1.analyzer.getFlatDocumentSymbols(document.uri);
const globalSymbols = symbols.filter(s => s.isGlobal() && s.isFunction());
const argparseSymbols = (0, flatten_1.flattenNested)(...globalSymbols).filter(s => s.fishKind === 'ARGPARSE');
for (const symbol of argparseSymbols) {
const completionLocations = analyze_1.analyzer.getImplementation(document, symbol.toPosition())
.filter(loc => !symbol.equalsLocation(loc));
if (completionLocations.length === 0)
continue;
for (const location of completionLocations) {
const cSymbols = analyze_1.analyzer
.getFlatCompletionSymbols(location.uri)
.filter(s => s.isNonEmpty());
if (cSymbols.length === 0)
continue;
const grouped = (0, complete_1.groupCompletionSymbolsTogether)(...cSymbols);
const result = (0, complete_1.getGroupedCompletionSymbolsAsArgparse)(grouped, argparseSymbols);
if (result.length > 0) {
missingCompletions.push(...result);
}
}
}
return missingCompletions;
}
function findMissingCompletionsWithExistingArgparse(document) {
const missingCompletions = [];
const completionSymbols = analyze_1.analyzer.getFlatCompletionSymbols(document.uri).filter(s => s.isNonEmpty());
const implementationLocations = [];
completionSymbols.forEach(s => {
const pos = s.toPosition();
if (!pos)
return;
const results = analyze_1.analyzer.getImplementation(document, pos)
.filter(loc => !Locations.Location.equals(loc, s.toLocation()));
if (results.length === 0)
return;
implementationLocations.push(...results);
});
const grouped = (0, complete_1.groupCompletionSymbolsTogether)(...completionSymbols);
if (grouped.length === 0)
return missingCompletions;
for (const location of implementationLocations) {
const cSymbols = analyze_1.analyzer.getFlatDocumentSymbols(location.uri)
.filter(s => s.fishKind === 'ARGPARSE');
if (cSymbols.length === 0)
continue;
for (const symbol of cSymbols) {
if (grouped.some(group => group.some(s => s.equalsArgparse(symbol)))) {
continue;
}
missingCompletions.push(symbol);
}
}
return missingCompletions;
}
function createCompletionDiagnostic(completionGroup, symbols) {
const diagnostics = [];
const focusedSymbol = symbols.find(s => s.isFunction() && s.isGlobal() && completionGroup.every(c => c.commandName === s.name));
if (!focusedSymbol) {
logger_1.logger.warning(`No focused location found for completion group: ${completionGroup.map(c => c.text).join(', ')}`, 'HERE');
return null;
}
const hasArgparse = (0, flatten_1.flattenNested)(focusedSymbol).find(l => l.fishKind === 'ARGPARSE');
const focusedNode = hasArgparse ? hasArgparse.node.firstNamedChild : focusedSymbol.node.firstChild?.nextSibling;
if (!focusedNode) {
logger_1.logger.warning(`No focused node found for completion group: ${completionGroup.map(c => c.text).join(', ')}`);
return null;
}
const joinedGroup = completionGroup.map(c => c.toArgparseOpt()).join('/');
const firstCompletion = completionGroup.find(c => c.isNonEmpty());
const firstCompletionDoc = firstCompletion.document;
const prettyPath = (0, translation_1.uriToReadablePath)(firstCompletionDoc.uri);
diagnostics.push({
message: `Add missing \`argparse ${joinedGroup}\` from completion in '${prettyPath}'`,
severity: 1,
source: 'fish-lsp',
code: 4008,
range: (0, tree_sitter_1.getRange)(focusedNode),
data: {
node: focusedNode,
},
});
const joinedGroupUsage = completionGroup.map((item, idx) => {
if (idx === 0) {
return item.toUsage();
}
return item.toFlag();
}).join('/');
diagnostics.push({
message: `Remove the unused completion \`${joinedGroupUsage}\` in '${prettyPath}'`,
severity: 1,
source: 'fish-lsp',
code: 4009,
range: (0, tree_sitter_1.getRange)(firstCompletion.parent),
data: {
node: firstCompletion.parent,
},
});
return diagnostics;
}
function createArgparseDiagnostic(symbol, document) {
const diagnostics = [];
const focusedNode = symbol.focusedNode;
if (!focusedNode) {
logger_1.logger.warning(`No focused node found for symbol: ${symbol.name}`);
return [];
}
const prettyPath = (0, translation_1.uriToReadablePath)(document.uri);
diagnostics.push({
message: `remove unused \`argparse ${symbol.argparseFlagName}\` in '${prettyPath}'`,
severity: 1,
source: 'fish-lsp',
code: 4009,
range: (0, tree_sitter_1.getRange)(focusedNode),
data: {
type: 'argparse removal',
node: focusedNode,
},
});
const completionLocation = analyze_1.analyzer.getImplementation(document, symbol.toPosition())
.find(loc => !Locations.Location.equals(loc, symbol.toLocation()));
if (completionLocation) {
const prettyCompletionPath = (0, translation_1.uriToReadablePath)(completionLocation.uri);
diagnostics.push({
message: `Add missing \`${symbol.parent.name + ' ' + symbol.argparseFlag}\` completion in '${prettyCompletionPath}'`,
severity: 1,
source: 'fish-lsp',
code: 4008,
range: (0, tree_sitter_1.getRange)(focusedNode),
data: {
type: 'argparse addition',
node: focusedNode,
},
});
}
return diagnostics;
}