pronto-clang_format 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|