Skip to content

Commit 576d33e

Browse files
authored
Merge pull request #210 from glennsarti/add-puppet-lint-fix
(GH-50) Add document formatter for puppet-lint
2 parents ce6d6fb + 97897aa commit 576d33e

File tree

10 files changed

+260
-2
lines changed

10 files changed

+260
-2
lines changed

client/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,12 @@
267267
"type": "object",
268268
"title": "puppet",
269269
"properties": {
270+
"puppet.format.enable": {
271+
"type": "boolean",
272+
"scope": "window",
273+
"default": true,
274+
"description": "Enable/disable the Puppet formatter"
275+
},
270276
"puppet.languageserver.address": {
271277
"type": "string",
272278
"default": "127.0.0.1",

client/src/commands/puppetcommands.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getNodeGraphUri, showNodeGraph
88
} from '../../src/providers/previewNodeGraphProvider';
99
import { puppetResourceCommand } from '../commands/puppet/puppetResourceCommand';
10+
import { PuppetFormatDocumentProvider } from '../providers/puppetFormatDocumentProvider';
1011

1112
export function setupPuppetCommands(langID:string, connManager:IConnectionManager, ctx:vscode.ExtensionContext, logger: ILogger){
1213

@@ -16,6 +17,16 @@ export function setupPuppetCommands(langID:string, connManager:IConnectionManage
1617
resourceCommand.run();
1718
}));
1819

20+
ctx.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider('puppet', {
21+
provideDocumentFormattingEdits: (document, options, token) => {
22+
if (vscode.workspace.getConfiguration('puppet').get('format.enable')) {
23+
return PuppetFormatDocumentProvider(document, options, connManager)
24+
} else {
25+
return []
26+
}
27+
}
28+
}));
29+
1930
ctx.subscriptions.push(vscode.commands.registerCommand(messages.PuppetCommandStrings.PuppetNodeGraphToTheSideCommandId,
2031
uri => showNodeGraph(uri, true))
2132
);

