debtective 0.2.3.4 → 0.2.3.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Debtective
4
+ module Comments
5
+ # Find the index of the line ending a statement
6
+ #
7
+ # @example
8
+ # FindEndOfStatement.new(
9
+ # [
10
+ # "class User",
11
+ # " def example",
12
+ # " x + y",
13
+ # " end"
14
+ # "end"
15
+ # ],
16
+ # 1
17
+ # ).call
18
+ # => 3
19
+ #
20
+ class FindEndOfStatement
21
+ # @param lines [Array<String>] lines of code
22
+ # @param first_line_index [Integer] index of the statement first line
23
+ def initialize(lines:, first_line_index:)
24
+ @lines = lines
25
+ @first_line_index = first_line_index
26
+ end
27
+
28
+ # @return [Integer] index of the line ending the statement
29
+ # @note suppress_stderr prevents outputs from RubyVM::InstructionSequence.compile
30
+ def call
31
+ last_line_index
32
+ end
33
+
34
+ private
35
+
36
+ # Recursively find index of the line ending the statement
37
+ # (it is possible that no line ends the statement
38
+ # especially if the first line is not the start of a statement)
39
+ def last_line_index(index = @first_line_index)
40
+ return @first_line_index if index >= @lines.size
41
+ return index if !chained?(index) && statement?(index)
42
+
43
+ last_line_index(index + 1)
44
+ end
45
+
46
+ # Check if the code from first index to given index is a statement,
47
+ # e.i. can be parsed by a ruby parser (using whitequark/parser)
48
+ def statement?(index)
49
+ code = @lines[@first_line_index..index].join("\n")
50
+ RubyVM::InstructionSequence.compile(code)
51
+ rescue SyntaxError
52
+ false
53
+ end
54
+
55
+ # Check if current line is chained, e.i. next line start with a "."
56
+ def chained?(index)
57
+ @lines[index + 1]&.match?(/^(\s*)\./)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "export"
4
+
5
+ module Debtective
6
+ module Comments
7
+ # Print elements as a table in the stdout and return the block result
8
+ class Print
9
+ Column = Struct.new(:name, :format, :lambda)
10
+
11
+ COLUMNS = [
12
+ Column.new(
13
+ "location",
14
+ "%-100.100s",
15
+ -> { _1.location }
16
+ ),
17
+ Column.new(
18
+ "type",
19
+ "%-7.7s",
20
+ -> { _1.type }
21
+ ),
22
+ Column.new(
23
+ "author",
24
+ "%-20.20s",
25
+ -> { _1.commit.author.name || "?" }
26
+ )
27
+ ].freeze
28
+
29
+ # @param user_name [String] git user name to filter with
30
+ def initialize(user_name: nil)
31
+ @user_name = user_name
32
+ end
33
+
34
+ # @return [Object] yielded bloc result
35
+ def call
36
+ puts separator
37
+ puts headers_row
38
+ puts separator
39
+ trace.enable
40
+ result = yield
41
+ trace.disable
42
+ puts separator
43
+ result
44
+ end
45
+
46
+ private
47
+
48
+ # Use a trace to log each element as soon as it is exported
49
+ # @return [Tracepoint]
50
+ def trace
51
+ TracePoint.new(:return) do |trace_point|
52
+ next unless trace_point.defined_class == Export && trace_point.method_id == :export_comment
53
+
54
+ log_comment(trace_point.return_value)
55
+ end
56
+ end
57
+
58
+ def headers_row
59
+ COLUMNS.map { format(_1.format, _1.name) }.join(" | ")
60
+ end
61
+
62
+ def comment_row(comment)
63
+ COLUMNS.map { format(_1.format, _1.lambda.call(comment)) }.join(" | ")
64
+ end
65
+
66
+ # If a user_name is given and is not the commit author
67
+ # the line is temporary and will be replaced by the next one
68
+ def log_comment(comment)
69
+ return if comment.nil?
70
+
71
+ if @user_name.nil? || @user_name == comment.commit.author.name
72
+ puts comment_row(comment)
73
+ else
74
+ print "#{comment_row(comment)}\r"
75
+ end
76
+ end
77
+
78
+ def separator
79
+ @separator ||= "-" * COLUMNS.map { format(_1.format, "") }.join("---").size
80
+ end
81
+ end
82
+ end
83
+ end
@@ -3,6 +3,7 @@
3
3
  module Debtective
4
4
  # Silence the $stderr
5
5
  module StderrHelper
6
+ # Prevent logs in stderr
6
7
  # @return void
7
8
  def suppress_stderr
