diff --git a/lib/languageserver/languageserver.rb b/lib/languageserver/languageserver.rb index 6ae81e54..22db2209 100644 --- a/lib/languageserver/languageserver.rb +++ b/lib/languageserver/languageserver.rb @@ -1,4 +1,4 @@ -%w[constants diagnostic completion_list completion_item hover location puppet_version puppet_compilation].each do |lib| +%w[constants diagnostic completion_list completion_item hover location puppet_version puppet_compilation puppet_fix_diagnostic_errors].each do |lib| begin require "languageserver/#{lib}" rescue LoadError diff --git a/lib/languageserver/puppet_fix_diagnostic_errors.rb b/lib/languageserver/puppet_fix_diagnostic_errors.rb new file mode 100644 index 00000000..e1155ff6 --- /dev/null +++ b/lib/languageserver/puppet_fix_diagnostic_errors.rb @@ -0,0 +1,39 @@ +module LanguageServer + module PuppetFixDiagnosticErrorsRequest + # export interface PuppetFixDiagnosticErrorsRequestParams { + # documentUri: string; + # alwaysReturnContent: boolean; + # } + + def self.create(options) + result = { + 'alwaysReturnContent' => false + } + raise('documentUri is a required field for PuppetFixDiagnosticErrorsRequest') if options['documentUri'].nil? + + result['documentUri'] = options['documentUri'] + result['alwaysReturnContent'] = options['alwaysReturnContent'] unless options['alwaysReturnContent'].nil? + result + end + end + + module PuppetFixDiagnosticErrorsResponse + # export interface PuppetFixDiagnosticErrorsResponse { + # documentUri: string; + # fixesApplied: number; + # newContent?: string; + # } + + def self.create(options) + result = {} + raise('documentUri is a required field for PuppetFixDiagnosticErrorsResponse') if options['documentUri'].nil? + raise('fixesApplied is a required field for PuppetFixDiagnosticErrorsResponse') if options['fixesApplied'].nil? + + result['documentUri'] = options['documentUri'] + result['fixesApplied'] = options['fixesApplied'] + result['newContent'] = options['newContent'] unless options['newContent'].nil? + + result + end + end +end diff --git a/lib/puppet-languageserver/document_validator.rb b/lib/puppet-languageserver/document_validator.rb index 21285cd4..553cf6bc 100644 --- a/lib/puppet-languageserver/document_validator.rb +++ b/lib/puppet-languageserver/document_validator.rb @@ -24,6 +24,33 @@ def self.find_module_root_from_path(path) module_root end + # Similar to 'validate' this will run puppet-lint and returns + # the manifest with any fixes applied + # + # Returns: + # [ Number of problems fixed, + # New Content + # ] + def self.fix_validate_errors(content, workspace) + # Find module root and attempt to build PuppetLint options + module_root = find_module_root_from_path(workspace) + linter_options = nil + if module_root.nil? + linter_options = PuppetLint::OptParser.build + else + Dir.chdir(module_root.to_s) { linter_options = PuppetLint::OptParser.build } + end + linter_options.parse!(['--fix']) + + linter = PuppetLint::Checks.new + linter.load_data(nil, content) + + problems = linter.run(nil, content) + problems_fixed = problems.nil? ? 0 : problems.count { |item| item[:kind] == :fixed } + + [problems_fixed, linter.manifest] + end + def self.validate(content, workspace, _max_problems = 100) result = [] # TODO: Need to implement max_problems diff --git a/lib/puppet-languageserver/message_router.rb b/lib/puppet-languageserver/message_router.rb index fe2f52f7..b7b20561 100644 --- a/lib/puppet-languageserver/message_router.rb +++ b/lib/puppet-languageserver/message_router.rb @@ -153,6 +153,30 @@ def receive_request(request) request.reply_result(LanguageServer::PuppetCompilation.create('dotContent' => dot_content, 'error' => error_content)) + when 'puppet/fixDiagnosticErrors' + begin + formatted_request = LanguageServer::PuppetFixDiagnosticErrorsRequest.create(request.params) + file_uri = formatted_request['documentUri'] + content = documents.document(file_uri) + + changes, new_content = PuppetLanguageServer::DocumentValidator.fix_validate_errors(content, @workspace) + + request.reply_result(LanguageServer::PuppetFixDiagnosticErrorsResponse.create( + 'documentUri' => formatted_request['documentUri'], + 'fixesApplied' => changes, + 'newContent' => changes > 0 || formatted_request['alwaysReturnContent'] ? new_content : nil + )) + rescue StandardError => exception + PuppetLanguageServer.log_message(:error, "(puppet/fixDiagnosticErrors) #{exception}") + unless formatted_request.nil? + request.reply_result(LanguageServer::PuppetFixDiagnosticErrorsResponse.create( + 'documentUri' => formatted_request['documentUri'], + 'fixesApplied' => 0, + 'newContent' => formatted_request['alwaysReturnContent'] ? content : nil # rubocop:disable Metrics/BlockNesting + )) + end + end + when 'textDocument/completion' file_uri = request.params['textDocument']['uri'] line_num = request.params['position']['line'] diff --git a/spec/languageserver/integration/puppet-languageserver/document_validator_spec.rb b/spec/languageserver/integration/puppet-languageserver/document_validator_spec.rb index 8fcba545..b90bfba5 100644 --- a/spec/languageserver/integration/puppet-languageserver/document_validator_spec.rb +++ b/spec/languageserver/integration/puppet-languageserver/document_validator_spec.rb @@ -3,6 +3,102 @@ describe 'document_validator' do let(:subject) { PuppetLanguageServer::DocumentValidator } + describe '#fix_validate_errors' do + describe "Given an incomplete manifest which has syntax errors but no lint errors" do + let(:manifest) { 'user { \'Bob\'' } + + it "should return no changes" do + problems_fixed, new_content = subject.fix_validate_errors(manifest, nil) + expect(problems_fixed).to eq(0) + expect(new_content).to eq(manifest) + end + end + + describe "Given a complete manifest which has a single fixable lint errors" do + let(:manifest) { " + user { \"Bob\": + ensure => 'present' + }" + } + let(:new_manifest) { " + user { 'Bob': + ensure => 'present' + }" + } + + it "should return changes" do + problems_fixed, new_content = subject.fix_validate_errors(manifest, nil) + expect(problems_fixed).to eq(1) + expect(new_content).to eq(new_manifest) + end + end + + describe "Given a complete manifest which has multiple fixable lint errors" do + let(:manifest) { " + // bad comment + user { \"Bob\": + name => 'username', + ensure => 'present' + }" + } + let(:new_manifest) { " + # bad comment + user { 'Bob': + name => 'username', + ensure => 'present' + }" + } + + it "should return changes" do + problems_fixed, new_content = subject.fix_validate_errors(manifest, nil) + expect(problems_fixed).to eq(3) + expect(new_content).to eq(new_manifest) + end + end + + + describe "Given a complete manifest which has unfixable lint errors" do + let(:manifest) { " + user { 'Bob': + name => 'name', + ensure => 'present' + }" + } + + it "should return no changes" do + problems_fixed, new_content = subject.fix_validate_errors(manifest, nil) + expect(problems_fixed).to eq(0) + expect(new_content).to eq(manifest) + end + end + + describe "Given a complete manifest with CRLF which has fixable lint errors" do + let(:manifest) { "user { \"Bob\":\r\nensure => 'present'\r\n}" } + let(:new_manifest) { "user { 'Bob':\r\nensure => 'present'\r\n}" } + + it "should preserve CRLF" do + pending('Release of https://github.com/rodjek/puppet-lint/commit/2a850ab3fd3694a4dd0c4d2f22a1e60b9ca0a495') + problems_fixed, new_content = subject.fix_validate_errors(manifest, nil) + expect(problems_fixed).to eq(1) + expect(new_content).to eq(new_manifest) + end + end + + describe "Given a complete manifest which has disabed fixable lint errors" do + let(:manifest) { " + user { \"Bob\": # lint:ignore:double_quoted_strings + ensure => 'present' + }" + } + + it "should return no changes" do + problems_fixed, new_content = subject.fix_validate_errors(manifest, nil) + expect(problems_fixed).to eq(0) + expect(new_content).to eq(manifest) + end + end + end + describe '#validate' do describe "Given an incomplete manifest which has syntax errors" do let(:manifest) { 'user { "Bob"' }