client/src/messages.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export interface PuppetVersionDetails {
1616

1717
export interface PuppetResourceRequestParams {
1818
typename: string;
19-
title:string;
19+
title: string;
2020
}
2121

2222
export namespace PuppetResourceRequest {
@@ -28,6 +28,21 @@ export interface PuppetResourceResponse {
2828
error: string;
2929
}
3030

31+
export interface PuppetFixDiagnosticErrorsRequestParams {
32+
documentUri: string;
33+
alwaysReturnContent: boolean;
34+
}
35+
36+
export namespace PuppetFixDiagnosticErrorsRequest {
37+
export const type = new RequestType<PuppetFixDiagnosticErrorsRequestParams, any, void, void>('puppet/fixDiagnosticErrors');
38+
}
39+
40+
export interface PuppetFixDiagnosticErrorsResponse {
41+
documentUri: string;
42+
fixesApplied: number;
43+
newContent: string;
44+
}
45+
3146
export namespace CompileNodeGraphRequest {
3247
export const type = new RequestType<any, any, void, void>('puppet/compileNodeGraph');
3348
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
3+
import * as vscode from 'vscode';
4+
import { ConnectionStatus } from '../interfaces';
5+
import { IConnectionManager } from '../connection';
6+
import * as messages from '../messages';
7+
8+
class RequestParams implements messages.PuppetFixDiagnosticErrorsRequestParams {
9+
documentUri: string;
10+
alwaysReturnContent: boolean;
11+
}
12+
13+
export function PuppetFormatDocumentProvider(document: vscode.TextDocument, options:vscode.FormattingOptions, connMgr: IConnectionManager) : Thenable<vscode.TextEdit[]> {
14+
if (connMgr.status != ConnectionStatus.Running ) {
15+
vscode.window.showInformationMessage("Formatting Puppet files is not available as the Language Server is not ready");
16+
return new Promise((resolve) => { resolve([]); });
17+
}
18+
19+
let requestParams = new RequestParams;
20+
requestParams.documentUri = document.uri.toString(false);
21+
requestParams.alwaysReturnContent = false;
22+
23+
return connMgr.languageClient
24+
.sendRequest(messages.PuppetFixDiagnosticErrorsRequest.type, requestParams)
25+
.then(
26+
(result) => {
27+
result = result as messages.PuppetFixDiagnosticErrorsResponse
28+
if (result.fixesApplied > 0 && result.newContent != null) {
29+
return [vscode.TextEdit.replace(new vscode.Range(0,0, document.lineCount,0), result.newContent)]
30+
} else {
31+
return []
32+
}
33+
}
34+
)
35+
}

server/lib/languageserver/constants.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,9 @@ module LanguageServer
4949
DIAGNOSTICSEVERITY_WARNING = 2
5050
DIAGNOSTICSEVERITY_INFORMATION = 3
5151
DIAGNOSTICSEVERITY_HINT = 4
52+
53+
MESSAGE_TYPE_ERROR = 1
54+
MESSAGE_TYPE_WARNING = 2
55+
MESSAGE_TYPE_INFO = 3
56+
MESSAGE_TYPE_LOG = 2
5257
end

server/lib/languageserver/languageserver.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
%w[constants diagnostic completion_list completion_item hover location puppet_version puppet_compilation].each do |lib|
1+
%w[constants diagnostic completion_list completion_item hover location puppet_version puppet_compilation puppet_fix_diagnostic_errors].each do |lib|
22
begin
33
require "languageserver/#{lib}"
44
rescue LoadError
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module LanguageServer
2+
module PuppetFixDiagnosticErrorsRequest
3+
# export interface PuppetFixDiagnosticErrorsRequestParams {
4+
# documentUri: string;
5+
# alwaysReturnContent: boolean;
6+
# }
7+
8+
def self.create(options)
9+
result = {
10+
'alwaysReturnContent' => false
11+
}
12+
raise('documentUri is a required field for PuppetFixDiagnosticErrorsRequest') if options['documentUri'].nil?
13+
14+
result['documentUri'] = options['documentUri']
15+
result['alwaysReturnContent'] = options['alwaysReturnContent'] unless options['alwaysReturnContent'].nil?
16+
result
17+
end
18+
end
19+
20+
module PuppetFixDiagnosticErrorsResponse
21+
# export interface PuppetFixDiagnosticErrorsResponse {
22+
# documentUri: string;
23+
# fixesApplied: number;
24+
# newContent?: string;
25+
# }
26+
27+
def self.create(options)
28+
result = {}
29+
raise('documentUri is a required field for PuppetFixDiagnosticErrorsResponse') if options['documentUri'].nil?
30+
raise('fixesApplied is a required field for PuppetFixDiagnosticErrorsResponse') if options['fixesApplied'].nil?
31+
32+
result['documentUri'] = options['documentUri']
33+
result['fixesApplied'] = options['fixesApplied']
34+
result['newContent'] = options['newContent'] unless options['newContent'].nil?
35+
36+
result
37+
end
38+
end
39+
end

server/lib/puppet-languageserver/document_validator.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,33 @@ def self.find_module_root_from_path(path)
2424
module_root
2525
end
2626

27+
# Similar to 'validate' this will run puppet-lint and returns
28+
# the manifest with any fixes applied
29+
#
30+
# Returns:
31+
# [ <Int> Number of problems fixed,
32+
# <String> New Content
33+
# ]
34+
def self.fix_validate_errors(content, workspace)
35+
# Find module root and attempt to build PuppetLint options
36+
module_root = find_module_root_from_path(workspace)
37+
linter_options = nil
38+
if module_root.nil?
39+
linter_options = PuppetLint::OptParser.build
40+
else
41+
Dir.chdir(module_root.to_s) { linter_options = PuppetLint::OptParser.build }
42+
end
43+
linter_options.parse!(['--fix'])
44+
45+
linter = PuppetLint::Checks.new
46+
linter.load_data(nil, content)
47+
48+
problems = linter.run(nil, content)
49+
problems_fixed = problems.nil? ? 0 : problems.count { |item| item[:kind] == :fixed }
50+
51+
[problems_fixed, linter.manifest]
52+
end
53+
2754
def self.validate(content, workspace, _max_problems = 100)
2855
result = []
2956
# TODO: Need to implement max_problems

server/lib/puppet-languageserver/message_router.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,30 @@ def receive_request(request)
153153
request.reply_result(LanguageServer::PuppetCompilation.create('dotContent' => dot_content,
154154
'error' => error_content))
155155

156+
when 'puppet/fixDiagnosticErrors'
157+
begin
158+
formatted_request = LanguageServer::PuppetFixDiagnosticErrorsRequest.create(request.params)
159+
file_uri = formatted_request['documentUri']
160+
content = documents.document(file_uri)
161+
162+
changes, new_content = PuppetLanguageServer::DocumentValidator.fix_validate_errors(content, @workspace)
163+
164+
request.reply_result(LanguageServer::PuppetFixDiagnosticErrorsResponse.create(
165+
'documentUri' => formatted_request['documentUri'],
166+
'fixesApplied' => changes,
167+
'newContent' => changes > 0 || formatted_request['alwaysReturnContent'] ? new_content : nil
168+
))
169+
rescue StandardError => exception
170+
PuppetLanguageServer.log_message(:error, "(puppet/fixDiagnosticErrors) #{exception}")
171+
unless formatted_request.nil?
172+
request.reply_result(LanguageServer::PuppetFixDiagnosticErrorsResponse.create(
173+
'documentUri' => formatted_request['documentUri'],
174+
'fixesApplied' => 0,
175+
'newContent' => formatted_request['alwaysReturnContent'] ? content : nil # rubocop:disable Metrics/BlockNesting
176+
))
177+
end
178+
end
179+
156180
when 'textDocument/completion'
157181
file_uri = request.params['textDocument']['uri']
158182
line_num = request.params['position']['line']

server/spec/languageserver/integration/puppet-languageserver/document_validator_spec.rb

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,102 @@
33
describe 'document_validator' do
44
let(:subject) { PuppetLanguageServer::DocumentValidator }
55

6+
describe '#fix_validate_errors' do
7+
describe "Given an incomplete manifest which has syntax errors but no lint errors" do
8+
let(:manifest) { 'user { \'Bob\'' }
9+
10+
it "should return no changes" do
11+
problems_fixed, new_content = subject.fix_validate_errors(manifest, nil)
12+
expect(problems_fixed).to eq(0)
13+
expect(new_content).to eq(manifest)
14+
end
15+
end
16+
17+
describe "Given a complete manifest which has a single fixable lint errors" do
18+
let(:manifest) { "
19+
user { \"Bob\":
20+
ensure => 'present'
21+
}"
22+
}
23+
let(:new_manifest) { "
24+
user { 'Bob':
25+
ensure => 'present'
26+
}"
27+
}
28+
29+
it "should return changes" do
30+
problems_fixed, new_content = subject.fix_validate_errors(manifest, nil)
31+
expect(problems_fixed).to eq(1)
32+
expect(new_content).to eq(new_manifest)
33+
end
34+
end
35+
36+
describe "Given a complete manifest which has multiple fixable lint errors" do
37+
let(:manifest) { "
38+
// bad comment
39+
user { \"Bob\":
40+
name => 'username',
41+
ensure => 'present'
42+
}"
43+
}
44+
let(:new_manifest) { "
45+
# bad comment
46+
user { 'Bob':
47+
name => 'username',
48+
ensure => 'present'
49+
}"
50+
}
51+
52+
it "should return changes" do
53+
problems_fixed, new_content = subject.fix_validate_errors(manifest, nil)
54+
expect(problems_fixed).to eq(3)
55+
expect(new_content).to eq(new_manifest)
56+
end
57+
end
58+
59+
60+
describe "Given a complete manifest which has unfixable lint errors" do
61+
let(:manifest) { "
62+
user { 'Bob':
63+
name => 'name',
64+
ensure => 'present'
65+
}"
66+
}
67+
68+
it "should return no changes" do
69+
problems_fixed, new_content = subject.fix_validate_errors(manifest, nil)
70+
expect(problems_fixed).to eq(0)
71+
expect(new_content).to eq(manifest)
72+
end
73+
end
74+
75+
describe "Given a complete manifest with CRLF which has fixable lint errors" do
76+
let(:manifest) { "user { \"Bob\":\r\nensure => 'present'\r\n}" }
77+
let(:new_manifest) { "user { 'Bob':\r\nensure => 'present'\r\n}" }
78+
79+
it "should preserve CRLF" do
80+
pending('Release of https://github.com/rodjek/puppet-lint/commit/2a850ab3fd3694a4dd0c4d2f22a1e60b9ca0a495')
81+
problems_fixed, new_content = subject.fix_validate_errors(manifest, nil)
82+
expect(problems_fixed).to eq(1)
83+
expect(new_content).to eq(new_manifest)
84+
end
85+
end
86+
87+
describe "Given a complete manifest which has disabed fixable lint errors" do
88+
let(:manifest) { "
89+
user { \"Bob\": # lint:ignore:double_quoted_strings
90+
ensure => 'present'
91+
}"
92+
}
93+
94+
it "should return no changes" do
95+
problems_fixed, new_content = subject.fix_validate_errors(manifest, nil)
96+
expect(problems_fixed).to eq(0)
97+
expect(new_content).to eq(manifest)
98+
end
99+
end
100+
end
101+
6102
describe '#validate' do
7103
describe "Given an incomplete manifest which has syntax errors" do
8104
let(:manifest) { 'user { "Bob"' }

0 commit comments

Comments
 (0)