debtective 0.2.0 → 0.2.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: 609f9971e421988a317405b19b0cb8aacc172ee246e29d2484d290f6fb6649ab
4
- data.tar.gz: b084f3772dfb519af3a3e254fa04f576d0edcbe59961841192cd5370c2daf7ac
3
+ metadata.gz: 3a9c75632f4f3a8785af1c96209c22b68772f8bde253cbd76e2bdb774a3812a8
4
+ data.tar.gz: d4b50399a0806ad8e4a6fb20305429882847ef79dd42bd176d17be3f5f1df786
5
5
  SHA512:
6
- metadata.gz: 84a8a437407fba597289be78946a41094d34cca1eeb27fed496c010e0d062adba149f47832f2e9c558d92d9b0b23e13bd2fbd551168761eafeb02757c7816861
7
- data.tar.gz: 0fb7f4326d4e3e9ee29c54117287f4eb3f9fd74083e0706b99bf2b30e68e31ef4b4f865511a80857f3b5aea49a2000f14d1f214427cdf3078115fb08b2be470a
6
+ metadata.gz: 59b68258f51e5f7a83992c8d24d0bc028fcb7dcdea2f379d0af011a8ec9c5dba645d6ffef4f94652d3ce7f0662185597934b96a6c8d2fa74ccc392b027b207fe
7
+ data.tar.gz: a5556a654c194fc442727c67f6c06686c6df851fe4008540670603d5aac8203e67e96a62d498b0eb57b91bdae76a2c967d789b29709659b84d3178632e7b893f
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,81 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "parser/current"
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)/
34
-
35
21
  # @param lines [Array<String>] lines of code
36
- def initialize(lines)
22
+ # @param index [Integer] index of the statement first line
23
+ def initialize(lines, first_line_index)
37
24
  @lines = lines
25
+ @first_line_index = first_line_index
38
26
  end
39
27
 
40
- # ends of the statement
28
+ # index of the line ending the statement
41
29
  # @return [Integer]
42
30
  def call
43
- @lines[1..]&.index { end_of_statement?(_1) } || 0
31
+ suppress_stderr do
32
+ last_line_index || @first_line_index
33
+ end
44
34
  end
45
35
 
46
36
  private
47
37
 
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])
38
+ # index of the line ending the statement
39
+ # @return[Integer, void]
40
+ # @note it is possible that no line ends the statement
41
+ # especially if first line is not the start of a statement
42
+ def last_line_index
43
+ @lines.index.with_index do |_line, index|
44
+ index >= @first_line_index &&
45
+ statement?(index) &&
46
+ !chained?(index)
47
+ end
52
48
  end
53
49
 
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)
50
+ # check if the code from first index to given index is a statement
51
+ # e.i. can be parsed by a ruby parser (using whitequark/parser)
52
+ # @param index [Integer]
53
+ # @return boolean
54
+ def statement?(index)
55
+ code = @lines[@first_line_index..index].join("\n")
56
+ ::Parser::CurrentRuby.parse(code)
57
+ true
58
+ rescue Parser::SyntaxError
59
+ false
60
+ end
58
61
 
59
- indent(line) <= first_line_indent
62
+ # check if current line is chained
63
+ # e.i. next line start with a .
64
+ # @param index [Integer]
65
+ # @return boolean
66
+ def chained?(index)
67
+ @lines[index + 1]&.match?(/^(\s*)\./)
60
68
  end
61
69
 
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
70
+ # silence the $stderr
71
+ # to avoid logs from the parser
72
+ # @return void
73
+ def suppress_stderr
74
+ original_stderr = $stderr.clone
75
+ $stderr.reopen(File.new("/dev/null", "w"))
76
+ yield
77
+ ensure
78
+ $stderr.reopen(original_stderr)
67
79
  end
68
80
  end
69
81
  end
@@ -5,10 +5,11 @@ require "debtective/end_of_statement"
5
5
  module Debtective
6
6
  # Find the todos comments and their boundaries
7
7
  class FileTodos
8
- Result = Struct.new(:pathname, :boundaries)
8
+ Result = Struct.new(:pathname, :todo_index, :boundaries)
9
9
 
10
- BEFORE_LINE_TODO_REGEX = /^\s*#\stodo:\s/i
11
- INLINE_TODO_REGEX = /\s*#\stodo:\s/i
10
+ BEFORE_LINE_TODO_REGEX = /^\s*#\sTODO:\s/
11
+ INLINE_TODO_REGEX = /\s*#\sTODO:\s/
12
+ COMMENT_REGEX = /\s*#/
12
13
 
