pronto-clang_format 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/pronto/clang_format/offence.rb +42 -0
- data/lib/pronto/clang_format/offence_categorizer/abstract_categorizer.rb +28 -0
- data/lib/pronto/clang_format/offence_categorizer/factory.rb +14 -0
- data/lib/pronto/clang_format/offence_categorizer/includes_order_categorizer.rb +16 -0
- data/lib/pronto/clang_format/offence_categorizer/indentation_categorizer.rb +13 -0
- data/lib/pronto/clang_format/version.rb +1 -1
- data/lib/pronto/clang_format/wrapper.rb +67 -30
- data/lib/pronto/clang_format_runner.rb +24 -21
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 302624fd634c66b617e65dd0a978245587d65bfd95b3f4cd078c8e7e09f7002d
|
4
|
+
data.tar.gz: 66453b07b07ea23b48050bf9d3081f1489155c526bf53eee7ad7a091e0424169
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5599a978450406787d148dc45d4948e9e20d392209cc912906a6b60bcb2a4b5a3b6a1f33899d140a6168b136edf2f61e216183d9ada36826498495e0d60a368
|
7
|
+
data.tar.gz: 4e1b9c2e129c7be2ab0026ebba925e97b10df3585bc659c2060e7e8adad3a535deca9a9931bec8709132e389eb619e5027bb232c94fd003243b4a48334d3227a
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'offence_categorizer/factory'
|
2
|
+
|
3
|
+
module Pronto
|
4
|
+
module ClangFormat
|
5
|
+
class Offence
|
6
|
+
attr_reader :offset, :line_no, :column, :length, :replacement,
|
7
|
+
:affected_lines_before
|
8
|
+
def initialize(offset, line_no, column, length, replacement,
|
9
|
+
affected_lines)
|
10
|
+
@offset = offset.freeze
|
11
|
+
@line_no = line_no.freeze
|
12
|
+
@column = column.freeze
|
13
|
+
@length = length.freeze
|
14
|
+
@replacement = replacement.freeze
|
15
|
+
@affected_lines_before = affected_lines.freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
# generates a user-friendly message that describes this offence. This is
|
19
|
+
# done by using OffenceCategorizer's chain of responsibility classes
|
20
|
+
def msg
|
21
|
+
OffenceCategorizer::Factory.create_categorizers.handle self
|
22
|
+
end
|
23
|
+
|
24
|
+
# the exact portion of text that is to be replaced by this offence
|
25
|
+
def replaced_text
|
26
|
+
affected_lines_before[column..(column + length - 1)]
|
27
|
+
end
|
28
|
+
|
29
|
+
# returns the range of line numbers affected by this offence
|
30
|
+
def affected_lines_range
|
31
|
+
(line_no..line_no + replaced_text.count("\n"))
|
32
|
+
end
|
33
|
+
|
34
|
+
# returns affected lines after fixing this offence
|
35
|
+
def affected_lines_after
|
36
|
+
affected_lines = String.new(affected_lines_before)
|
37
|
+
affected_lines[column..(column + length - 1)] = replacement
|
38
|
+
affected_lines
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Pronto
|
2
|
+
module ClangFormat
|
3
|
+
module OffenceCategorizer
|
4
|
+
# base class to implement a chain of responsibility
|
5
|
+
class AbstractCategorizer
|
6
|
+
def initialize(successor = nil)
|
7
|
+
@successor = successor
|
8
|
+
end
|
9
|
+
|
10
|
+
def handle(offence)
|
11
|
+
current_result = handle_current offence
|
12
|
+
if !current_result.nil?
|
13
|
+
current_result
|
14
|
+
elsif !@successor.nil?
|
15
|
+
@successor.handle offence
|
16
|
+
else # unahndled offence
|
17
|
+
"This should be rewritten as: \n#{offence.affected_lines_after}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def handle_current(_offence)
|
22
|
+
raise NotImplementedError, 'this method needs to be implemented in '\
|
23
|
+
'subclasses'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative 'indentation_categorizer'
|
2
|
+
require_relative 'includes_order_categorizer'
|
3
|
+
|
4
|
+
module Pronto
|
5
|
+
module ClangFormat
|
6
|
+
module OffenceCategorizer
|
7
|
+
class Factory
|
8
|
+
def self.create_categorizers
|
9
|
+
IncludesOrderCategorizer.new IndentationCategorizer.new
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative 'abstract_categorizer'
|
2
|
+
|
3
|
+
module Pronto
|
4
|
+
module ClangFormat
|
5
|
+
module OffenceCategorizer
|
6
|
+
class IncludesOrderCategorizer < AbstractCategorizer
|
7
|
+
def handle_current(offence)
|
8
|
+
return unless offence.replaced_text.include? 'include'
|
9
|
+
|
10
|
+
"Include statements are not ordered alphabetically. "\
|
11
|
+
"They should be rewritten as:\n#{offence.affected_lines_after}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative 'abstract_categorizer'
|
2
|
+
|
3
|
+
module Pronto
|
4
|
+
module ClangFormat
|
5
|
+
module OffenceCategorizer
|
6
|
+
class IndentationCategorizer < AbstractCategorizer
|
7
|
+
def handle_current(offence)
|
8
|
+
"Incorrect indentation" if offence.column <= 1
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'open3'
|
2
2
|
require 'rexml/document'
|
3
3
|
require 'shellwords'
|
4
|
+
require_relative 'offence'
|
4
5
|
|
5
|
-
# TODO: make sure clang-format returns its output sorted by offset. If this is always true, switch to using .each_with_index.to_a.reverse.bsearch { |n, i| n < clang_offset }
|
6
6
|
module Pronto
|
7
7
|
module ClangFormat
|
8
8
|
class Wrapper
|
@@ -10,53 +10,90 @@ module Pronto
|
|
10
10
|
@clang_format_path = ENV['PRONTO_CLANG_FORMAT_PATH'] || 'clang-format'
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
# runs clang-format for the provided file and returns an array of
|
14
|
+
# offence objects
|
15
|
+
# Params:
|
16
|
+
# - file_path: path to the file to be examined by clang-format
|
17
|
+
def run(file_path)
|
15
18
|
stdout, stderr, = Open3.capture3("#{@clang_format_path} "\
|
16
|
-
"-
|
19
|
+
"-style=#{style} "\
|
20
|
+
"-output-replacements-xml "\
|
21
|
+
"#{file_path}")
|
17
22
|
if stderr && !stderr.empty?
|
18
|
-
puts "WARN: pronto-clang_format: #{
|
23
|
+
puts "WARN: pronto-clang_format: #{file_path}: #{stderr}"
|
19
24
|
end
|
20
25
|
return [] if stdout.nil? || stdout == 0
|
21
|
-
parse_output(
|
26
|
+
parse_output(file_path, stdout)
|
22
27
|
end
|
23
28
|
|
24
|
-
|
29
|
+
private
|
30
|
+
|
31
|
+
def style
|
32
|
+
ENV['PRONTO_CLANG_FORMAT_STYLE'] || 'file'
|
33
|
+
end
|
34
|
+
|
35
|
+
# parses clang-format output for a given file and returns an array of
|
36
|
+
# offence objects
|
37
|
+
# Params:
|
38
|
+
# - file_path: file path for which clang-format generated the output
|
39
|
+
# - output: clang-format standard output for the given file path
|
40
|
+
def parse_output(file_path, output)
|
41
|
+
file_contents = File.read(file_path, mode: 'r')
|
42
|
+
newlines_array = newline_offsets_in(file_contents)
|
25
43
|
doc = REXML::Document.new output
|
26
44
|
doc.root.elements.map do |element|
|
27
45
|
offset = element.attributes['offset'].to_i
|
28
46
|
length = element.attributes['length'].to_i
|
29
|
-
line_no, column =
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
47
|
+
line_no, column = ln_col_from_offset(newlines_array, offset)
|
48
|
+
replacement = element.get_text ? element.get_text.value : ''
|
49
|
+
# remove non-changed new lines from the beginning of the replacement
|
50
|
+
while replacement.start_with?("\n") && column.zero? && length > 0
|
51
|
+
offset += 1
|
52
|
+
length -= 1
|
53
|
+
line_no, column = ln_col_from_offset(newlines_array, offset)
|
54
|
+
replacement = replacement[1..-1]
|
55
|
+
end
|
56
|
+
# calculate number of the last line affected by the replacement
|
57
|
+
end_line_no, = ln_col_from_offset(newlines_array, offset + length)
|
58
|
+
# extract relevant lines and pass to offence
|
59
|
+
start_offset = offset_from_ln_col(newlines_array, line_no, 1)
|
60
|
+
end_offset = offset_from_ln_col(newlines_array, end_line_no + 1, 0)
|
61
|
+
old_lines_text = file_contents[start_offset..end_offset]
|
62
|
+
.prepend("\n")
|
63
|
+
Offence.new(offset, line_no, column, length, replacement,
|
64
|
+
old_lines_text)
|
35
65
|
end
|
36
66
|
end
|
37
67
|
|
38
|
-
|
39
|
-
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
|
68
|
+
# returns line no and column given byte offset in a text file
|
69
|
+
# Params:
|
70
|
+
# - newlines_array: an array that contains offsets of newline
|
71
|
+
# characters in the text file (can be obtained by calling
|
72
|
+
# newline_offsets_in(file_contents)
|
73
|
+
# - offset: offset to be converted
|
74
|
+
def ln_col_from_offset(newlines_array, offset)
|
75
|
+
preceding_newlines = newlines_array
|
76
|
+
.select { |newline| newline <= offset }
|
44
77
|
ln = preceding_newlines.length + 1
|
45
|
-
line_start_offset = preceding_newlines.last ||
|
78
|
+
line_start_offset = preceding_newlines.last || -1
|
46
79
|
col = offset - line_start_offset
|
47
80
|
[ln, col]
|
48
81
|
end
|
49
82
|
|
50
|
-
#
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
83
|
+
# returns byte offset given line no and a column in a text file
|
84
|
+
# Params:
|
85
|
+
# - newlines_array: an array that contains offsets of newline
|
86
|
+
# characters in the text file (can be obtained by calling
|
87
|
+
# newline_offsets_in(file_contents)
|
88
|
+
# - offset: offset to be converted
|
89
|
+
def offset_from_ln_col(newlines_array, ln, col)
|
90
|
+
(ln >= 2 ? newlines_array[ln - 2] : -1) + col
|
91
|
+
end
|
92
|
+
|
93
|
+
# returns an array that contains offsets of newline characters in given
|
94
|
+
# text
|
95
|
+
def newline_offsets_in(text)
|
96
|
+
(0..(text.length - 1)).select { |i| text[i] == "\n" }
|
60
97
|
end
|
61
98
|
end
|
62
99
|
end
|
@@ -6,43 +6,46 @@ module Pronto
|
|
6
6
|
def initialize(_, __ = nil)
|
7
7
|
super
|
8
8
|
@inspector = ::Pronto::ClangFormat::Wrapper.new
|
9
|
+
comma_separated_exts = ENV['PRONTO_CLANG_FORMAT_FILE_EXTS']
|
10
|
+
if comma_separated_exts.nil? # load default cpp files extensions
|
11
|
+
@cpp_extensions = %w[c h cpp cc cxx c++ hh hxx hpp h++ icc inl tcc tpp
|
12
|
+
ipp]
|
13
|
+
else # load desired extensions from environment variable
|
14
|
+
@cpp_extensions = comma_separated_exts.split(',').map(&:strip)
|
15
|
+
end
|
9
16
|
end
|
10
17
|
|
11
18
|
def run
|
12
19
|
return [] if !@patches || @patches.count.zero?
|
13
20
|
@patches
|
14
|
-
.select { |p| valid_patch(p) }
|
21
|
+
.select { |p| valid_patch?(p) }
|
15
22
|
.map { |p| inspect(p) }
|
16
23
|
.flatten.compact
|
17
24
|
end
|
18
|
-
|
19
|
-
def valid_patch(patch)
|
20
|
-
|
21
|
-
|
22
|
-
|
25
|
+
|
26
|
+
def valid_patch?(patch)
|
27
|
+
return false if patch.additions < 1
|
28
|
+
cpp_file?(patch.new_file_full_path)
|
29
|
+
end
|
30
|
+
|
31
|
+
def cpp_file?(file_path)
|
32
|
+
@cpp_extensions.include? file_path.extname[1..-1]
|
23
33
|
end
|
24
34
|
|
25
35
|
def inspect(patch)
|
26
|
-
|
27
|
-
|
28
|
-
offences = @inspector.run(filename)
|
36
|
+
file_path = patch.new_file_full_path
|
37
|
+
offences = @inspector.run(file_path)
|
29
38
|
offences.map do |offence|
|
30
|
-
patch.added_lines
|
31
|
-
.
|
32
|
-
|
39
|
+
line = patch.added_lines.find do |added_line|
|
40
|
+
offence.affected_lines_range.cover? added_line.new_lineno
|
41
|
+
end
|
42
|
+
new_message(offence, line) unless line.nil?
|
33
43
|
end
|
34
44
|
end
|
35
|
-
|
45
|
+
|
36
46
|
def new_message(offence, line)
|
37
47
|
path = line.patch.delta.new_file[:path]
|
38
|
-
Message.new(path, line, :warning,
|
39
|
-
end
|
40
|
-
|
41
|
-
#TODO: correct line numbers for indentation errors (clang outputs the replacement to start from the preceding line)
|
42
|
-
#TODO: better messages, maybe have categories? (e.g. indentation, missing space, superfluous space, missine newline, superfluous newline, ...)
|
43
|
-
|
44
|
-
def msg_for_offence(offence)
|
45
|
-
"col: #{offence[:column]}, len: #{offence[:length]}, offset: #{offence[:offset]}, replace with: #{offence[:replacement].dump}"
|
48
|
+
Message.new(path, line, :warning, offence.msg, nil, self.class)
|
46
49
|
end
|
47
50
|
end
|
48
51
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pronto-clang_format
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Jabbour
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-05-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pronto
|
@@ -67,6 +67,11 @@ files:
|
|
67
67
|
- README.md
|
68
68
|
- Rakefile
|
69
69
|
- lib/pronto/clang_format.rb
|
70
|
+
- lib/pronto/clang_format/offence.rb
|
71
|
+
- lib/pronto/clang_format/offence_categorizer/abstract_categorizer.rb
|
72
|
+
- lib/pronto/clang_format/offence_categorizer/factory.rb
|
73
|
+
- lib/pronto/clang_format/offence_categorizer/includes_order_categorizer.rb
|
74
|
+
- lib/pronto/clang_format/offence_categorizer/indentation_categorizer.rb
|
70
75
|
- lib/pronto/clang_format/version.rb
|
71
76
|
- lib/pronto/clang_format/wrapper.rb
|
72
77
|
- lib/pronto/clang_format_runner.rb
|