debtective 0.2.0 → 0.2.2

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: 609f9971e421988a317405b19b0cb8aacc172ee246e29d2484d290f6fb6649ab
4
- data.tar.gz: b084f3772dfb519af3a3e254fa04f576d0edcbe59961841192cd5370c2daf7ac
3
+ metadata.gz: fa54bf140a87eeb794cba1f8823d00a9ab5407b77f7d8b18c39414857e301edc
4
+ data.tar.gz: 2169b279c88a1527c3e1107921aaacd7b81905442361804ac4eaf3731856357e
5
5
  SHA512:
6
- metadata.gz: 84a8a437407fba597289be78946a41094d34cca1eeb27fed496c010e0d062adba149f47832f2e9c558d92d9b0b23e13bd2fbd551168761eafeb02757c7816861
7
- data.tar.gz: 0fb7f4326d4e3e9ee29c54117287f4eb3f9fd74083e0706b99bf2b30e68e31ef4b4f865511a80857f3b5aea49a2000f14d1f214427cdf3078115fb08b2be470a
6
+ metadata.gz: e3c5d0c77005f8b6d410339b9e649ba774d4a677ccc60cb7ee96c4761b24bc740880d648871cc65309b542935008a13df8fcd30cd89b7c968243231046fdd728
7
+ data.tar.gz: 8a9e4f628f36eb6d21eef51464d068e24fd696c8e8335127a2d95cc683bbca0c59bc91272e0450c8daac662f8f7f5014ff060bc692e7699925778c2f64725d9c
data/README.md CHANGED
@@ -1,14 +1,16 @@
1
1
  # Debtective
2
2
 
3
- Help find out todos in your application.
3
+ Find todos in your codebase so you don't forget to pay off your debts! 💰
4
4
 
5
5
  ## Usage
6
6
 
7
- Run the task:
7
+ Run the task with:
8
8
 
9
- - `bundle exec rake debtective:todo_list`
9
+ ```bash
10
+ bundle exec rake debtective:todo_list
11
+ ```
10
12
 
11
- More tasks to come!
13
+ It outputs the todos positions with the concerned lines of code and some counts.
12
14
 
13
15
  ## Installation
14
16
 
@@ -16,7 +18,7 @@ Add this line to your application's Gemfile:
16
18
 
17
19
  ```ruby
18
20
  group :development do
19
- gem 'debtective'
21
+ gem "debtective"
20
22
  end
21
23
  ```
22
24
 
@@ -32,6 +34,16 @@ Or install it yourself as:
32
34
  $ gem install debtective