13
14
  # @param pathname [Pathname]
14
15
  def initialize(pathname)
@@ -18,34 +19,43 @@ module Debtective
18
19
  # @return [Array<FileTodos::Result>]
19
20
  def call
20
21
  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
22
+ boundaries = boundaries(line, index)
23
+ next if boundaries.nil?
24
+
25
+ Result.new(@pathname, index, boundaries)
27
26
  end
28
27
  end
29
28
 
30
29
  private
31
30
 
31
+ # return todo boundaries if there is a todo
32
+ # @param line [String]
33
+ # @param index [Integer]
34
+ # @return [Range, nil]
35
+ def boundaries(line, index)
36
+ case line
37
+ when BEFORE_LINE_TODO_REGEX
38
+ first_line_index = statement_first_line_index(index)
39
+ last_line_index = EndOfStatement.new(@lines, first_line_index).call
40
+ first_line_index..last_line_index
41
+ when INLINE_TODO_REGEX
42
+ index..index
43
+ end
44
+ end
45
+
32
46
  # @return [Array<String>]
33
47
  def lines
34
48
  @lines ||= @pathname.readlines
35
49
  end
36
50
 
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]
51
+ # @param todo_index [Integer]
45
52
  # @return [Integer]
46
- def end_line(index)
47
- next_lines = lines[(index + 1)..]
48
- EndOfStatement.new(next_lines).call
53
+ def statement_first_line_index(todo_index)
54
+ @lines.index.with_index do |line, i|
55
+ i > todo_index &&
56
+ !line.strip.empty? &&
57
+ !line.match?(COMMENT_REGEX)
58
+ end
49
59
  end
50
60
  end
51
61
  end
@@ -27,7 +27,7 @@ module Debtective
27
27
  Debtective.configuration&.paths || DEFAULT_PATHS
28
28
  end
29
29
 
30
- # @return [Array<Pathname>] only path to ruby files
30
+ # @return [Array<Pathname>] only pathes to ruby files
31
31
  def ruby_pathnames
32
32
  paths
33
33
  .flat_map { File.extname(_1) == "" ? Dir[_1] : [_1] }
@@ -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.1"
5
5
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "debtective/report"
3
+ require "debtective/todo_list"
4
4
 
5
5
  begin
6
6
  require "#{Rails.root}/config/initializers/debtective" if defined?(Rails)
@@ -11,6 +11,52 @@ end
11
11
  namespace :debtective do
12
12
  desc "Todo List"
13
13
  task :todo_list do
14
- Debtective::Report.new.call
14
+ todo_list = Debtective::TodoList.new.call
15
+ todos = todo_list.todos.sort_by { _1.boundaries.size }.reverse
16
+
17
+ separator = Array.new(120 + 3 + 12 + 3 + 12) { "-" }.join
18
+
19
+ puts separator
20
+
21
+ puts "todo list"
22
+
23
+ puts separator
24
+
25
+ puts(
26
+ [
27
+ "pathname".ljust(120),
28
+ "lines".rjust(12)
29
+ ].join(" | ")
30
+ )
31
+
32
+ puts separator
33
+
34
+ todos.each do |todo|
35
+ location = "#{todo.pathname}:#{todo.todo_index + 1}".ljust(120)
36
+ puts(
37
+ [
38
+ location,
39
+ [todo.boundaries.first + 1, todo.boundaries.last + 1].join(":").rjust(12),
40
+ todo.boundaries.size.to_s.rjust(12)
41
+ ].join(" | ")
42
+ )
43
+ end
44
+
45
+ puts separator
46
+
47
+ puts "todos count"
48
+ puts todos.count
49
+
50
+ puts separator
51
+
52
+ puts "combined lines count"
53
+ puts todo_list.combined_count
54
+
55
+ puts separator
56
+
57
+ puts "extended lines count"
58
+ puts todo_list.extended_count
59
+
60
+ puts separator
15
61
  end
16
62
  end
metadata CHANGED
@@ -1,15 +1,29 @@
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.1
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-09 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rake
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -109,7 +123,6 @@ files:
109
123
  - lib/debtective/end_of_statement.rb
110
124
  - lib/debtective/file_todos.rb
111
125
  - lib/debtective/railtie.rb
112
- - lib/debtective/report.rb
113
126
  - lib/debtective/todo_list.rb
114
127
  - lib/debtective/version.rb
115
128
  - lib/tasks/debtective/todo_list.rake
@@ -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