debtective 0.2.3.4 → 0.2.3.7

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.
@@ -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.7"
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,17 +1,31 @@
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.7
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
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.13.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.13.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: parser
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
31
  - - ">="
@@ -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