debtective 0.2.0 → 0.2.2

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