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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da6b480040bfb881723b002a3c288b16bbff3a65ed6553f24cc7774722fa0f77
4
- data.tar.gz: 780c8d7b5754c41502acbcd2eda9ec7adabc85fc1fb8958c97934225487d7fd8
3
+ metadata.gz: 302624fd634c66b617e65dd0a978245587d65bfd95b3f4cd078c8e7e09f7002d
4
+ data.tar.gz: 66453b07b07ea23b48050bf9d3081f1489155c526bf53eee7ad7a091e0424169
5
5
  SHA512:
6
- metadata.gz: 50ac0304347960daf2d12cf5860ea23ff428f592d63f60edd796b12db076c76758d34a0bd50a03210c029337f6acef21943e83b1900d8c32b9c60c085bdc2ab9
7
- data.tar.gz: 14ba65662a824f97ee0e3150524803d04b5261ad1848dec97b33796d8ff442e1dcecce1858277dd49a0726174c822a67750fcd5f417ad967ce97e98b4a76e5d6
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,5 +1,5 @@
1
1
  module Pronto
2
2
  module ClangFormat
3
- VERSION = '0.1.0'.freeze
3
+ VERSION = '0.1.1'.freeze
4
4
  end
5
5
  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
- def run(filepath)
14
- # TODO: add environment variable for style file
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
- "-output-replacements-xml #{filepath}")
19
+ "-style=#{style} "\
20
+ "-output-replacements-xml "\
21
+ "#{file_path}")
17
22
  if stderr && !stderr.empty?
18
- puts "WARN: pronto-clang_format: #{filepath}: #{stderr}"
23
+ puts "WARN: pronto-clang_format: #{file_path}: #{stderr}"
19
24
  end
20
25
  return [] if stdout.nil? || stdout == 0
21
- parse_output(filepath, stdout)
26
+ parse_output(file_path, stdout)
22
27
  end
23
28
 
24
- def parse_output(filepath, output)
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 = ln_col_for(offset, filepath)
30
- {
31
- offset: offset,
32
- line_no: line_no, column: column, length: length,
33
- replacement: element.get_text.value
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
- private
39
-
40
- # a function that returns line no and column given byte offset in a file
41
- def ln_col_for(offset, filepath)
42
- preceding_newlines = newline_offsets_in(filepath)
43
- .select { |newline| newline < offset }
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 || 0
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
- # a memoized function that returns offsets of newline characters
51
- # in a file. used in ln_col_for
52
- def newline_offsets_in(filepath)
53
- @newline_offsets_in ||= Hash.new do |h, key|
54
- # reading as binary to support CRLF and LF
55
- text = File.read(key, mode: 'rb')
56
- h[key] = (0..(text.length - 1))
57
- .select { |i| text[i] == "\n" }
58
- end
59
- @newline_offsets_in[filepath]
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
- #TODO: file should be a cpp or c or a header file (make sure we aren't missing anything)
21
- #TODO: should we consider patches with added lines only?
22
- true
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
- puts 'inspecting patch'
27
- filename = patch.new_file_full_path
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
- .select { |line| line.new_lineno == offence[:line_no] }
32
- .map { |line| new_message(offence, line) }
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, msg_for_offence(offence), nil, self.class)
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.0
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-03-24 00:00:00.000000000 Z
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