33
35
  ```
34
36
 
37
+ Configure the paths that should be analyzed (all by default):
38
+
39
+ ```ruby
40
+ # config/initializers/debtective.rb
41
+
42
+ Debtective.configure do |config|
43
+ config.paths = ["app/**/*", "lib/**/*"]
44
+ end
45
+ ```
46
+
35
47
  ## Contributing
36
48
 
37
49
  This gem is still a work in progress. You can use GitHub issue to start a discussion.
@@ -1,69 +1,71 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "debtective/stderr_helper"
4
+
3
5
  module Debtective
4
- # Find end of a block given its first line and the next lines
5
- #
6
- # With a class, a def, an if statement... or any "block statement"
7
- #
8
- # 0 def foo
9
- # 1 do_this <--
10
- # 2 end
11
- # 3 end
12
- #
13
- # The end of the block is the last line before the `end` keyword (index 1)
14
- #
15
- # With a carriage return
16
- #
17
- # 0 User
18
- # 1 .where(draft: true)
19
- # 2 .preload(:tasks) <--
20
- # 3 end
21
- #
22
- # The end of the block is the last line of the statement (index 2)
6
+ # Find the index of the line ending a statement
23
7
  #
24
- # With a single line (no block)
25
- #
26
- # 0 do_this <--
27
- # 1 do_that
28
- # 2 end
29
- #
30
- # Then the end is the first line (index 0)
8
+ # EndOfStatement.new(
9
+ # [
10
+ # "class User",
11
+ # " def example",
12
+ # " x + y",
13
+ # " end"
14
+ # "end"
15
+ # ],
16
+ # 1
17
+ # ).call
18
+ # => 3
31
19
  #
32
20
  class EndOfStatement
33
- NON_ENDING_KEYWORDS_REGEX = /^(\s*)(else$|elsif\s|(rescue(\s|$))|ensure)/
21
+ include StderrHelper
34
22
 
35
23
  # @param lines [Array<String>] lines of code
36
- def initialize(lines)
24
+ # @param index [Integer] index of the statement first line
25
+ def initialize(lines, first_line_index)
37
26
  @lines = lines
27
+ @first_line_index = first_line_index
38
28
  end
39
29
 
40
- # ends of the statement
30
+ # index of the line ending the statement
41
31
  # @return [Integer]
32
+ # @note use suppress_stderr to prevent error outputs
33
+ # from RubyVM::InstructionSequence.compile
42
34
  def call
43
- @lines[1..]&.index { end_of_statement?(_1) } || 0
35
+ suppress_stderr { last_line_index }
44
36
  end
45
37
 
46
38
  private
47
39
 
48
- # indent (number of spaces) of the first line | for memoization
49
- # @return [Integer]
50
- def first_line_indent
51
- @first_line_indent ||= indent(@lines[0])
52
- end
40
+ # recursively find index of the line ending the statement
41
+ # @param index [Integer] used for recursion
42
+ # @return[Integer, void]
43
+ # @note it is possible that no line ends the statement
44
+ # especially if first line is not the start of a statement
45
+ def last_line_index(index = @first_line_index)
46
+ return @first_line_index if index >= @lines.size
47
+ return index if !chained?(index) && statement?(index)
53
48
 
54
- # return true if the line is the end of the statement
55
- # @return [Boolean]
56
- def end_of_statement?(line)
57
- return false if line.match?(NON_ENDING_KEYWORDS_REGEX)
49
+ last_line_index(index + 1)
50
+ end
58
51
 
59
- indent(line) <= first_line_indent
52
+ # check if the code from first index to given index is a statement
53
+ # e.i. can be parsed by a ruby parser (using whitequark/parser)
54
+ # @param index [Integer]
55
+ # @return boolean
56
+ def statement?(index)
57
+ code = @lines[@first_line_index..index].join("\n")
58
+ RubyVM::InstructionSequence.compile(code)
59
+ rescue SyntaxError
60
+ false
60
61
  end
61
62
 
62
- # returns the indent (number of spaces) of the line
63
- # @param line [String]
64
- # @return [Integer]
65
- def indent(line)
66
- line.match(/^(\s*).*$/)[1].length
63
+ # check if current line is chained
64
+ # e.i. next line start with a .
65
+ # @param index [Integer]
66
+ # @return boolean
67
+ def chained?(index)
68
+ @lines[index + 1]&.match?(/^(\s*)\./)
67
69
  end
68
70
  end
69
71
  end
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "debtective/todo"
3
4
  require "debtective/end_of_statement"
4
5
 
5
6
  module Debtective
6
7
  # Find the todos comments and their boundaries
7
8
  class FileTodos
8
- Result = Struct.new(:pathname, :boundaries)
9
-
10
- BEFORE_LINE_TODO_REGEX = /^\s*#\stodo:\s/i
11
- INLINE_TODO_REGEX = /\s*#\stodo:\s/i
9
+ BEFORE_LINE_TODO_REGEX = /^\s*#\sTODO:\s/
10
+ INLINE_TODO_REGEX = /\s*#\sTODO:\s/
11
+ COMMENT_REGEX = /\s*#/
12
12
 
13
13
  # @param pathname [Pathname]
14
14
  def initialize(pathname)
@@ -18,34 +18,43 @@ module Debtective
18
18
  # @return [Array<FileTodos::Result>]
19
19
  def call
20
20
  lines.filter_map.with_index do |line, index|
21
- case line
22
- when BEFORE_LINE_TODO_REGEX
23
- Result.new(@pathname, boundaries(index))
24
- when INLINE_TODO_REGEX
25
- Result.new(@pathname, index..index)
26
- end
21
+ boundaries = boundaries(line, index)
22
+ next if boundaries.nil?
23
+
24
+ Todo.new(@pathname, index, boundaries)
27
25
  end
28
26
  end
29
27
 
30
28
  private
31
29
 
30
+ # return todo boundaries if there is a todo
31
+ # @param line [String]
32
+ # @param index [Integer]
33
+ # @return [Range, nil]
34
+ def boundaries(line, index)
35
+ case line
36
+ when BEFORE_LINE_TODO_REGEX
37
+ first_line_index = statement_first_line_index(index)
38
+ last_line_index = EndOfStatement.new(@lines, first_line_index).call
39
+ first_line_index..last_line_index
40
+ when INLINE_TODO_REGEX
41
+ index..index
42
+ end
43
+ end
44
+
32
45
  # @return [Array<String>]
33
46
  def lines
34
47
  @lines ||= @pathname.readlines
35
48
  end
36
49
 
37
- # @param index [Integer]
38
- # @return [Boundaries]
39
- def boundaries(index)
40
- offset = index + 2
41
- offset..end_line(index) + offset
42
- end
43
-
44
- # @param index [Integer]
50
+ # @param todo_index [Integer]
45
51
  # @return [Integer]
46
- def end_line(index)
47
- next_lines = lines[(index + 1)..]
48
- EndOfStatement.new(next_lines).call
52
+ def statement_first_line_index(todo_index)
53
+ @lines.index.with_index do |line, i|
54
+ i > todo_index &&
55
+ !line.strip.empty? &&
56
+ !line.match?(COMMENT_REGEX)
57
+ end
49
58
  end
50
59
  end
51
60
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Debtective
4
+ # Silence the $stderr
5
+ module StderrHelper
6
+ # @return void
7
+ def suppress_stderr
8
+ original_stderr = $stderr.clone
9
+ $stderr.reopen(File.new("/dev/null", "w"))
10
+ yield
11
+ ensure
12
+ $stderr.reopen(original_stderr)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "debtective/end_of_statement"
4
+
5
+ module Debtective
6
+ # Hold todo information
7
+ class Todo
8
+ attr_accessor :pathname, :todo_index, :boundaries
9
+
10
+ # @param pathname [Pathname]
11
+ # @param todo_index [Integer] first line of todo comment
12
+ # @param boundaries [Range] first and last index of the concerned code
13
+ def initialize(pathname, todo_index, boundaries)
14
+ @pathname = pathname
15
+ @todo_index = todo_index
16
+ @boundaries = boundaries
17
+ end
18
+
19
+ # location in the codebase
20
+ # @return [String]
21
+ def location
22
+ "#{pathname}:#{todo_index + 1}"
23
+ end
24
+
25
+ # size of the todo code
26
+ # @return [Integer]
27
+ def size
28
+ boundaries.size
29
+ end
30
+
31
+ # line numbers (like displayed in an IDE)
32
+ # @return [Range]
33
+ def line_numbers
34
+ (boundaries.min + 1)..(boundaries.max + 1)
35
+ end
36
+ end
37
+ end
@@ -7,17 +7,10 @@ module Debtective
7
7
  class TodoList
8
8
  DEFAULT_PATHS = ["./**/*"].freeze
9
9
 
10
- REPORTS = %i[
11
- combined_count
12
- extended_count
13
- todos
14
- ].freeze
15
-
16
- Result = Struct.new(*REPORTS)
17
-
18
- # @return [TodoList::Result]
10
+ # @return [Array<FileTodos::Result>]
19
11
  def call
20
- Result.new(*REPORTS.map { __send__(_1) })
12
+ ruby_pathnames
13
+ .flat_map { FileTodos.new(_1).call }
21
14
  end
22
15
 
23
16
  private
@@ -27,7 +20,7 @@ module Debtective
27
20
  Debtective.configuration&.paths || DEFAULT_PATHS
28
21
  end
29
22
 
30
- # @return [Array<Pathname>] only path to ruby files
23
+ # @return [Array<Pathname>] only pathes to ruby files
31
24
  def ruby_pathnames
32
25
  paths
33
26
  .flat_map { File.extname(_1) == "" ? Dir[_1] : [_1] }
@@ -35,14 +28,6 @@ module Debtective
35
28
  .select { _1.file? && _1.extname == ".rb" }
36
29
  end
37
30
 
38
- # @return [Array<FileTodos::Result>]
39
- def todos
40
- @todos ||=
41
- ruby_pathnames
42
- .flat_map { FileTodos.new(_1).call }
43
- .select(&:any?)
44
- end
45
-
46
31
  # @return [Integer]
47
32
  def extended_count
48
33
  todos
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Debtective
4
+ # Apply calculations on a todo list
5
+ class TodoListCounts
6
+ # @param todo_list [Array<Debtective::Todo>]
7
+ def initialize(todos)
8
+ @todos = todos
9
+ end
10
+
11
+ # @return [Integer]
12
+ def extended_count
13
+ @todos.sum(&:size)
14
+ end
15
+
16
+ # @return [Integer]
17
+ def combined_count
18
+ @todos
19
+ .group_by(&:pathname)
20
+ .values
21
+ .sum do |pathname_todos|
22
+ pathname_todos
23
+ .map { _1.boundaries.to_a }
24
+ .reduce(:|)
25
+ .length
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Debtective
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.2"
5
5
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "debtective/report"
3
+ require "debtective/todo_list"
4
+ require "debtective/todo_list_counts"
4
5
 
5
6
  begin
6
7
  require "#{Rails.root}/config/initializers/debtective" if defined?(Rails)
@@ -11,6 +12,40 @@ end
11
12
  namespace :debtective do
12
13
  desc "Todo List"
13
14
  task :todo_list do
14
- Debtective::Report.new.call
15
+ array_row =
16
+ lambda do |value1, value2, value3|
17
+ [
18
+ value1.to_s.ljust(120),
19
+ value2.to_s.rjust(12),
20
+ value3.to_s.rjust(12)
21
+ ].join(" | ").prepend("| ").concat(" |")
22
+ end
23
+
24
+ separator = Array.new(array_row.call(nil, nil, nil).size) { "-" }.join
25
+
26
+ todo_list = Debtective::TodoList.new.call
27
+ todos = todo_list.sort_by { _1.boundaries.size }.reverse
28
+ todo_list_counts = Debtective::TodoListCounts.new(todos)
29
+
30
+ puts separator
31
+ puts "todo list"
32
+ puts separator
33
+ puts array_row.call("pathname", "line numbers", "lines count")
34
+ puts separator
35
+ todos.each { puts array_row.call(_1.location, _1.line_numbers, _1.size) }
36
+
37
+ puts separator
38
+ puts "todos count"
39
+ puts todos.count
40
+
41
+ puts separator
42
+ puts "combined lines count"
43
+ puts todo_list_counts.combined_count
44
+
45
+ puts separator
46
+ puts "extended lines count"
47
+ puts todo_list_counts.extended_count
48
+
49
+ puts separator
15
50
  end
16
51
  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.0
4
+ version: 0.2.2
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-07 00:00:00.000000000 Z
11
+ date: 2023-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -109,8 +109,10 @@ files:
109
109
  - lib/debtective/end_of_statement.rb
110
110
  - lib/debtective/file_todos.rb
111
111
  - lib/debtective/railtie.rb
112
- - lib/debtective/report.rb
112
+ - lib/debtective/stderr_helper.rb
113
+ - lib/debtective/todo.rb
113
114
  - lib/debtective/todo_list.rb
115
+ - lib/debtective/todo_list_counts.rb
114
116
  - lib/debtective/version.rb
115
117
  - lib/tasks/debtective/todo_list.rake
116
118
  homepage: https://github.com/BigBigDoudou/debtective
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "debtective/todo_list"
4
-
5
- module Debtective
6
- # Output information about the debt
7
- class Report
8
- # output todo list
9
- def call
10
- puts_separator
11
- puts "todo list"
12
- puts_separator
13
- puts table_header
14
- puts_separator
15
- puts_table_content
16
- puts_separator
17
- puts_todos_count
18
- puts_separator
19
- puts_combines_lines_count
20
- puts_separator
21
- puts_extended_lines_count
22
- puts_separator
23
- end
24
-
25
- private
26
-
27
- # @return [TodoList::Result]
28
- def todo_list
29
- @todo_list ||= Debtective::TodoList.new.call
30
- end
31
-
32
- # @return [Array<FileTodos::Result>]
33
- def todos
34
- @todos ||= todo_list.todos.sort_by { _1.boundaries.size }.reverse
35
- end
36
-
37
- # @return [String]
38
- def puts_separator
39
- puts Array.new(table_header.length) { "-" }.join
40
- end
41
-
42
- # @return [void]
43
- def table_header
44
- @table_header ||= [
45
- "pathname".ljust(120),
46
- "boundaries".rjust(12),
47
- "lines".rjust(12)
48
- ].join(" | ")
49
- end
50
-
51
- # @return [void]
52
- def puts_table_content
53
- todos.each do |todo|
54
- puts(
55
- [
56
- todo.pathname.to_s.ljust(120),
57
- todo.boundaries.to_s.rjust(12),
58
- todo.boundaries.size.to_s.rjust(12)
59
- ].join(" | ")
60
- )
61
- end
62
- end
63
-
64
- # @return [void]
65
- def puts_todos_count
66
- puts "todos count"
67
- puts todos.count
68
- end
69
-
70
- # @return [void]
71
- def puts_combines_lines_count
72
- puts "combined lines count"
73
- puts todo_list.combined_count
74
- end
75
-
76
- # @return [void]
77
- def puts_extended_lines_count
78
- puts "extended lines count"
79
- puts todo_list.extended_count
80
- end
81
- end
82
- end