UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

200 lines (199 loc) 7.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DiagnosticCommentsHandler = exports.DIAGNOSTIC_COMMENT_REGEX = void 0; exports.isDiagnosticComment = isDiagnosticComment; exports.isValidErrorCode = isValidErrorCode; exports.parseDiagnosticComment = parseDiagnosticComment; const node_types_1 = require("../utils/node-types"); const errorCodes_1 = require("./errorCodes"); const config_1 = require("../config"); /** * Regular expression to match fish-lsp diagnostic control comments * Matches patterns like: * # @fish-lsp-disable * # @fish-lsp-enable * # @fish-lsp-disable 1001 1002 * # @fish-lsp-enable 1001 * # @fish-lsp-disable-next-line * # @fish-lsp-disable-next-line 3002 3001 */ exports.DIAGNOSTIC_COMMENT_REGEX = /^#\s*@fish-lsp-(disable|enable)(?:-(next-line))?\s*([0-9\s]*)?$/; /** * Checks if a node is a diagnostic control comment * @param node The syntax node to check * @returns true if the node is a diagnostic control comment */ function isDiagnosticComment(node) { if (!(0, node_types_1.isComment)(node)) return false; return exports.DIAGNOSTIC_COMMENT_REGEX.test(node.text.trim()); } function isValidErrorCode(code) { return Object.values(errorCodes_1.ErrorCodes).includes(code); } /** * Parses a diagnostic comment node into its components * @param node The syntax node to parse * @returns DiagnosticComment object containing the parsed information */ function parseDiagnosticComment(node) { if (!isDiagnosticComment(node)) return null; const match = node.text.trim().match(exports.DIAGNOSTIC_COMMENT_REGEX); if (!match) return null; const [, action, nextLine, codesStr] = match; const codeStrings = codesStr ? codesStr.trim().split(/\s+/) : []; // Parse the diagnostic codes if present const parsedCodes = codeStrings .map(codeStr => parseInt(codeStr, 10)) .filter(code => !isNaN(code)); const validCodes = []; const invalidCodes = []; codeStrings.forEach((codeStr, idx) => { const code = parsedCodes[idx]; if (code && !isNaN(code) && isValidErrorCode(code)) { validCodes.push(code); } else { invalidCodes.push(codeStr); } }); return { action: action, target: nextLine ? 'next-line' : 'line', codes: validCodes, lineNumber: node.startPosition.row, invalidCodes: invalidCodes.length > 0 ? invalidCodes : undefined, }; } function globalEnabledComments() { const allComments = errorCodes_1.ErrorCodes.allErrorCodes; if (config_1.config.fish_lsp_diagnostic_disable_error_codes.length > 0) { return allComments.filter((comment) => !config_1.config.fish_lsp_diagnostic_disable_error_codes.includes(comment)); } return allComments; } class DiagnosticCommentsHandler { stateStack = []; enabledComments = globalEnabledComments(); invalidCodeWarnings = new Map(); // lineNumber -> invalid codesublic invalidCodes: number[] = []; constructor() { // Initialize with global state this.pushState(this.initialState); } get initialState() { return { enabledCodes: new Set(this.enabledComments), comment: { action: 'enable', target: 'line', codes: this.enabledComments, lineNumber: -1, }, }; } get currentState() { return this.stateStack[this.stateStack.length - 1]; } pushState(state) { this.stateStack.push(state); } popState() { if (this.stateStack.length > 1) { // Keep at least the global state this.stateStack.pop(); this.enabledComments = Array.from(this.currentState.enabledCodes); } } handleNode(node) { // Clean up any expired next-line comments this.cleanupNextLineComments(node.startPosition.row); // Early return if not a diagnostic comment if (!isDiagnosticComment(node)) { return; } const comment = parseDiagnosticComment(node); if (!comment) return; // Track invalid codes if present if (comment.invalidCodes && comment.invalidCodes.length > 0) { this.invalidCodeWarnings.set(comment.lineNumber, comment.invalidCodes); } this.processComment(comment); } processComment(comment) { const newEnabledCodes = new Set(this.currentState.enabledCodes); if (['enable', 'disable'].includes(comment.action) && comment.codes.length === 0) { comment.codes = globalEnabledComments(); } if (comment.action === 'disable') { comment.codes.forEach(code => newEnabledCodes.delete(code)); } else { comment.codes.forEach(code => newEnabledCodes.add(code)); } const newState = { enabledCodes: newEnabledCodes, comment, invalidCodes: comment.invalidCodes, }; if (comment.target === 'next-line') { // For next-line, we'll push a new state that will be popped after the line this.pushState(newState); } else { // For regular comments, we'll replace the current state if (this.stateStack.length > 1) { this.popState(); // Remove the current state } this.pushState(newState); } this.enabledComments = Array.from(newEnabledCodes); } cleanupNextLineComments(currentLine) { while (this.stateStack.length > 1 && // Keep global state this.currentState.comment.target === 'next-line' && currentLine > this.currentState.comment.lineNumber + 1) { this.popState(); } } isCodeEnabled(code) { // ErrorCodes.allErrorCodes.filter(e => !this.currentState.enabledCodes.has(e)) return !!this.enabledComments.find(comment => comment === code); } // For debugging/testing getStackDepth() { return this.stateStack.length; } getCurrentState() { return this.currentState; } getCurrentStateVerbose() { const currentState = this.getCurrentState(); const disabledCodes = errorCodes_1.ErrorCodes.allErrorCodes.filter(e => !currentState.enabledCodes.has(e)); const enabledCodes = Array.from(currentState.enabledCodes) .map(e => errorCodes_1.ErrorCodes.codes[e].code) .concat(disabledCodes) .sort((a, b) => a - b) .map(item => { if (disabledCodes.includes(item)) return '....'; return item; }) .join(' | '); const invalidCodes = Array.from(this.invalidCodeWarnings.entries()) .map(([line, codes]) => `${line}: ${codes.join(' | ')}`); return { depth: this.getStackDepth(), enabledCodes: enabledCodes, invalidCodes: invalidCodes, currentState: { action: currentState.comment.action, target: currentState.comment.target, codes: currentState.comment?.codes.join(' | '), lineNumber: currentState.comment.lineNumber, }, }; } } exports.DiagnosticCommentsHandler = DiagnosticCommentsHandler;