UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

257 lines (256 loc) 9.8 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 error_codes_1 = require("./error-codes"); const config_1 = require("../config"); exports.DIAGNOSTIC_COMMENT_REGEX = /^#\s*@fish-lsp-(disable|enable)(?:-(next-line))?\s*([0-9\s]*)?$/; 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(error_codes_1.ErrorCodes).includes(code); } 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+/) : []; 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.length > 0 ? validCodes : error_codes_1.ErrorCodes.allErrorCodes, lineNumber: node.startPosition.row, invalidCodes: invalidCodes.length > 0 ? invalidCodes : undefined, }; } class DiagnosticCommentsHandler { stateStack = []; controlPoints = []; lineStateMap = new Map(); invalidCodeWarnings = new Map(); enabledComments = []; constructor() { this.pushState(this.initialState); this.enabledComments = Array.from(this.currentState.enabledCodes); } get initialState() { return { enabledCodes: new Set(this.globalEnabledCodes()), comment: { action: 'enable', target: 'line', codes: this.globalEnabledCodes(), lineNumber: -1, }, }; } get currentState() { return this.stateStack[this.stateStack.length - 1]; } globalEnabledCodes() { if (config_1.config.fish_lsp_diagnostic_disable_error_codes.length > 0) { return error_codes_1.ErrorCodes.allErrorCodes.filter(code => !config_1.config.fish_lsp_diagnostic_disable_error_codes.includes(code)).filter(code => error_codes_1.ErrorCodes.nonDeprecatedErrorCodes.some(e => e.code === code)); } return error_codes_1.ErrorCodes.allErrorCodes.filter(code => error_codes_1.ErrorCodes.nonDeprecatedErrorCodes.some(e => e.code === code)); } pushState(state) { this.stateStack.push(state); } popState() { if (this.stateStack.length > 1) { this.stateStack.pop(); this.enabledComments = Array.from(this.currentState.enabledCodes); } } handleNode(node) { this.cleanupNextLineComments(node.startPosition.row); if (!isDiagnosticComment(node)) { return; } const comment = parseDiagnosticComment(node); if (!comment) return; 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 (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, }; const controlPoint = { line: comment.lineNumber, action: comment.action, codes: comment.codes, isNextLine: comment.target === 'next-line', }; this.controlPoints.push(controlPoint); this.controlPoints.sort((a, b) => a.line - b.line); if (comment.target === 'next-line') { this.pushState(newState); } else { if (this.stateStack.length > 1) { this.popState(); } this.pushState(newState); } this.enabledComments = Array.from(newEnabledCodes); } cleanupNextLineComments(currentLine) { while (this.stateStack.length > 1 && this.currentState.comment.target === 'next-line' && currentLine > this.currentState.comment.lineNumber + 1) { this.popState(); } } finalizeStateMap(maxLine) { let currentState = { enabledCodes: new Set(this.globalEnabledCodes()), }; const regularPoints = this.controlPoints.filter(p => !p.isNextLine); const nextLinePoints = new Map(); for (const point of this.controlPoints) { if (point.isNextLine) { const targetLine = point.line + 1; const existing = nextLinePoints.get(targetLine) || []; existing.push({ ...point, line: targetLine }); nextLinePoints.set(targetLine, existing); } } for (let line = 0; line <= maxLine; line++) { for (const point of regularPoints) { if (point.line <= line) { this.applyControlPointToState(currentState, point); } } const baseState = { enabledCodes: new Set(currentState.enabledCodes), }; const nextLineDirs = nextLinePoints.get(line) || []; for (const directive of nextLineDirs) { this.applyControlPointToState(currentState, directive); } this.lineStateMap.set(line, { enabledCodes: new Set(currentState.enabledCodes), }); if (nextLineDirs.length > 0) { currentState = baseState; } } } applyControlPointToState(state, point) { if (point.action === 'disable') { for (const code of point.codes) { state.enabledCodes.delete(code); } } else { for (const code of point.codes) { state.enabledCodes.add(code); } } } isCodeEnabledAtNode(code, node) { const position = { line: node.startPosition.row, character: node.startPosition.column }; return this.isCodeEnabledAtPosition(code, position); } isCodeEnabledAtPosition(code, position) { if (this.lineStateMap.has(position.line)) { const state = this.lineStateMap.get(position.line); return state.enabledCodes.has(code); } return this.computeStateAtPosition(position).enabledCodes.has(code); } computeStateAtPosition(position) { const state = { enabledCodes: new Set(this.globalEnabledCodes()), }; for (const point of this.controlPoints) { if (point.line > position.line) { break; } if (!point.isNextLine && point.line <= position.line) { this.applyControlPointToState(state, point); } if (point.isNextLine && point.line + 1 === position.line) { this.applyControlPointToState(state, { ...point, line: position.line }); } } return state; } isCodeEnabled(code) { return this.currentState.enabledCodes.has(code); } getStackDepth() { return this.stateStack.length; } getCurrentState() { return this.currentState; } getCurrentStateVerbose() { const currentState = this.getCurrentState(); const disabledCodes = error_codes_1.ErrorCodes.allErrorCodes.filter(e => !currentState.enabledCodes.has(e)); const enabledCodes = Array.from(currentState.enabledCodes) .map(e => error_codes_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(' | ')}`); const lineStates = Array.from(this.lineStateMap.entries()) .map(([line, state]) => `Line ${line}: ${Array.from(state.enabledCodes).length} enabled codes`); 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, }, controlPoints: this.controlPoints.length, lineStates: lineStates.slice(0, 10), }; } } exports.DiagnosticCommentsHandler = DiagnosticCommentsHandler;