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 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