spoom 1.5.0 → 1.7.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 +14 -0
- data/lib/spoom/backtrace_filter/minitest.rb +3 -4
- data/lib/spoom/cli/deadcode.rb +1 -2
- data/lib/spoom/cli/helper.rb +41 -31
- data/lib/spoom/cli/srb/assertions.rb +48 -0
- data/lib/spoom/cli/srb/bump.rb +1 -2
- data/lib/spoom/cli/srb/coverage.rb +1 -1
- data/lib/spoom/cli/srb/metrics.rb +68 -0
- data/lib/spoom/cli/srb/sigs.rb +209 -0
- data/lib/spoom/cli/srb/tc.rb +16 -1
- data/lib/spoom/cli/srb.rb +16 -4
- data/lib/spoom/cli.rb +1 -2
- data/lib/spoom/colors.rb +2 -6
- data/lib/spoom/context/bundle.rb +8 -9
- data/lib/spoom/context/exec.rb +3 -6
- data/lib/spoom/context/file_system.rb +12 -19
- data/lib/spoom/context/git.rb +14 -19
- data/lib/spoom/context/sorbet.rb +14 -27
- data/lib/spoom/context.rb +4 -8
- data/lib/spoom/counters.rb +22 -0
- data/lib/spoom/coverage/d3/base.rb +6 -8
- data/lib/spoom/coverage/d3/circle_map.rb +6 -16
- data/lib/spoom/coverage/d3/pie.rb +14 -19
- data/lib/spoom/coverage/d3/timeline.rb +46 -47
- data/lib/spoom/coverage/d3.rb +2 -4
- data/lib/spoom/coverage/report.rb +41 -79
- data/lib/spoom/coverage/snapshot.rb +8 -14
- data/lib/spoom/coverage.rb +3 -5
- data/lib/spoom/deadcode/definition.rb +12 -14
- data/lib/spoom/deadcode/erb.rb +10 -8
- data/lib/spoom/deadcode/index.rb +21 -25
- data/lib/spoom/deadcode/indexer.rb +5 -6
- data/lib/spoom/deadcode/plugins/action_mailer.rb +2 -3
- data/lib/spoom/deadcode/plugins/action_mailer_preview.rb +2 -3
- data/lib/spoom/deadcode/plugins/actionpack.rb +19 -22
- data/lib/spoom/deadcode/plugins/active_model.rb +2 -3
- data/lib/spoom/deadcode/plugins/active_record.rb +62 -53
- data/lib/spoom/deadcode/plugins/active_support.rb +3 -2
- data/lib/spoom/deadcode/plugins/base.rb +29 -32
- data/lib/spoom/deadcode/plugins/graphql.rb +2 -3
- data/lib/spoom/deadcode/plugins/minitest.rb +4 -4
- data/lib/spoom/deadcode/plugins/namespaces.rb +5 -5
- data/lib/spoom/deadcode/plugins/rails.rb +5 -5
- data/lib/spoom/deadcode/plugins/rubocop.rb +5 -5
- data/lib/spoom/deadcode/plugins/ruby.rb +3 -4
- data/lib/spoom/deadcode/plugins/sorbet.rb +12 -6
- data/lib/spoom/deadcode/plugins/thor.rb +2 -3
- data/lib/spoom/deadcode/plugins.rb +23 -31
- data/lib/spoom/deadcode/remover.rb +58 -79
- data/lib/spoom/deadcode/send.rb +2 -8
- data/lib/spoom/file_collector.rb +11 -19
- data/lib/spoom/file_tree.rb +36 -51
- data/lib/spoom/location.rb +9 -20
- data/lib/spoom/model/builder.rb +54 -17
- data/lib/spoom/model/model.rb +71 -74
- data/lib/spoom/model/namespace_visitor.rb +4 -3
- data/lib/spoom/model/reference.rb +4 -8
- data/lib/spoom/model/references_visitor.rb +50 -30
- data/lib/spoom/parse.rb +4 -4
- data/lib/spoom/poset.rb +22 -24
- data/lib/spoom/printer.rb +10 -13
- data/lib/spoom/rbs.rb +77 -0
- data/lib/spoom/sorbet/config.rb +17 -24
- data/lib/spoom/sorbet/errors.rb +87 -45
- data/lib/spoom/sorbet/lsp/base.rb +10 -16
- data/lib/spoom/sorbet/lsp/errors.rb +8 -16
- data/lib/spoom/sorbet/lsp/structures.rb +65 -91
- data/lib/spoom/sorbet/lsp.rb +20 -22
- data/lib/spoom/sorbet/metrics/code_metrics_visitor.rb +236 -0
- data/lib/spoom/sorbet/metrics/metrics_file_parser.rb +34 -0
- data/lib/spoom/sorbet/metrics.rb +2 -32
- data/lib/spoom/sorbet/sigils.rb +16 -23
- data/lib/spoom/sorbet/translate/rbs_comments_to_sorbet_sigs.rb +242 -0
- data/lib/spoom/sorbet/translate/sorbet_assertions_to_rbs_comments.rb +123 -0
- data/lib/spoom/sorbet/translate/sorbet_sigs_to_rbs_comments.rb +293 -0
- data/lib/spoom/sorbet/translate/strip_sorbet_sigs.rb +23 -0
- data/lib/spoom/sorbet/translate/translator.rb +71 -0
- data/lib/spoom/sorbet/translate.rb +49 -0
- data/lib/spoom/sorbet.rb +6 -12
- data/lib/spoom/source/rewriter.rb +167 -0
- data/lib/spoom/source.rb +4 -0
- data/lib/spoom/timeline.rb +4 -6
- data/lib/spoom/version.rb +1 -1
- data/lib/spoom/visitor.rb +298 -151
- data/lib/spoom.rb +4 -3
- data/rbi/spoom.rbi +3567 -0
- metadata +62 -8
data/lib/spoom/poset.rb
CHANGED
@@ -7,22 +7,21 @@ module Spoom
|
|
7
7
|
# The partial order relation is a binary relation that is reflexive, antisymmetric, and transitive.
|
8
8
|
# It can be used to represent a hierarchy of classes or modules, the dependencies between gems, etc.
|
9
9
|
class Poset
|
10
|
-
extend T::Sig
|
11
10
|
extend T::Generic
|
12
11
|
|
13
12
|
class Error < Spoom::Error; end
|
14
13
|
|
15
14
|
E = type_member { { upper: Object } }
|
16
15
|
|
17
|
-
|
16
|
+
#: -> void
|
18
17
|
def initialize
|
19
|
-
@elements =
|
18
|
+
@elements = {} #: Hash[E, Element[E]]
|
20
19
|
end
|
21
20
|
|
22
21
|
# Get the POSet element for a given value
|
23
22
|
#
|
24
23
|
# Raises if the element is not found
|
25
|
-
|
24
|
+
#: (E value) -> Element[E]
|
26
25
|
def [](value)
|
27
26
|
element = @elements[value]
|
28
27
|
raise Error, "POSet::Element not found for #{value}" unless element
|
@@ -31,7 +30,7 @@ module Spoom
|
|
31
30
|
end
|
32
31
|
|
33
32
|
# Add an element to the POSet
|
34
|
-
|
33
|
+
#: (E value) -> Element[E]
|
35
34
|
def add_element(value)
|
36
35
|
element = @elements[value]
|
37
36
|
return element if element
|
@@ -40,7 +39,7 @@ module Spoom
|
|
40
39
|
end
|
41
40
|
|
42
41
|
# Is the given value a element in the POSet?
|
43
|
-
|
42
|
+
#: (E value) -> bool
|
44
43
|
def element?(value)
|
45
44
|
@elements.key?(value)
|
46
45
|
end
|
@@ -50,7 +49,7 @@ module Spoom
|
|
50
49
|
# Transitive edges (transitive closure) are automatically computed.
|
51
50
|
# Adds the elements if they don't exist.
|
52
51
|
# If the direct edge already exists, nothing is done.
|
53
|
-
|
52
|
+
#: (E from, E to) -> void
|
54
53
|
def add_direct_edge(from, to)
|
55
54
|
from_element = add_element(from)
|
56
55
|
to_element = add_element(to)
|
@@ -88,7 +87,7 @@ module Spoom
|
|
88
87
|
end
|
89
88
|
|
90
89
|
# Is there an edge (direct or indirect) from `from` to `to`?
|
91
|
-
|
90
|
+
#: (E from, E to) -> bool
|
92
91
|
def edge?(from, to)
|
93
92
|
from_element = @elements[from]
|
94
93
|
return false unless from_element
|
@@ -97,13 +96,13 @@ module Spoom
|
|
97
96
|
end
|
98
97
|
|
99
98
|
# Is there a direct edge from `from` to `to`?
|
100
|
-
|
99
|
+
#: (E from, E to) -> bool
|
101
100
|
def direct_edge?(from, to)
|
102
101
|
self[from].parents.include?(to)
|
103
102
|
end
|
104
103
|
|
105
104
|
# Show the POSet as a DOT graph using xdot (used for debugging)
|
106
|
-
|
105
|
+
#: (?direct: bool, ?transitive: bool) -> void
|
107
106
|
def show_dot(direct: true, transitive: true)
|
108
107
|
Open3.popen3("xdot -") do |stdin, _stdout, _stderr, _thread|
|
109
108
|
stdin.write(to_dot(direct: direct, transitive: transitive))
|
@@ -112,7 +111,7 @@ module Spoom
|
|
112
111
|
end
|
113
112
|
|
114
113
|
# Return the POSet as a DOT graph
|
115
|
-
|
114
|
+
#: (?direct: bool, ?transitive: bool) -> String
|
116
115
|
def to_dot(direct: true, transitive: true)
|
117
116
|
dot = +"digraph {\n"
|
118
117
|
dot << " rankdir=BT;\n"
|
@@ -134,30 +133,29 @@ module Spoom
|
|
134
133
|
|
135
134
|
# An element in a POSet
|
136
135
|
class Element
|
137
|
-
extend T::Sig
|
138
136
|
extend T::Generic
|
139
137
|
include Comparable
|
140
138
|
|
141
139
|
E = type_member { { upper: Object } }
|
142
140
|
|
143
141
|
# The value held by this element
|
144
|
-
|
142
|
+
#: E
|
145
143
|
attr_reader :value
|
146
144
|
|
147
145
|
# Edges (direct and indirect) from this element to other elements in the same POSet
|
148
|
-
|
146
|
+
#: Set[Element[E]]
|
149
147
|
attr_reader :dtos, :tos, :dfroms, :froms
|
150
148
|
|
151
|
-
|
149
|
+
#: (E value) -> void
|
152
150
|
def initialize(value)
|
153
151
|
@value = value
|
154
|
-
@dtos =
|
155
|
-
@tos =
|
156
|
-
@dfroms =
|
157
|
-
@froms =
|
152
|
+
@dtos = Set.new #: Set[Element[E]]
|
153
|
+
@tos = Set.new #: Set[Element[E]]
|
154
|
+
@dfroms = Set.new #: Set[Element[E]]
|
155
|
+
@froms = Set.new #: Set[Element[E]]
|
158
156
|
end
|
159
157
|
|
160
|
-
|
158
|
+
#: (untyped other) -> Integer?
|
161
159
|
def <=>(other)
|
162
160
|
return unless other.is_a?(Element)
|
163
161
|
return 0 if self == other
|
@@ -170,25 +168,25 @@ module Spoom
|
|
170
168
|
end
|
171
169
|
|
172
170
|
# Direct parents of this element
|
173
|
-
|
171
|
+
#: -> Array[E]
|
174
172
|
def parents
|
175
173
|
@dtos.map(&:value)
|
176
174
|
end
|
177
175
|
|
178
176
|
# Direct and indirect ancestors of this element
|
179
|
-
|
177
|
+
#: -> Array[E]
|
180
178
|
def ancestors
|
181
179
|
@tos.map(&:value)
|
182
180
|
end
|
183
181
|
|
184
182
|
# Direct children of this element
|
185
|
-
|
183
|
+
#: -> Array[E]
|
186
184
|
def children
|
187
185
|
@dfroms.map(&:value)
|
188
186
|
end
|
189
187
|
|
190
188
|
# Direct and indirect descendants of this element
|
191
|
-
|
189
|
+
#: -> Array[E]
|
192
190
|
def descendants
|
193
191
|
@froms.map(&:value)
|
194
192
|
end
|
data/lib/spoom/printer.rb
CHANGED
@@ -5,15 +5,12 @@ require "stringio"
|
|
5
5
|
|
6
6
|
module Spoom
|
7
7
|
class Printer
|
8
|
-
extend T::Sig
|
9
|
-
extend T::Helpers
|
10
|
-
|
11
8
|
include Colorize
|
12
9
|
|
13
|
-
|
10
|
+
#: (IO | StringIO)
|
14
11
|
attr_accessor :out
|
15
12
|
|
16
|
-
|
13
|
+
#: (?out: (IO | StringIO), ?colors: bool, ?indent_level: Integer) -> void
|
17
14
|
def initialize(out: $stdout, colors: true, indent_level: 0)
|
18
15
|
@out = out
|
19
16
|
@colors = colors
|
@@ -21,19 +18,19 @@ module Spoom
|
|
21
18
|
end
|
22
19
|
|
23
20
|
# Increase indent level
|
24
|
-
|
21
|
+
#: -> void
|
25
22
|
def indent
|
26
23
|
@indent_level += 2
|
27
24
|
end
|
28
25
|
|
29
26
|
# Decrease indent level
|
30
|
-
|
27
|
+
#: -> void
|
31
28
|
def dedent
|
32
29
|
@indent_level -= 2
|
33
30
|
end
|
34
31
|
|
35
32
|
# Print `string` into `out`
|
36
|
-
|
33
|
+
#: (String? string) -> void
|
37
34
|
def print(string)
|
38
35
|
return unless string
|
39
36
|
|
@@ -43,7 +40,7 @@ module Spoom
|
|
43
40
|
# Print `string` colored with `color` into `out`
|
44
41
|
#
|
45
42
|
# Does not use colors unless `@colors`.
|
46
|
-
|
43
|
+
#: (String? string, *Color color) -> void
|
47
44
|
def print_colored(string, *color)
|
48
45
|
return unless string
|
49
46
|
|
@@ -52,13 +49,13 @@ module Spoom
|
|
52
49
|
end
|
53
50
|
|
54
51
|
# Print a new line into `out`
|
55
|
-
|
52
|
+
#: -> void
|
56
53
|
def printn
|
57
54
|
print("\n")
|
58
55
|
end
|
59
56
|
|
60
57
|
# Print `string` with indent and newline
|
61
|
-
|
58
|
+
#: (String? string) -> void
|
62
59
|
def printl(string)
|
63
60
|
return unless string
|
64
61
|
|
@@ -68,13 +65,13 @@ module Spoom
|
|
68
65
|
end
|
69
66
|
|
70
67
|
# Print an indent space into `out`
|
71
|
-
|
68
|
+
#: -> void
|
72
69
|
def printt
|
73
70
|
print(" " * @indent_level)
|
74
71
|
end
|
75
72
|
|
76
73
|
# Colorize `string` with color if `@colors`
|
77
|
-
|
74
|
+
#: (String string, *Spoom::Color color) -> String
|
78
75
|
def colorize(string, *color)
|
79
76
|
return string unless @colors
|
80
77
|
|
data/lib/spoom/rbs.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module RBS
|
6
|
+
class Comments
|
7
|
+
#: Array[Annotations]
|
8
|
+
attr_reader :annotations
|
9
|
+
|
10
|
+
#: Array[Signature]
|
11
|
+
attr_reader :signatures
|
12
|
+
|
13
|
+
#: -> void
|
14
|
+
def initialize
|
15
|
+
@annotations = [] #: Array[Annotations]
|
16
|
+
@signatures = [] #: Array[Signature]
|
17
|
+
end
|
18
|
+
|
19
|
+
#: -> bool
|
20
|
+
def empty?
|
21
|
+
@annotations.empty? && @signatures.empty?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Comment
|
26
|
+
#: String
|
27
|
+
attr_reader :string
|
28
|
+
|
29
|
+
#: Prism::Location
|
30
|
+
attr_reader :location
|
31
|
+
|
32
|
+
#: (String, Prism::Location) -> void
|
33
|
+
def initialize(string, location)
|
34
|
+
@string = string
|
35
|
+
@location = location
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Annotations < Comment; end
|
40
|
+
class Signature < Comment; end
|
41
|
+
|
42
|
+
module ExtractRBSComments
|
43
|
+
#: (Prism::Node) -> Comments
|
44
|
+
def node_rbs_comments(node)
|
45
|
+
res = Comments.new
|
46
|
+
|
47
|
+
comments = node.location.leading_comments.reverse
|
48
|
+
return res if comments.empty?
|
49
|
+
|
50
|
+
continuation_comments = [] #: Array[Prism::Comment]
|
51
|
+
|
52
|
+
comments.each do |comment|
|
53
|
+
string = comment.slice
|
54
|
+
|
55
|
+
if string.start_with?("# @")
|
56
|
+
string = string.delete_prefix("#").strip
|
57
|
+
res.annotations << Annotations.new(string, comment.location)
|
58
|
+
elsif string.start_with?("#: ")
|
59
|
+
string = string.delete_prefix("#:").strip
|
60
|
+
location = comment.location
|
61
|
+
|
62
|
+
continuation_comments.reverse_each do |continuation_comment|
|
63
|
+
string = "#{string}#{continuation_comment.slice.delete_prefix("#|")}"
|
64
|
+
location = location.join(continuation_comment.location)
|
65
|
+
end
|
66
|
+
continuation_comments.clear
|
67
|
+
res.signatures << Signature.new(string, location)
|
68
|
+
elsif string.start_with?("#|")
|
69
|
+
continuation_comments << comment
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
res
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/spoom/sorbet/config.rb
CHANGED
@@ -24,25 +24,23 @@ module Spoom
|
|
24
24
|
# puts config.ignore # "c"
|
25
25
|
# ```
|
26
26
|
class Config
|
27
|
-
|
27
|
+
DEFAULT_ALLOWED_EXTENSIONS = [".rb", ".rbi"].freeze #: Array[String]
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
sig { returns(T::Array[String]) }
|
29
|
+
#: Array[String]
|
32
30
|
attr_accessor :paths, :ignore, :allowed_extensions
|
33
31
|
|
34
|
-
|
32
|
+
#: bool
|
35
33
|
attr_accessor :no_stdlib
|
36
34
|
|
37
|
-
|
35
|
+
#: -> void
|
38
36
|
def initialize
|
39
|
-
@paths =
|
40
|
-
@ignore =
|
41
|
-
@allowed_extensions =
|
42
|
-
@no_stdlib =
|
37
|
+
@paths = [] #: Array[String]
|
38
|
+
@ignore = [] #: Array[String]
|
39
|
+
@allowed_extensions = [] #: Array[String]
|
40
|
+
@no_stdlib = false #: bool
|
43
41
|
end
|
44
42
|
|
45
|
-
|
43
|
+
#: -> Config
|
46
44
|
def copy
|
47
45
|
new_config = Sorbet::Config.new
|
48
46
|
new_config.paths.concat(@paths)
|
@@ -64,28 +62,26 @@ module Spoom
|
|
64
62
|
#
|
65
63
|
# puts config.options_string # "/foo /bar --ignore /baz --allowed-extension .rb"
|
66
64
|
# ~~~
|
67
|
-
|
65
|
+
#: -> String
|
68
66
|
def options_string
|
69
67
|
opts = []
|
70
|
-
opts.concat(paths)
|
71
|
-
opts.concat(ignore.map { |p| "--ignore #{p}" })
|
72
|
-
opts.concat(allowed_extensions.map { |ext| "--allowed-extension #{ext}" })
|
68
|
+
opts.concat(paths.map { |p| "'#{p}'" })
|
69
|
+
opts.concat(ignore.map { |p| "--ignore '#{p}'" })
|
70
|
+
opts.concat(allowed_extensions.map { |ext| "--allowed-extension '#{ext}'" })
|
73
71
|
opts << "--no-stdlib" if @no_stdlib
|
74
72
|
opts.join(" ")
|
75
73
|
end
|
76
74
|
|
77
75
|
class << self
|
78
|
-
|
79
|
-
|
80
|
-
sig { params(sorbet_config_path: String).returns(Spoom::Sorbet::Config) }
|
76
|
+
#: (String sorbet_config_path) -> Spoom::Sorbet::Config
|
81
77
|
def parse_file(sorbet_config_path)
|
82
78
|
parse_string(File.read(sorbet_config_path))
|
83
79
|
end
|
84
80
|
|
85
|
-
|
81
|
+
#: (String sorbet_config) -> Spoom::Sorbet::Config
|
86
82
|
def parse_string(sorbet_config)
|
87
83
|
config = Config.new
|
88
|
-
state =
|
84
|
+
state = nil #: Symbol?
|
89
85
|
sorbet_config.each_line do |line|
|
90
86
|
line = line.strip
|
91
87
|
case line
|
@@ -95,9 +91,6 @@ module Spoom
|
|
95
91
|
when /^--allowed-extension=/
|
96
92
|
config.allowed_extensions << parse_option(line)
|
97
93
|
next
|
98
|
-
when /^--ignore=/
|
99
|
-
config.ignore << parse_option(line)
|
100
|
-
next
|
101
94
|
when /^--ignore$/
|
102
95
|
state = :ignore
|
103
96
|
next
|
@@ -146,7 +139,7 @@ module Spoom
|
|
146
139
|
|
147
140
|
private
|
148
141
|
|
149
|
-
|
142
|
+
#: (String line) -> String
|
150
143
|
def parse_option(line)
|
151
144
|
T.must(line.split("=").last).strip
|
152
145
|
end
|
data/lib/spoom/sorbet/errors.rb
CHANGED
@@ -1,54 +1,76 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "rexml/document"
|
5
|
+
|
4
6
|
module Spoom
|
5
7
|
module Sorbet
|
6
8
|
module Errors
|
7
9
|
DEFAULT_ERROR_URL_BASE = "https://srb.help/"
|
8
10
|
|
9
11
|
class << self
|
10
|
-
|
11
|
-
|
12
|
-
sig { params(errors: T::Array[Error]).returns(T::Array[Error]) }
|
12
|
+
#: (Array[Error] errors) -> Array[Error]
|
13
13
|
def sort_errors_by_code(errors)
|
14
14
|
errors.sort_by { |e| [e.code, e.file, e.line, e.message] }
|
15
15
|
end
|
16
|
+
|
17
|
+
#: (Array[Error]) -> REXML::Document
|
18
|
+
def to_junit_xml(errors)
|
19
|
+
testsuite_element = REXML::Element.new("testsuite")
|
20
|
+
testsuite_element.add_attributes(
|
21
|
+
"name" => "Sorbet",
|
22
|
+
"failures" => errors.size,
|
23
|
+
)
|
24
|
+
|
25
|
+
if errors.empty?
|
26
|
+
# Avoid creating an empty report when there are no errors so that
|
27
|
+
# reporting tools know that the type checking ran successfully.
|
28
|
+
testcase_element = testsuite_element.add_element("testcase")
|
29
|
+
testcase_element.add_attributes(
|
30
|
+
"name" => "Typecheck",
|
31
|
+
"tests" => 1,
|
32
|
+
)
|
33
|
+
else
|
34
|
+
errors.each do |error|
|
35
|
+
testsuite_element.add_element(error.to_junit_xml_element)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
doc = REXML::Document.new
|
40
|
+
doc << REXML::XMLDecl.new
|
41
|
+
doc.add_element(testsuite_element)
|
42
|
+
|
43
|
+
doc
|
44
|
+
end
|
16
45
|
end
|
17
46
|
# Parse errors from Sorbet output
|
18
47
|
class Parser
|
19
|
-
extend T::Sig
|
20
|
-
|
21
48
|
class ParseError < Spoom::Error; end
|
22
49
|
|
23
|
-
HEADER =
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
],
|
31
|
-
T::Array[String],
|
32
|
-
)
|
50
|
+
HEADER = [
|
51
|
+
"👋 Hey there! Heads up that this is not a release build of sorbet.",
|
52
|
+
"Release builds are faster and more well-supported by the Sorbet team.",
|
53
|
+
"Check out the README to learn how to build Sorbet in release mode.",
|
54
|
+
"To forcibly silence this error, either pass --silence-dev-message,",
|
55
|
+
"or set SORBET_SILENCE_DEV_MESSAGE=1 in your shell environment.",
|
56
|
+
] #: Array[String]
|
33
57
|
|
34
58
|
class << self
|
35
|
-
|
36
|
-
|
37
|
-
sig { params(output: String, error_url_base: String).returns(T::Array[Error]) }
|
59
|
+
#: (String output, ?error_url_base: String) -> Array[Error]
|
38
60
|
def parse_string(output, error_url_base: DEFAULT_ERROR_URL_BASE)
|
39
61
|
parser = Spoom::Sorbet::Errors::Parser.new(error_url_base: error_url_base)
|
40
62
|
parser.parse(output)
|
41
63
|
end
|
42
64
|
end
|
43
65
|
|
44
|
-
|
66
|
+
#: (?error_url_base: String) -> void
|
45
67
|
def initialize(error_url_base: DEFAULT_ERROR_URL_BASE)
|
46
|
-
@errors =
|
47
|
-
@error_line_match_regex =
|
48
|
-
@current_error =
|
68
|
+
@errors = [] #: Array[Error]
|
69
|
+
@error_line_match_regex = error_line_match_regexp(error_url_base) #: Regexp
|
70
|
+
@current_error = nil #: Error?
|
49
71
|
end
|
50
72
|
|
51
|
-
|
73
|
+
#: (String output) -> Array[Error]
|
52
74
|
def parse(output)
|
53
75
|
output.each_line do |line|
|
54
76
|
break if /^No errors! Great job\./.match?(line)
|
@@ -71,7 +93,7 @@ module Spoom
|
|
71
93
|
|
72
94
|
private
|
73
95
|
|
74
|
-
|
96
|
+
#: (String error_url_base) -> Regexp
|
75
97
|
def error_line_match_regexp(error_url_base)
|
76
98
|
url = Regexp.escape(error_url_base)
|
77
99
|
%r{
|
@@ -88,7 +110,7 @@ module Spoom
|
|
88
110
|
}x
|
89
111
|
end
|
90
112
|
|
91
|
-
|
113
|
+
#: (String line) -> Error?
|
92
114
|
def match_error_line(line)
|
93
115
|
match = line.match(@error_line_match_regex)
|
94
116
|
return unless match
|
@@ -97,14 +119,14 @@ module Spoom
|
|
97
119
|
Error.new(file, line&.to_i, message, code&.to_i)
|
98
120
|
end
|
99
121
|
|
100
|
-
|
122
|
+
#: (Error error) -> void
|
101
123
|
def open_error(error)
|
102
124
|
raise ParseError, "Error: Already parsing an error!" if @current_error
|
103
125
|
|
104
126
|
@current_error = error
|
105
127
|
end
|
106
128
|
|
107
|
-
|
129
|
+
#: -> void
|
108
130
|
def close_error
|
109
131
|
raise ParseError, "Error: Not already parsing an error!" unless @current_error
|
110
132
|
|
@@ -112,7 +134,7 @@ module Spoom
|
|
112
134
|
@current_error = nil
|
113
135
|
end
|
114
136
|
|
115
|
-
|
137
|
+
#: (String line) -> void
|
116
138
|
def append_error(line)
|
117
139
|
raise ParseError, "Error: Not already parsing an error!" unless @current_error
|
118
140
|
|
@@ -126,51 +148,71 @@ module Spoom
|
|
126
148
|
|
127
149
|
class Error
|
128
150
|
include Comparable
|
129
|
-
extend T::Sig
|
130
151
|
|
131
|
-
|
152
|
+
#: String?
|
132
153
|
attr_reader :file, :message
|
133
154
|
|
134
|
-
|
155
|
+
#: Integer?
|
135
156
|
attr_reader :line, :code
|
136
157
|
|
137
|
-
|
158
|
+
#: Array[String]
|
138
159
|
attr_reader :more
|
139
160
|
|
140
161
|
# Other files associated with the error
|
141
|
-
|
162
|
+
#: Set[String]
|
142
163
|
attr_reader :files_from_error_sections
|
143
164
|
|
144
|
-
|
145
|
-
params(
|
146
|
-
file: T.nilable(String),
|
147
|
-
line: T.nilable(Integer),
|
148
|
-
message: T.nilable(String),
|
149
|
-
code: T.nilable(Integer),
|
150
|
-
more: T::Array[String],
|
151
|
-
).void
|
152
|
-
end
|
165
|
+
#: (String? file, Integer? line, String? message, Integer? code, ?Array[String] more) -> void
|
153
166
|
def initialize(file, line, message, code, more = [])
|
154
167
|
@file = file
|
155
168
|
@line = line
|
156
169
|
@message = message
|
157
170
|
@code = code
|
158
171
|
@more = more
|
159
|
-
@files_from_error_sections =
|
172
|
+
@files_from_error_sections = Set.new #: Set[String]
|
160
173
|
end
|
161
174
|
|
162
175
|
# By default errors are sorted by location
|
163
|
-
|
176
|
+
#: (untyped other) -> Integer
|
164
177
|
def <=>(other)
|
165
178
|
return 0 unless other.is_a?(Error)
|
166
179
|
|
167
180
|
[file, line, code, message] <=> [other.file, other.line, other.code, other.message]
|
168
181
|
end
|
169
182
|
|
170
|
-
|
183
|
+
#: -> String
|
171
184
|
def to_s
|
172
185
|
"#{file}:#{line}: #{message} (#{code})"
|
173
186
|
end
|
187
|
+
|
188
|
+
#: -> REXML::Element
|
189
|
+
def to_junit_xml_element
|
190
|
+
testcase_element = REXML::Element.new("testcase")
|
191
|
+
# Unlike traditional test suites, we can't report all tests
|
192
|
+
# regardless of outcome; we only have errors to report. As a
|
193
|
+
# result we reinterpret the definitions of the test properties
|
194
|
+
# bit: the error message becomes the test name and the full error
|
195
|
+
# info gets plugged into the failure body along with file/line
|
196
|
+
# information (displayed in Jenkins as the "Stacktrace" for the
|
197
|
+
# error).
|
198
|
+
testcase_element.add_attributes(
|
199
|
+
"name" => message,
|
200
|
+
"file" => file,
|
201
|
+
"line" => line,
|
202
|
+
)
|
203
|
+
failure_element = testcase_element.add_element("failure")
|
204
|
+
failure_element.add_attributes(
|
205
|
+
"type" => code,
|
206
|
+
)
|
207
|
+
explanation_text = [
|
208
|
+
"In file #{file}:\n",
|
209
|
+
*more,
|
210
|
+
].join.chomp
|
211
|
+
# Use CDATA so that parsers know the whitespace is significant.
|
212
|
+
failure_element.add(REXML::CData.new(explanation_text))
|
213
|
+
|
214
|
+
testcase_element
|
215
|
+
end
|
174
216
|
end
|
175
217
|
end
|
176
218
|
end
|