UNPKG

yaml-language-server

Version:
379 lines 20.9 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Red Hat, Inc. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.YamlCodeActions = void 0; const l10n = __importStar(require("@vscode/l10n")); const path = __importStar(require("path")); const jsonLanguageTypes_1 = require("../jsonLanguageTypes"); const vscode_languageserver_types_1 = require("vscode-languageserver-types"); const yaml_1 = require("yaml"); const commands_1 = require("../../commands"); const textBuffer_1 = require("../utils/textBuffer"); const yamlScalar_1 = require("../utils/yamlScalar"); const baseValidator_1 = require("../parser/schemaValidation/baseValidator"); const strings_1 = require("../utils/strings"); const arrUtils_1 = require("../utils/arrUtils"); const yaml_documents_1 = require("../parser/yaml-documents"); const block_string_rewriter_1 = require("../utils/block-string-rewriter"); const flow_style_rewriter_1 = require("../utils/flow-style-rewriter"); class YamlCodeActions { constructor(clientCapabilities) { this.clientCapabilities = clientCapabilities; this.indentation = ' '; this.lineWidth = 80; } configure(settings, printWidth) { this.indentation = settings.indentation; this.lineWidth = printWidth; } getCodeAction(document, params) { if (!params.context.diagnostics) { return; } const result = []; result.push(...this.getConvertToBooleanActions(params.context.diagnostics, document)); result.push(...this.getJumpToSchemaActions(params.context.diagnostics)); result.push(...this.getTabToSpaceConverting(params.context.diagnostics, document)); result.push(...this.getUnusedAnchorsDelete(params.context.diagnostics, document)); result.push(...this.getConvertToBlockStyleActions(params.context.diagnostics, document)); result.push(...this.getConvertStringToBlockStyleActions(params.range, document)); result.push(...this.getKeyOrderActions(params.context.diagnostics, document)); result.push(...this.getQuickFixForPropertyOrValueMismatch(params.context.diagnostics, document)); return result; } getJumpToSchemaActions(diagnostics) { const isOpenTextDocumentEnabled = this.clientCapabilities?.window?.showDocument?.support ?? false; if (!isOpenTextDocumentEnabled) { return []; } const schemaUriToDiagnostic = new Map(); for (const diagnostic of diagnostics) { const schemaUri = diagnostic.data?.schemaUri || []; for (const schemaUriStr of schemaUri) { if (schemaUriStr) { if (!schemaUriToDiagnostic.has(schemaUriStr)) { schemaUriToDiagnostic.set(schemaUriStr, []); } schemaUriToDiagnostic.get(schemaUriStr).push(diagnostic); } } } const result = []; for (const schemaUri of schemaUriToDiagnostic.keys()) { const action = vscode_languageserver_types_1.CodeAction.create(l10n.t('Jump to schema location ({0})', path.basename(schemaUri)), vscode_languageserver_types_1.Command.create('JumpToSchema', commands_1.YamlCommands.JUMP_TO_SCHEMA, schemaUri)); action.diagnostics = schemaUriToDiagnostic.get(schemaUri); result.push(action); } return result; } getTabToSpaceConverting(diagnostics, document) { const result = []; const textBuff = new textBuffer_1.TextBuffer(document); const processedLine = []; for (const diag of diagnostics) { if (diag.message === 'Tabs are not allowed as indentation') { if (processedLine.includes(diag.range.start.line)) { continue; } const lineContent = textBuff.getLineContent(diag.range.start.line); let replacedTabs = 0; let newText = ''; for (let i = diag.range.start.character; i <= diag.range.end.character; i++) { const char = lineContent.charAt(i); if (char !== '\t') { break; } replacedTabs++; newText += this.indentation; } processedLine.push(diag.range.start.line); let resultRange = diag.range; if (replacedTabs !== diag.range.end.character - diag.range.start.character) { resultRange = vscode_languageserver_types_1.Range.create(diag.range.start, vscode_languageserver_types_1.Position.create(diag.range.end.line, diag.range.start.character + replacedTabs)); } result.push(vscode_languageserver_types_1.CodeAction.create(l10n.t('Convert Tab to Spaces'), createWorkspaceEdit(document.uri, [vscode_languageserver_types_1.TextEdit.replace(resultRange, newText)]), vscode_languageserver_types_1.CodeActionKind.QuickFix)); } } if (result.length !== 0) { const replaceEdits = []; for (let i = 0; i <= textBuff.getLineCount(); i++) { const lineContent = textBuff.getLineContent(i); let replacedTabs = 0; let newText = ''; for (let j = 0; j < lineContent.length; j++) { const char = lineContent.charAt(j); if (char !== ' ' && char !== '\t') { if (replacedTabs !== 0) { replaceEdits.push(vscode_languageserver_types_1.TextEdit.replace(vscode_languageserver_types_1.Range.create(i, j - replacedTabs, i, j), newText)); replacedTabs = 0; newText = ''; } break; } if (char === ' ' && replacedTabs !== 0) { replaceEdits.push(vscode_languageserver_types_1.TextEdit.replace(vscode_languageserver_types_1.Range.create(i, j - replacedTabs, i, j), newText)); replacedTabs = 0; newText = ''; continue; } if (char === '\t') { newText += this.indentation; replacedTabs++; } } // line contains only tabs if (replacedTabs !== 0) { replaceEdits.push(vscode_languageserver_types_1.TextEdit.replace(vscode_languageserver_types_1.Range.create(i, 0, i, textBuff.getLineLength(i)), newText)); } } if (replaceEdits.length > 0) { result.push(vscode_languageserver_types_1.CodeAction.create(l10n.t('Convert all Tabs to Spaces'), createWorkspaceEdit(document.uri, replaceEdits), vscode_languageserver_types_1.CodeActionKind.QuickFix)); } } return result; } getUnusedAnchorsDelete(diagnostics, document) { const result = []; const buffer = new textBuffer_1.TextBuffer(document); for (const diag of diagnostics) { if (diag.message.startsWith('Unused anchor') && diag.source === baseValidator_1.YAML_SOURCE) { const range = vscode_languageserver_types_1.Range.create(diag.range.start, diag.range.end); const actual = buffer.getText(range); const lineContent = buffer.getLineContent(range.end.line); const lastWhitespaceChar = (0, strings_1.getFirstNonWhitespaceCharacterAfterOffset)(lineContent, range.end.character); range.end.character = lastWhitespaceChar; const action = vscode_languageserver_types_1.CodeAction.create(l10n.t('Delete unused anchor: {0}', actual), createWorkspaceEdit(document.uri, [vscode_languageserver_types_1.TextEdit.del(range)]), vscode_languageserver_types_1.CodeActionKind.QuickFix); action.diagnostics = [diag]; result.push(action); } } return result; } getConvertToBooleanActions(diagnostics, document) { const results = []; for (const diagnostic of diagnostics) { if (diagnostic.message === 'Incorrect type. Expected "boolean".') { const value = document.getText(diagnostic.range).toLocaleLowerCase(); if (value === '"true"' || value === '"false"' || value === "'true'" || value === "'false'") { const newValue = value.includes('true') ? 'true' : 'false'; results.push(vscode_languageserver_types_1.CodeAction.create(l10n.t('Convert to boolean'), createWorkspaceEdit(document.uri, [vscode_languageserver_types_1.TextEdit.replace(diagnostic.range, newValue)]), vscode_languageserver_types_1.CodeActionKind.QuickFix)); } } } return results; } getConvertToBlockStyleActions(diagnostics, document) { const results = []; for (const diagnostic of diagnostics) { if (diagnostic.code === 'flowMap' || diagnostic.code === 'flowSeq') { const node = getNodeForDiagnostic(document, diagnostic); if ((0, yaml_1.isMap)(node.internalNode) || (0, yaml_1.isSeq)(node.internalNode)) { const blockTypeDescription = (0, yaml_1.isMap)(node.internalNode) ? 'map' : 'sequence'; const rewriter = new flow_style_rewriter_1.FlowStyleRewriter(this.indentation); results.push(vscode_languageserver_types_1.CodeAction.create(l10n.t('Convert to block style {0}', blockTypeDescription), createWorkspaceEdit(document.uri, [vscode_languageserver_types_1.TextEdit.replace(diagnostic.range, rewriter.write(node))]), vscode_languageserver_types_1.CodeActionKind.QuickFix)); } } } return results; } getConvertStringToBlockStyleActions(range, document) { const yamlDocument = yaml_documents_1.yamlDocumentsCache.getYamlDocument(document); const startOffset = range ? document.offsetAt(range.start) : 0; const endOffset = range ? document.offsetAt(range.end) : Infinity; const results = []; for (const singleYamlDocument of yamlDocument.documents) { const matchingNodes = []; (0, yaml_1.visit)(singleYamlDocument.internalDocument, (key, node) => { if ((0, yaml_1.isScalar)(node)) { if ((startOffset <= node.range[0] && node.range[2] <= endOffset) || (node.range[0] <= startOffset && endOffset <= node.range[2])) { if (node.type === 'QUOTE_DOUBLE' || node.type === 'QUOTE_SINGLE') { if (typeof node.value === 'string' && (node.value.indexOf('\n') >= 0 || node.value.length > this.lineWidth)) { matchingNodes.push(node); } } } } }); for (const node of matchingNodes) { const range = vscode_languageserver_types_1.Range.create(document.positionAt(node.range[0]), document.positionAt(node.range[1])); const rewriter = new block_string_rewriter_1.BlockStringRewriter(this.indentation, this.lineWidth); const foldedBlockScalar = rewriter.writeFoldedBlockScalar(node); if (foldedBlockScalar !== null) { results.push(vscode_languageserver_types_1.CodeAction.create(l10n.t('Convert string to folded block string'), createWorkspaceEdit(document.uri, [vscode_languageserver_types_1.TextEdit.replace(range, foldedBlockScalar)]), vscode_languageserver_types_1.CodeActionKind.Refactor)); } const literalBlockScalar = rewriter.writeLiteralBlockScalar(node); if (literalBlockScalar !== null) { results.push(vscode_languageserver_types_1.CodeAction.create(l10n.t('Convert string to literal block string'), createWorkspaceEdit(document.uri, [vscode_languageserver_types_1.TextEdit.replace(range, literalBlockScalar)]), vscode_languageserver_types_1.CodeActionKind.Refactor)); } } } return results; } getKeyOrderActions(diagnostics, document) { const results = []; for (const diagnostic of diagnostics) { if (diagnostic?.code === 'mapKeyOrder') { let node = getNodeForDiagnostic(document, diagnostic); while (node && node.type !== 'object') { node = node.parent; } if (node && (0, yaml_1.isMap)(node.internalNode)) { const sorted = structuredClone(node.internalNode); const _getTrailingTokens = (value) => { if (!value) return; if (yaml_1.CST.isScalar(value)) { if (value.type === 'block-scalar') { value.props ?? (value.props = []); return value.props; } value.end ?? (value.end = []); return value.end; } if (value.type === 'flow-collection') { value.end ?? (value.end = []); return value.end; } if (value.type === 'block-map') { const lastItem = value.items[value.items.length - 1]; return lastItem ? _getTrailingTokens(lastItem.value) : undefined; } return; }; if ((sorted.srcToken.type === 'block-map' || sorted.srcToken.type === 'flow-collection') && (node.internalNode.srcToken.type === 'block-map' || node.internalNode.srcToken.type === 'flow-collection')) { sorted.srcToken.items.sort((a, b) => { if (a.key && b.key && yaml_1.CST.isScalar(a.key) && yaml_1.CST.isScalar(b.key)) { return a.key.source.localeCompare(b.key.source); } if (!a.key && b.key) { return -1; } if (a.key && !b.key) { return 1; } if (!a.key && !b.key) { return 0; } }); for (let i = 0; i < sorted.srcToken.items.length; i++) { const item = sorted.srcToken.items[i]; const uItem = node.internalNode.srcToken.items[i]; item.start = uItem.start; // strip leading blank lines so reordered entries stay one-per-line while (item.start?.[0]?.type === 'newline') item.start.shift(); const itemTokens = _getTrailingTokens(item.value); const itemNewLineIndex = itemTokens?.findIndex((p) => p.type === 'newline') ?? -1; const uNewLineToken = _getTrailingTokens(uItem.value)?.find((p) => p.type === 'newline' && p.offset < node.offset + node.length) ?? null; if (uNewLineToken && itemNewLineIndex < 0 && itemTokens) { itemTokens.push({ type: 'newline', indent: 0, offset: item.value.offset, source: '\n' }); } if (!uNewLineToken && itemNewLineIndex > -1 && itemTokens) { itemTokens.splice(itemNewLineIndex, 1); } } } const replaceRange = vscode_languageserver_types_1.Range.create(document.positionAt(node.offset), document.positionAt(node.offset + node.length)); results.push(vscode_languageserver_types_1.CodeAction.create(l10n.t('Fix key order for this map'), createWorkspaceEdit(document.uri, [vscode_languageserver_types_1.TextEdit.replace(replaceRange, yaml_1.CST.stringify(sorted.srcToken))]), vscode_languageserver_types_1.CodeActionKind.QuickFix)); } } } return results; } /** * Check if diagnostic contains info for quick fix * Supports Enum/Const/Property mismatch */ getPossibleQuickFixValues(diagnostic) { if (typeof diagnostic.data !== 'object') { return; } if (diagnostic.code === jsonLanguageTypes_1.ErrorCode.EnumValueMismatch && 'values' in diagnostic.data && Array.isArray(diagnostic.data.values)) { return diagnostic.data.values; } else if (diagnostic.code === jsonLanguageTypes_1.ErrorCode.PropertyExpected && 'properties' in diagnostic.data && Array.isArray(diagnostic.data.properties)) { return diagnostic.data.properties; } } getQuickFixForPropertyOrValueMismatch(diagnostics, document) { const results = []; for (const diagnostic of diagnostics) { const values = this.getPossibleQuickFixValues(diagnostic); if (!values?.length) { continue; } for (const value of values) { const scalar = typeof value === 'string' ? (0, yamlScalar_1.toYamlStringScalar)(value) : String(value); results.push(vscode_languageserver_types_1.CodeAction.create(scalar, createWorkspaceEdit(document.uri, [vscode_languageserver_types_1.TextEdit.replace(diagnostic.range, scalar)]), vscode_languageserver_types_1.CodeActionKind.QuickFix)); } } return results; } } exports.YamlCodeActions = YamlCodeActions; function getNodeForDiagnostic(document, diagnostic) { const yamlDocuments = yaml_documents_1.yamlDocumentsCache.getYamlDocument(document); const startOffset = document.offsetAt(diagnostic.range.start); const endOffset = document.offsetAt(diagnostic.range.end); const yamlDoc = (0, arrUtils_1.matchOffsetToDocument)(startOffset, yamlDocuments); let node = yamlDoc.getNodeFromOffset(startOffset); if (node && startOffset < endOffset && node.offset + node.length === startOffset) { const nodeInsideRange = yamlDoc.getNodeFromOffset(startOffset + 1); if (nodeInsideRange) { node = nodeInsideRange; } } return node; } function createWorkspaceEdit(uri, edits) { const changes = {}; changes[uri] = edits; const edit = { changes, }; return edit; } //# sourceMappingURL=yamlCodeActions.js.map