8
9
  original_stderr = $stderr.clone
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Debtective
4
- VERSION = "0.2.3.4"
4
+ VERSION = "0.2.3.6"
5
5
  end
data/lib/debtective.rb CHANGED
@@ -1,20 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "debtective/version"
4
- require "debtective/configuration"
5
- require "debtective/railtie" if defined?(Rails)
6
4
 
7
5
  # Entrypoint
8
- module Debtective
9
- class << self
10
- attr_accessor :configuration
11
-
12
- # Configures Debtective
13
- # @return Debtective::Configuration
14
- # @yieldparam [Proc] configuration to apply
15
- def configure
16
- self.configuration ||= Configuration.new
17
- yield(configuration)
18
- end
19
- end
20
- end
6
+ module Debtective; end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: debtective
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3.4
4
+ version: 0.2.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Edouard Piron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-19 00:00:00.000000000 Z
11
+ date: 2023-03-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: git
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -118,18 +132,22 @@ extra_rdoc_files: []
118
132
  files:
119
133
  - bin/debtective
120
134
  - lib/debtective.rb
121
- - lib/debtective/configuration.rb
122
- - lib/debtective/export.rb
123
- - lib/debtective/find_commit.rb
124
- - lib/debtective/find_end_of_statement.rb
125
- - lib/debtective/offenses/export.rb
126
- - lib/debtective/offenses/offense.rb
127
- - lib/debtective/print.rb
128
- - lib/debtective/railtie.rb
135
+ - lib/debtective/command.rb
136
+ - lib/debtective/comments/build_comment.rb
137
+ - lib/debtective/comments/command.rb
138
+ - lib/debtective/comments/comment/base.rb
139
+ - lib/debtective/comments/comment/fixme.rb
140
+ - lib/debtective/comments/comment/magic.rb
141
+ - lib/debtective/comments/comment/note.rb
142
+ - lib/debtective/comments/comment/offense.rb
143
+ - lib/debtective/comments/comment/shebang.rb
144
+ - lib/debtective/comments/comment/todo.rb
145
+ - lib/debtective/comments/comment/yard.rb
146
+ - lib/debtective/comments/export.rb
147
+ - lib/debtective/comments/find_commit.rb
148
+ - lib/debtective/comments/find_end_of_statement.rb
149
+ - lib/debtective/comments/print.rb
129
150
  - lib/debtective/stderr_helper.rb
130
- - lib/debtective/todos/build.rb
131
- - lib/debtective/todos/export.rb
132
- - lib/debtective/todos/todo.rb
133
151
  - lib/debtective/version.rb
134
152
  homepage: https://github.com/BigBigDoudou/debtective
