From a35ea3bedb5c312c46c1f7d6351da3f7df0c0937 Mon Sep 17 00:00:00 2001 From: Glenn Sarti Date: Fri, 22 Jun 2018 13:39:12 +0800 Subject: [PATCH] (GH-33) Add Code Action provider for ignoring Lint Errors TODO --- lib/languageserver/code_action.rb | 122 ++++++++++++++++++ lib/languageserver/constants.rb | 73 +++++++++++ lib/languageserver/languageserver.rb | 2 +- .../manifest/code_action_provider.rb | 51 ++++++++ lib/puppet-languageserver/message_router.rb | 15 +++ lib/puppet-languageserver/providers.rb | 1 + .../server_capabilities.rb | 1 + 7 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 lib/languageserver/code_action.rb create mode 100644 lib/puppet-languageserver/manifest/code_action_provider.rb diff --git a/lib/languageserver/code_action.rb b/lib/languageserver/code_action.rb new file mode 100644 index 00000000..4229bc6e --- /dev/null +++ b/lib/languageserver/code_action.rb @@ -0,0 +1,122 @@ +module LanguageServer + # /** + # * Params for the CodeActionRequest + # */ + # interface CodeActionParams { + # /** + # * The document in which the command was invoked. + # */ + # textDocument: TextDocumentIdentifier; + + # /** + # * The range for which the command was invoked. + # */ + # range: Range; + + # /** + # * Context carrying additional information. + # */ + # context: CodeActionContext; + # } + module CodeActionRequest + def self.create(options) + result = {} + raise('textDocument is a required field for CodeActionRequest') if options['textDocument'].nil? + raise('range is a required field for CodeActionRequest') if options['range'].nil? + raise('context is a required field for CodeActionRequest') if options['context'].nil? + + result['textDocument'] = options['textDocument'] + result['range'] = options['range'] + result['context'] = CodeActionContext.create(options['context']) + + result + end + end + + # /** + # * Contains additional diagnostic information about the context in which + # * a code action is run. + # */ + # interface CodeActionContext { + # /** + # * An array of diagnostics. + # */ + # diagnostics: Diagnostic[]; + + # /** + # * Requested kind of actions to return. + # * + # * Actions not of this kind are filtered out by the client before being shown. So servers + # * can omit computing them. + # */ + # only?: CodeActionKind[]; + # } + module CodeActionContext + def self.create(options) + result = {} + raise('diagnostics is a required field for CodeActionContext') if options['diagnostics'].nil? + + result['diagnostics'] = [] + + options['diagnostics'].each do |diag| + # TODO: Should really ask Diagnostic.create to create the object + result['diagnostics'] << diag + end + result['only'] = options['only'] unless options['only'].nil? + + result + end + end + + # /** + # * A code action represents a change that can be performed in code, e.g. to fix a problem or + # * to refactor code. + # * + # * A CodeAction must set either `edit` and/or a `command`. If both are supplied, the `edit` is applied first, then the `command` is executed. + # */ + # export interface CodeAction { + + # /** + # * A short, human-readable, title for this code action. + # */ + # title: string; + + # /** + # * The kind of the code action. + # * + # * Used to filter code actions. + # */ + # kind?: CodeActionKind; + + # /** + # * The diagnostics that this code action resolves. + # */ + # diagnostics?: Diagnostic[]; + + # /** + # * The workspace edit this code action performs. + # */ + # edit?: WorkspaceEdit; + + # /** + # * A command this code action executes. If a code action + # * provides an edit and a command, first the edit is + # * executed and then the command. + # */ + # command?: Command; + # } + module CodeAction + def self.create(options) + result = {} + raise('title is a required field for CodeAction') if options['title'].nil? + + result['title'] = options['title'] + result['kind'] = options['kind'] unless options['kind'].nil? + result['diagnostics'] = options['diagnostics'] unless options['diagnostics'].nil? + result['edit'] = options['edit'] unless options['edit'].nil? + result['command'] = options['command'] unless options['command'].nil? + + result + end + end +end diff --git a/lib/languageserver/constants.rb b/lib/languageserver/constants.rb index 72732cb7..ff5c62e2 100644 --- a/lib/languageserver/constants.rb +++ b/lib/languageserver/constants.rb @@ -61,4 +61,77 @@ module LanguageServer MESSAGE_TYPE_WARNING = 2 MESSAGE_TYPE_INFO = 3 MESSAGE_TYPE_LOG = 2 + + # /** + # * A set of predefined code action kinds + # */ + # export namespace CodeActionKind { + # /** + # * Base kind for quickfix actions: 'quickfix' + # */ + # export const QuickFix: CodeActionKind = 'quickfix'; + + # /** + # * Base kind for refactoring actions: 'refactor' + # */ + # export const Refactor: CodeActionKind = 'refactor'; + + # /** + # * Base kind for refactoring extraction actions: 'refactor.extract' + # * + # * Example extract actions: + # * + # * - Extract method + # * - Extract function + # * - Extract variable + # * - Extract interface from class + # * - ... + # */ + # export const RefactorExtract: CodeActionKind = 'refactor.extract'; + + # /** + # * Base kind for refactoring inline actions: 'refactor.inline' + # * + # * Example inline actions: + # * + # * - Inline function + # * - Inline variable + # * - Inline constant + # * - ... + # */ + # export const RefactorInline: CodeActionKind = 'refactor.inline'; + + # /** + # * Base kind for refactoring rewrite actions: 'refactor.rewrite' + # * + # * Example rewrite actions: + # * + # * - Convert JavaScript function to class + # * - Add or remove parameter + # * - Encapsulate field + # * - Make method static + # * - Move method to base class + # * - ... + # */ + # export const RefactorRewrite: CodeActionKind = 'refactor.rewrite'; + + # /** + # * Base kind for source actions: `source` + # * + # * Source code actions apply to the entire file. + # */ + # export const Source: CodeActionKind = 'source'; + + # /** + # * Base kind for an organize imports source action: `source.organizeImports` + # */ + # export const SourceOrganizeImports: CodeActionKind = 'source.organizeImports'; + # } + CODEACTIONKIND_QUICKFIX = 'quickfix'.freeze + CODEACTIONKIND_REFACTOR = 'refactor'.freeze + CODEACTIONKIND_REFACTOREXTRACT = 'refactor.extract'.freeze + CODEACTIONKIND_REFACTORINLINE = 'refactor.inline'.freeze + CODEACTIONKIND_REFACTORREWRITE = 'refactor.rewrite'.freeze + CODEACTIONKIND_SOURCE = 'source'.freeze + CODEACTIONKIND_SOURCEORGANIZEIMPORS = 'source.organizeImports'.freeze end diff --git a/lib/languageserver/languageserver.rb b/lib/languageserver/languageserver.rb index cdcb84e1..70bb90ac 100644 --- a/lib/languageserver/languageserver.rb +++ b/lib/languageserver/languageserver.rb @@ -1,4 +1,4 @@ -%w[constants diagnostic completion_list completion_item document_symbol hover location puppet_version puppet_compilation puppet_fix_diagnostic_errors].each do |lib| +%w[constants diagnostic code_action completion_list completion_item document_symbol hover location puppet_version puppet_compilation puppet_fix_diagnostic_errors].each do |lib| begin require "languageserver/#{lib}" rescue LoadError diff --git a/lib/puppet-languageserver/manifest/code_action_provider.rb b/lib/puppet-languageserver/manifest/code_action_provider.rb new file mode 100644 index 00000000..9e23d62c --- /dev/null +++ b/lib/puppet-languageserver/manifest/code_action_provider.rb @@ -0,0 +1,51 @@ +module PuppetLanguageServer + module Manifest + module CodeActionProvider + def self.provide_actions(code_action_request) + # We only provide actions on diagnostics + return nil if code_action_request['context']['diagnostics'].empty? + file_uri = code_action_request['textDocument']['uri'] + content = PuppetLanguageServer::DocumentStore.document(file_uri) + # We only providea actions on files that are being edited + return nil if content.nil? + content_version = PuppetLanguageServer::DocumentStore.document_version(file_uri) + # Extract the per line information + content_lines = content.lines + + result = [] + code_action_request['context']['diagnostics'].each do |diag| + text_change = " # lint:ignore:#{diag['code']}" + diag_start_line = diag['range']['start']['line'] + result << LanguageServer::CodeAction.create( + 'title' => " Ignore '#{diag['message']}'", + 'edit' => { + 'documentChanges' => [{ + 'textDocument' => { + 'uri' => file_uri, + 'version' => content_version + }, + 'edits' => [ + { + 'range' => { + 'start' => { 'line' => diag_start_line, 'character' => line_length(content_lines, diag_start_line) }, + 'end' => { 'line' => diag_start_line, 'character' => line_length(content_lines, diag_start_line) + 1 } + }, + 'newText' => text_change + } + ] + }] + }, + 'kind' => LanguageServer::CODEACTIONKIND_REFACTORINLINE, + 'diagnostics' => [diag] + ) + end + + result + end + + def self.line_length(content_lines, line_number) + content_lines[line_number].chomp.length + end + end + end +end diff --git a/lib/puppet-languageserver/message_router.rb b/lib/puppet-languageserver/message_router.rb index 59a7db06..77963a34 100644 --- a/lib/puppet-languageserver/message_router.rb +++ b/lib/puppet-languageserver/message_router.rb @@ -132,6 +132,21 @@ def receive_request(request) request.reply_result(LanguageServer::Hover.create_nil_response) end + when 'textDocument/codeAction' + begin + request_object = LanguageServer::CodeActionRequest.create(request.params) + file_uri = request_object['textDocument']['uri'] + case documents.document_type(file_uri) + when :manifest + request.reply_result(PuppetLanguageServer::Manifest::CodeActionProvider.provide_actions(request_object)) + else + raise "Unable to provide code actions on #{file_uri}" + end + rescue StandardError => exception + PuppetLanguageServer.log_message(:error, "(textDocument/codeAction) #{exception}") + request.reply_result(nil) + end + when 'textDocument/definition' file_uri = request.params['textDocument']['uri'] line_num = request.params['position']['line'] diff --git a/lib/puppet-languageserver/providers.rb b/lib/puppet-languageserver/providers.rb index 9c88636c..1a69d6a4 100644 --- a/lib/puppet-languageserver/providers.rb +++ b/lib/puppet-languageserver/providers.rb @@ -1,5 +1,6 @@ %w[ epp/validation_provider + manifest/code_action_provider manifest/completion_provider manifest/definition_provider manifest/document_symbol_provider diff --git a/lib/puppet-languageserver/server_capabilities.rb b/lib/puppet-languageserver/server_capabilities.rb index 53ce85a9..609ed153 100644 --- a/lib/puppet-languageserver/server_capabilities.rb +++ b/lib/puppet-languageserver/server_capabilities.rb @@ -6,6 +6,7 @@ def self.capabilities { 'textDocumentSync' => LanguageServer::TEXTDOCUMENTSYNCKIND_FULL, 'hoverProvider' => true, + 'codeActionProvider' => true, 'completionProvider' => { 'resolveProvider' => true, 'triggerCharacters' => ['>', '$', '[', '=']