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 +4 -4
- data/README.md +17 -5
- data/lib/debtective/end_of_statement.rb +47 -45
- data/lib/debtective/file_todos.rb +30 -21
- data/lib/debtective/stderr_helper.rb +15 -0
- data/lib/debtective/todo.rb +37 -0
- data/lib/debtective/todo_list.rb +4 -19
- data/lib/debtective/todo_list_counts.rb +29 -0
- data/lib/debtective/version.rb +1 -1
- data/lib/tasks/debtective/todo_list.rake +37 -2
- metadata +5 -3
- data/lib/debtective/report.rb +0 -82
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa54bf140a87eeb794cba1f8823d00a9ab5407b77f7d8b18c39414857e301edc
|
4
|
+
data.tar.gz: 2169b279c88a1527c3e1107921aaacd7b81905442361804ac4eaf3731856357e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e3c5d0c77005f8b6d410339b9e649ba774d4a677ccc60cb7ee96c4761b24bc740880d648871cc65309b542935008a13df8fcd30cd89b7c968243231046fdd728
|
7
|
+
data.tar.gz: 8a9e4f628f36eb6d21eef51464d068e24fd696c8e8335127a2d95cc683bbca0c59bc91272e0450c8daac662f8f7f5014ff060bc692e7699925778c2f64725d9c
|
data/README.md
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
# Debtective
|
2
2
|
|
3
|
-
|
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
|
-
|
9
|
+
```bash
|
10
|
+
bundle exec rake debtective:todo_list
|
11
|
+
```
|
10
12
|
|
11
|
-
|
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
|
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
|
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
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
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
|
-
|
21
|
+
include StderrHelper
|
34
22
|
|
35
23
|
# @param lines [Array<String>] lines of code
|
36
|
-
|
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
|
-
#
|
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
|
-
|
35
|
+
suppress_stderr { last_line_index }
|
44
36
|
end
|
45
37
|
|
46
38
|
private
|
47
39
|
|
48
|
-
#
|
49
|
-
# @
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
55
|
-
|
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
|
-
|
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
|
-
#
|
63
|
-
#
|
64
|
-
# @
|
65
|
-
|
66
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
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
|
47
|
-
|
48
|
-
|
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
|
data/lib/debtective/todo_list.rb
CHANGED
@@ -7,17 +7,10 @@ module Debtective
|
|
7
7
|
class TodoList
|
8
8
|
DEFAULT_PATHS = ["./**/*"].freeze
|
9
9
|
|
10
|
-
|
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
|
-
|
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
|
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
|
data/lib/debtective/version.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "debtective/
|
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
|
-
|
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.
|
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-
|
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/
|
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
|
data/lib/debtective/report.rb
DELETED
@@ -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
|