135
153
  licenses:
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Debtective
4
- # Manage gem configuration
5
- class Configuration
6
- attr_accessor :paths
7
- end
8
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require "pathname"
5
- require "debtective/print"
6
-
7
- module Debtective
8
- # Export elements in a JSON file
9
- class Export
10
- # @param user_name [String] git user email to filter
11
- # @param quiet [boolean]
12
- def initialize(user_name: nil, quiet: false)
13
- @user_name = user_name
14
- @quiet = quiet
15
- end
16
-
17
- # @return [void]
18
- def call
19
- @elements = @quiet ? find_elements : log_table
20
- filter_elements!
21
- log_counts unless @quiet
22
- update_json_file
23
- puts(FILE_PATH) unless @quiet
24
- end
25
-
26
- private
27
-
28
- FILE_PATH = ""
29
-
30
- # list of paths to search in
31
- # @return [Array<String>]
32
- def pathnames
33
- (Debtective.configuration&.paths || ["./**/*"])
34
- .flat_map { Dir[_1] }
35
- .map { Pathname(_1) }
36
- .select { _1.file? && _1.extname == ".rb" }
37
- end
38
-
39
- # select only elements commited by the given user
40
- # @return [void]
41
- def filter_elements!
42
- return if @user_name.nil?
43
-
44
- @elements.select! { _1.commit.author.name == @user_name }
45
- end
46
-
47
- # @return [void]
48
- def log_counts
49
- puts "total: #{@elements.count}"
50
- end
51
-
52
- # write elements into the JSON file
53
- # @return [void]
54
- def update_json_file
55
- File.open(self.class::FILE_PATH, "w") do |file|
56
- file.puts(
57
- JSON.pretty_generate(
58
- @elements.map(&:to_h)
59
- )
60
- )
61
- end
62
- end
63
- end
64
- end
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "git"
4
- require "open3"
5
-
6
- module Debtective
7
- # Find the commit that introduced the given line of code
8
- class FindCommit
9
- Author = Struct.new(:email, :name)
10
- Commit = Struct.new(:sha, :author, :time)
11
-
12
- SPECIAL_CHARACTER_REGEX = /(?!\w|\s|#|:).+/
13
-
14
- # @param pathname [Pathname] file path
15
- # @param code_line [String] line of code_line
16
- def initialize(pathname:, code_line:)
17
- @pathname = pathname
18
- @code_line = code_line
19
- end
20
-
21
- # @return [Debtective::FindCommit::Commit]
22
- def call
23
- Commit.new(sha, author, time)
24
- rescue Git::GitExecuteError
25
- author = Author.new(nil, nil)
26
- Commit.new(nil, author, nil)
27
- end
28
-
29
- # @return [Debtective::FindCommit::Author]
30
- def author
31
- Author.new(commit.author.email, commit.author.name)
32
- end
33
-
34
- # @return [Time]
35
- def time
36
- commit.date
37
- end
38
-
39
- # @return [String]
40
- def sha
41
- @sha ||=
42
- begin
43
- cmd = "git log -S \"#{safe_code}\" #{@pathname}"
44
- stdout, _stderr, _status = ::Open3.capture3(cmd)
45
- stdout[/commit (\w{40})\n/, 1]
46
- end
47
- end
48
-
49
- private
50
-
51
- # @return [Git::Base]
52
- def git
53
- Git.open(".")
54
- end
55
-
56
- # @return [Git::Object::Commit]
57
- def commit
58
- git.gcommit(sha)
59
- end
60
-
61
- # characters " and ` can break the git command
62
- # @return [String]
63
- def safe_code
64
- @code_line.gsub(/"/, "\\\"").gsub("`", "\\\\`")
65
- end
66
- end
67
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "debtective/stderr_helper"
4
-
5
- module Debtective
6
- # Find the index of the line ending a statement
7
- #
8
- # @example
9
- # FindEndOfStatement.new(
10
- # [
11
- # "class User",
12
- # " def example",
13
- # " x + y",
14
- # " end"
15
- # "end"
16
- # ],
17
- # 1
18
- # ).call
19
- # => 3
20
- #
21
- class FindEndOfStatement
22
- include StderrHelper
23
-
24
- # @param lines [Array<String>] lines of code
25
- # @param index [Integer] index of the statement first line
26
- def initialize(lines:, first_line_index:)
27
- @lines = lines
28
- @first_line_index = first_line_index
29
- end
30
-
31
- # index of the line ending the statement
32
- # @return [Integer]
33
- # @note use suppress_stderr to prevent error outputs
34
- # from RubyVM::InstructionSequence.compile
35
- def call
36
- suppress_stderr { last_line_index }
37
- end
38
-
39
- private
40
-
41
- # recursively find index of the line ending the statement
42
- # @param index [Integer] used for recursion
43
- # @return[Integer, void]
44
- # @note it is possible that no line ends the statement
45
- # especially if first line is not the start of a statement
46
- def last_line_index(index = @first_line_index)
47
- return @first_line_index if index >= @lines.size
48
- return index if !chained?(index) && statement?(index)
49
-
50
- last_line_index(index + 1)
51
- end
52
-
53
- # check if the code from first index to given index is a statement
54
- # e.i. can be parsed by a ruby parser (using whitequark/parser)
55
- # @param index [Integer]
56
- # @return boolean
57
- def statement?(index)
58
- code = @lines[@first_line_index..index].join("\n")
59
- RubyVM::InstructionSequence.compile(code)
60
- rescue SyntaxError
61
- false
62
- end
63
-
64
- # check if current line is chained
65
- # e.i. next line start with a .
66
- # @param index [Integer]
67
- # @return boolean
68
- def chained?(index)
69
- @lines[index + 1]&.match?(/^(\s*)\./)
70
- end
71
- end
72
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "debtective/export"
4
- require "debtective/print"
5
- require "debtective/offenses/offense"
6
-
7
- module Debtective
8
- module Offenses
9
- # Export offenses in a JSON file
10
- class Export < Debtective::Export
11
- private
12
-
13
- FILE_PATH = "offenses.json"
14
-
15
- TABLE_COLUMNS = [
16
- Debtective::Print::Column.new(
17
- "location",
18
- "%-80.80s",
19
- -> { _1.location }
20
- ),
21
- Debtective::Print::Column.new(
22
- "author",
23
- "%-20.20s",
24
- -> { _1.commit.author.name || "?" }
25
- ),
26
- Debtective::Print::Column.new(
27
- "cop",
28
- "%-27.27s",
29
- -> { _1.cop || "?" }
30
- )
31
- ].freeze
32
-
33
- # @return [Array<Debtective::Offenses::Offense>]
34
- def log_table
35
- Debtective::Print.new(
36
- columns: TABLE_COLUMNS,
37
- track: Debtective::Offenses::Offense,
38
- user_name: @user_name
39
- ).call { find_elements }
40
- end
41
-
42
- # @return [Array<Debtective::Offenses::Offense>]
43
- def find_elements
44
- pathnames.flat_map do |pathname|
45
- pathname.readlines.filter_map.with_index do |line, index|
46
- next unless line =~ /\s# rubocop:disable (.*)/
47
-
48
- Debtective::Offenses::Offense.new(
49
- pathname: pathname,
50
- index: index,
51
- cop: Regexp.last_match[1]
52
- )
53
- end
54
- end
55
- end
56
- end
57
- end
58
- end
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "debtective/find_commit"
4
-
5
- module Debtective
6
- module Offenses
7
- # Hold offense information
8
- class Offense
9
- attr_accessor :pathname, :cop
10
-
11
- # @param pathname [Pathname]
12
- # @param cop [Array<String>]
13
- def initialize(pathname:, index:, cop:)
14
- @pathname = pathname
15
- @index = index
16
- @cop = cop
17
- end
18
-
19
- # location in the codebase
20
- # @return [String]
21
- def location
22
- "#{@pathname}:#{@index + 1}"
23
- end
24
-
25
- # return commit that introduced the offense
26
- # @return [Debtective::FindCommit::Commit]
27
- def commit
28
- @commit ||=
29
- Debtective::FindCommit.new(
30
- pathname: @pathname,
31
- code_line: @pathname.readlines[@index]
32
- ).call
33
- end
34
-
35
- # @return [Integer]
36
- def days
37
- return if commit.time.nil?
38
-
39
- ((Time.now - commit.time) / (24 * 60 * 60)).round
40
- end
41
-
42
- # @return [Hash]
43
- def to_h
44
- {
45
- pathname: @pathname,
46
- location: location,
47
- index: @index,
48
- commit: {
49
- sha: commit.sha,
50
- author: commit.author.to_h,
51
- time: commit.time
52
- }
53
- }
54
- end
55
- end
56
- end
57
- end
@@ -1,76 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Debtective
4
- # Print elements as a table in the stdout
5
- class Print
6
- Column = Struct.new(:name, :format, :lambda)
7
-
8
- # @param columns [Array<Debtective::Print::Column>]
9
- # @param track [Class]
10
- # @param user_name [String] git user name to filter
11
- def initialize(columns:, track:, user_name: nil)
12
- @columns = columns
13
- @track = track
14
- @user_name = user_name
15
- end
16
-
17
- # @return [Array]
18
- def call
19
- puts separator
20
- puts headers_row
21
- puts separator
22
- trace.enable
23
- result = yield
24
- trace.disable
25
- puts separator
26
- result
27
- end
28
-
29
- private
30
-
31
- # use a trace to log each element as soon as it is found
32
- # @return [Tracepoint]
33
- def trace
34
- TracePoint.new(:return) do |trace_point|
35
- next unless trace_point.defined_class == @track && trace_point.method_id == :initialize
36
-
37
- object = trace_point.self
38
- log_object(object)
39
- end
40
- end
41
-
42
- # @return [String]
43
- def headers_row
44
- @columns.map do |column|
45
- format(column.format, column.name)
46
- end.join(" | ")
47
- end
48
-
49
- # @return [String]
50
- def object_row(object)
51
- @columns.map do |column|
52
- format(column.format, column.lambda.call(object))
53
- end.join(" | ")
54
- end
55
-
56
- # if a user_name is given and is not the commit author
57
- # the line is temporary and will be replaced by the next one
58
- # @return [void]
59
- def log_object(object)
60
- if @user_name.nil? || @user_name == object.commit.author.name
61
- puts object_row(object)
62
- else
63
- print "#{object_row(object)}\r"
64
- end
65
- end
66
-
67
- # @return [String]
68
- def separator
69
- @separator ||=
70
- begin
71
- size = @columns.map { format(_1.format, "") }.join("---").size
72
- "-" * size
73
- end
74
- end
75
- end
76
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Debtective
4
- # Makes Rails aware of the tasks
5
- class Railtie < Rails::Railtie
6
- rake_tasks do
7
- load "tasks/debtective/todo_list.rake"
8
- end
9
- end
10
- end