spoom 1.1.11 → 1.1.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,94 +9,97 @@ require "date"
9
9
 
10
10
  module Spoom
11
11
  module Coverage
12
- extend T::Sig
13
-
14
- sig { params(path: String, rbi: T::Boolean, sorbet_bin: T.nilable(String)).returns(Snapshot) }
15
- def self.snapshot(path: '.', rbi: true, sorbet_bin: nil)
16
- config = sorbet_config(path: path)
17
- config.allowed_extensions.push(".rb", ".rbi") if config.allowed_extensions.empty?
18
-
19
- new_config = config.copy
20
- new_config.allowed_extensions.reject! { |ext| !rbi && ext == ".rbi" }
21
-
22
- metrics = Spoom::Sorbet.srb_metrics(
23
- "--no-config",
24
- "--no-error-sections",
25
- "--no-error-count",
26
- "--isolate-error-code=0",
27
- new_config.options_string,
28
- path: path,
29
- capture_err: true,
30
- sorbet_bin: sorbet_bin
31
- )
32
-
33
- snapshot = Snapshot.new
34
- return snapshot unless metrics
35
-
36
- sha = Spoom::Git.last_commit(path: path)
37
- snapshot.commit_sha = sha
38
- snapshot.commit_timestamp = Spoom::Git.commit_timestamp(sha, path: path).to_i if sha
39
-
40
- snapshot.files = metrics.fetch("types.input.files", 0)
41
- snapshot.modules = metrics.fetch("types.input.modules.total", 0)
42
- snapshot.classes = metrics.fetch("types.input.classes.total", 0)
43
- snapshot.singleton_classes = metrics.fetch("types.input.singleton_classes.total", 0)
44
- snapshot.methods_with_sig = metrics.fetch("types.sig.count", 0)
45
- snapshot.methods_without_sig = metrics.fetch("types.input.methods.total", 0) - snapshot.methods_with_sig
46
- snapshot.calls_typed = metrics.fetch("types.input.sends.typed", 0)
47
- snapshot.calls_untyped = metrics.fetch("types.input.sends.total", 0) - snapshot.calls_typed
48
-
49
- snapshot.duration += metrics.fetch("run.utilization.system_time.us", 0)
50
- snapshot.duration += metrics.fetch("run.utilization.user_time.us", 0)
51
-
52
- Snapshot::STRICTNESSES.each do |strictness|
53
- next unless metrics.key?("types.input.files.sigil.#{strictness}")
54
- snapshot.sigils[strictness] = T.must(metrics["types.input.files.sigil.#{strictness}"])
12
+ class << self
13
+ extend T::Sig
14
+
15
+ sig { params(path: String, rbi: T::Boolean, sorbet_bin: T.nilable(String)).returns(Snapshot) }
16
+ def snapshot(path: ".", rbi: true, sorbet_bin: nil)
17
+ config = sorbet_config(path: path)
18
+ config.allowed_extensions.push(".rb", ".rbi") if config.allowed_extensions.empty?
19
+
20
+ new_config = config.copy
21
+ new_config.allowed_extensions.reject! { |ext| !rbi && ext == ".rbi" }
22
+
23
+ metrics = Spoom::Sorbet.srb_metrics(
24
+ "--no-config",
25
+ "--no-error-sections",
26
+ "--no-error-count",
27
+ "--isolate-error-code=0",
28
+ new_config.options_string,
29
+ path: path,
30
+ capture_err: true,
31
+ sorbet_bin: sorbet_bin
32
+ )
33
+
34
+ snapshot = Snapshot.new
35
+ return snapshot unless metrics
36
+
37
+ sha = Spoom::Git.last_commit(path: path)
38
+ snapshot.commit_sha = sha
39
+ snapshot.commit_timestamp = Spoom::Git.commit_timestamp(sha, path: path).to_i if sha
40
+
41
+ snapshot.files = metrics.fetch("types.input.files", 0)
42
+ snapshot.modules = metrics.fetch("types.input.modules.total", 0)
43
+ snapshot.classes = metrics.fetch("types.input.classes.total", 0)
44
+ snapshot.singleton_classes = metrics.fetch("types.input.singleton_classes.total", 0)
45
+ snapshot.methods_with_sig = metrics.fetch("types.sig.count", 0)
46
+ snapshot.methods_without_sig = metrics.fetch("types.input.methods.total", 0) - snapshot.methods_with_sig
47
+ snapshot.calls_typed = metrics.fetch("types.input.sends.typed", 0)
48
+ snapshot.calls_untyped = metrics.fetch("types.input.sends.total", 0) - snapshot.calls_typed
49
+
50
+ snapshot.duration += metrics.fetch("run.utilization.system_time.us", 0)
51
+ snapshot.duration += metrics.fetch("run.utilization.user_time.us", 0)
52
+
53
+ Snapshot::STRICTNESSES.each do |strictness|
54
+ next unless metrics.key?("types.input.files.sigil.#{strictness}")
55
+
56
+ snapshot.sigils[strictness] = T.must(metrics["types.input.files.sigil.#{strictness}"])
57
+ end
58
+
59
+ snapshot.version_static = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-static", path: path)
60
+ snapshot.version_runtime = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-runtime", path: path)
61
+
62
+ files = Spoom::Sorbet.srb_files(new_config, path: path)
63
+ snapshot.rbi_files = files.count { |file| file.end_with?(".rbi") }
64
+
65
+ snapshot
55
66
  end
56
67
 
57
- snapshot.version_static = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-static", path: path)
58
- snapshot.version_runtime = Spoom::Sorbet.version_from_gemfile_lock(gem: "sorbet-runtime", path: path)
59
-
60
- files = Spoom::Sorbet.srb_files(new_config, path: path)
61
- snapshot.rbi_files = files.count { |file| file.end_with?(".rbi") }
62
-
63
- snapshot
64
- end
65
-
66
- sig { params(snapshots: T::Array[Snapshot], palette: D3::ColorPalette, path: String).returns(Report) }
67
- def self.report(snapshots, palette:, path: ".")
68
- intro_commit = Git.sorbet_intro_commit(path: path)
69
- intro_date = intro_commit ? Git.commit_time(intro_commit, path: path) : nil
70
-
71
- Report.new(
72
- project_name: File.basename(File.expand_path(path)),
73
- palette: palette,
74
- snapshots: snapshots,
75
- sigils_tree: sigils_tree(path: path),
76
- sorbet_intro_commit: intro_commit,
77
- sorbet_intro_date: intro_date,
78
- )
79
- end
68
+ sig { params(snapshots: T::Array[Snapshot], palette: D3::ColorPalette, path: String).returns(Report) }
69
+ def report(snapshots, palette:, path: ".")
70
+ intro_commit = Git.sorbet_intro_commit(path: path)
71
+ intro_date = intro_commit ? Git.commit_time(intro_commit, path: path) : nil
72
+
73
+ Report.new(
74
+ project_name: File.basename(File.expand_path(path)),
75
+ palette: palette,
76
+ snapshots: snapshots,
77
+ sigils_tree: sigils_tree(path: path),
78
+ sorbet_intro_commit: intro_commit,
79
+ sorbet_intro_date: intro_date,
80
+ )
81
+ end
80
82
 
81
- sig { params(path: String).returns(Sorbet::Config) }
82
- def self.sorbet_config(path: ".")
83
- Sorbet::Config.parse_file("#{path}/#{Spoom::Sorbet::CONFIG_PATH}")
84
- end
83
+ sig { params(path: String).returns(Sorbet::Config) }
84
+ def sorbet_config(path: ".")
85
+ Sorbet::Config.parse_file("#{path}/#{Spoom::Sorbet::CONFIG_PATH}")
86
+ end
85
87
 
86
- sig { params(path: String).returns(FileTree) }
87
- def self.sigils_tree(path: ".")
88
- config = sorbet_config(path: path)
89
- files = Sorbet.srb_files(config, path: path)
88
+ sig { params(path: String).returns(FileTree) }
89
+ def sigils_tree(path: ".")
90
+ config = sorbet_config(path: path)
91
+ files = Sorbet.srb_files(config, path: path)
90
92
 
91
- extensions = config.allowed_extensions
92
- extensions = [".rb"] if extensions.empty?
93
- extensions -= [".rbi"]
93
+ extensions = config.allowed_extensions
94
+ extensions = [".rb"] if extensions.empty?
95
+ extensions -= [".rbi"]
94
96
 
95
- pattern = /\.(#{Regexp.union(extensions.map { |ext| ext[1..-1] })})$/
96
- files.select! { |file| file =~ pattern }
97
- files.reject! { |file| file =~ %r{/test/} }
97
+ pattern = /\.(#{Regexp.union(extensions.map { |ext| ext[1..-1] })})$/
98
+ files.select! { |file| file =~ pattern }
99
+ files.reject! { |file| file =~ %r{/test/} }
98
100
 
99
- FileTree.new(files, strip_prefix: path)
101
+ FileTree.new(files, strip_prefix: path)
102
+ end
100
103
  end
101
104
  end
102
105
  end
@@ -33,6 +33,7 @@ module Spoom
33
33
  if path.empty? || parts.size == 1
34
34
  return @roots[path] ||= Node.new(parent: nil, name: path)
35
35
  end
36
+
36
37
  parent_path = T.must(parts[0...-1]).join("/")
37
38
  parent = add_path(parent_path)
38
39
  name = T.must(parts.last)
@@ -105,6 +106,7 @@ module Spoom
105
106
  def path
106
107
  parent = self.parent
107
108
  return name unless parent
109
+
108
110
  "#{parent.path}/#{name}"
109
111
  end
110
112
  end
data/lib/spoom/git.rb CHANGED
@@ -6,119 +6,129 @@ require "time"
6
6
  module Spoom
7
7
  # Execute git commands
8
8
  module Git
9
- extend T::Sig
10
-
11
- # Execute a `command`
12
- sig { params(command: String, arg: String, path: String).returns(ExecResult) }
13
- def self.exec(command, *arg, path: '.')
14
- return ExecResult.new(
15
- out: "",
16
- err: "Error: `#{path}` is not a directory.",
17
- status: false,
18
- exit_code: 1
19
- ) unless File.directory?(path)
20
-
21
- T.unsafe(Open3).popen3(command, *arg, chdir: path) do |_, stdout, stderr, thread|
22
- status = T.cast(thread.value, Process::Status)
23
- ExecResult.new(
24
- out: stdout.read,
25
- err: stderr.read,
26
- status: T.must(status.success?),
27
- exit_code: T.must(status.exitstatus)
28
- )
9
+ class << self
10
+ extend T::Sig
11
+
12
+ # Execute a `command`
13
+ sig { params(command: String, arg: String, path: String).returns(ExecResult) }
14
+ def exec(command, *arg, path: ".")
15
+ return ExecResult.new(
16
+ out: "",
17
+ err: "Error: `#{path}` is not a directory.",
18
+ status: false,
19
+ exit_code: 1
20
+ ) unless File.directory?(path)
21
+
22
+ T.unsafe(Open3).popen3(command, *arg, chdir: path) do |_, stdout, stderr, thread|
23
+ status = T.cast(thread.value, Process::Status)
24
+ ExecResult.new(
25
+ out: stdout.read,
26
+ err: stderr.read,
27
+ status: T.must(status.success?),
28
+ exit_code: T.must(status.exitstatus)
29
+ )
30
+ end
29
31
  end
30
- end
31
32
 
32
- # Git commands
33
+ # Git commands
33
34
 
34
- sig { params(arg: String, path: String).returns(ExecResult) }
35
- def self.checkout(*arg, path: ".")
36
- exec("git checkout -q #{arg.join(' ')}", path: path)
37
- end
35
+ sig { params(arg: String, path: String).returns(ExecResult) }
36
+ def checkout(*arg, path: ".")
37
+ exec("git checkout -q #{arg.join(" ")}", path: path)
38
+ end
38
39
 
39
- sig { params(arg: String, path: String).returns(ExecResult) }
40
- def self.diff(*arg, path: ".")
41
- exec("git diff #{arg.join(' ')}", path: path)
42
- end
40
+ sig { params(arg: String, path: String).returns(ExecResult) }
41
+ def diff(*arg, path: ".")
42
+ exec("git diff #{arg.join(" ")}", path: path)
43
+ end
43
44
 
44
- sig { params(arg: String, path: String).returns(ExecResult) }
45
- def self.log(*arg, path: ".")
46
- exec("git log #{arg.join(' ')}", path: path)
47
- end
45
+ sig { params(arg: String, path: String).returns(ExecResult) }
46
+ def log(*arg, path: ".")
47
+ exec("git log #{arg.join(" ")}", path: path)
48
+ end
48
49
 
49
- sig { params(arg: String, path: String).returns(ExecResult) }
50
- def self.rev_parse(*arg, path: ".")
51
- exec("git rev-parse --short #{arg.join(' ')}", path: path)
52
- end
50
+ sig { params(arg: String, path: String).returns(ExecResult) }
51
+ def rev_parse(*arg, path: ".")
52
+ exec("git rev-parse --short #{arg.join(" ")}", path: path)
53
+ end
53
54
 
54
- sig { params(arg: String, path: String).returns(ExecResult) }
55
- def self.show(*arg, path: ".")
56
- exec("git show #{arg.join(' ')}", path: path)
57
- end
55
+ sig { params(arg: String, path: String).returns(ExecResult) }
56
+ def show(*arg, path: ".")
57
+ exec("git show #{arg.join(" ")}", path: path)
58
+ end
58
59
 
59
- sig { params(path: String).returns(T.nilable(String)) }
60
- def self.current_branch(path: ".")
61
- result = exec("git branch --show-current", path: path)
62
- return nil unless result.status
63
- result.out.strip
64
- end
60
+ sig { params(path: String).returns(T.nilable(String)) }
61
+ def current_branch(path: ".")
62
+ result = exec("git branch --show-current", path: path)
63
+ return nil unless result.status
65
64
 
66
- # Utils
65
+ result.out.strip
66
+ end
67
67
 
68
- # Get the commit epoch timestamp for a `sha`
69
- sig { params(sha: String, path: String).returns(T.nilable(Integer)) }
70
- def self.commit_timestamp(sha, path: ".")
71
- result = show("--no-notes --no-patch --pretty=%at #{sha}", path: path)
72
- return nil unless result.status
73
- result.out.strip.to_i
74
- end
68
+ # Utils
75
69
 
76
- # Get the commit Time for a `sha`
77
- sig { params(sha: String, path: String).returns(T.nilable(Time)) }
78
- def self.commit_time(sha, path: ".")
79
- timestamp = commit_timestamp(sha, path: path)
80
- return nil unless timestamp
81
- epoch_to_time(timestamp.to_s)
82
- end
70
+ # Get the commit epoch timestamp for a `sha`
71
+ sig { params(sha: String, path: String).returns(T.nilable(Integer)) }
72
+ def commit_timestamp(sha, path: ".")
73
+ result = show("--no-notes --no-patch --pretty=%at #{sha}", path: path)
74
+ return nil unless result.status
83
75
 
84
- # Get the last commit sha
85
- sig { params(path: String).returns(T.nilable(String)) }
86
- def self.last_commit(path: ".")
87
- result = rev_parse("HEAD", path: path)
88
- return nil unless result.status
89
- result.out.strip
90
- end
76
+ result.out.strip.to_i
77
+ end
91
78
 
92
- # Translate a git epoch timestamp into a Time
93
- sig { params(timestamp: String).returns(Time) }
94
- def self.epoch_to_time(timestamp)
95
- Time.strptime(timestamp, "%s")
96
- end
79
+ # Get the commit Time for a `sha`
80
+ sig { params(sha: String, path: String).returns(T.nilable(Time)) }
81
+ def commit_time(sha, path: ".")
82
+ timestamp = commit_timestamp(sha, path: path)
83
+ return nil unless timestamp
97
84
 
98
- # Is there uncommited changes in `path`?
99
- sig { params(path: String).returns(T::Boolean) }
100
- def self.workdir_clean?(path: ".")
101
- diff("HEAD", path: path).out.empty?
102
- end
85
+ epoch_to_time(timestamp.to_s)
86
+ end
103
87
 
104
- # Get the hash of the commit introducing the `sorbet/config` file
105
- sig { params(path: String).returns(T.nilable(String)) }
106
- def self.sorbet_intro_commit(path: ".")
107
- result = Spoom::Git.log("--diff-filter=A --format='%h' -1 -- sorbet/config", path: path)
108
- return nil unless result.status
109
- out = result.out.strip
110
- return nil if out.empty?
111
- out
112
- end
88
+ # Get the last commit sha
89
+ sig { params(path: String).returns(T.nilable(String)) }
90
+ def last_commit(path: ".")
91
+ result = rev_parse("HEAD", path: path)
92
+ return nil unless result.status
113
93
 
114
- # Get the hash of the commit removing the `sorbet/config` file
115
- sig { params(path: String).returns(T.nilable(String)) }
116
- def self.sorbet_removal_commit(path: ".")
117
- result = Spoom::Git.log("--diff-filter=D --format='%h' -1 -- sorbet/config", path: path)
118
- return nil unless result.status
119
- out = result.out.strip
120
- return nil if out.empty?
121
- out
94
+ result.out.strip
95
+ end
96
+
97
+ # Translate a git epoch timestamp into a Time
98
+ sig { params(timestamp: String).returns(Time) }
99
+ def epoch_to_time(timestamp)
100
+ Time.strptime(timestamp, "%s")
101
+ end
102
+
103
+ # Is there uncommited changes in `path`?
104
+ sig { params(path: String).returns(T::Boolean) }
105
+ def workdir_clean?(path: ".")
106
+ diff("HEAD", path: path).out.empty?
107
+ end
108
+
109
+ # Get the hash of the commit introducing the `sorbet/config` file
110
+ sig { params(path: String).returns(T.nilable(String)) }
111
+ def sorbet_intro_commit(path: ".")
112
+ result = Spoom::Git.log("--diff-filter=A --format='%h' -1 -- sorbet/config", path: path)
113
+ return nil unless result.status
114
+
115
+ out = result.out.strip
116
+ return nil if out.empty?
117
+
118
+ out
119
+ end
120
+
121
+ # Get the hash of the commit removing the `sorbet/config` file
122
+ sig { params(path: String).returns(T.nilable(String)) }
123
+ def sorbet_removal_commit(path: ".")
124
+ result = Spoom::Git.log("--diff-filter=D --format='%h' -1 -- sorbet/config", path: path)
125
+ return nil unless result.status
126
+
127
+ out = result.out.strip
128
+ return nil if out.empty?
129
+
130
+ out
131
+ end
122
132
  end
123
133
  end
124
134
  end
data/lib/spoom/printer.rb CHANGED
@@ -38,6 +38,7 @@ module Spoom
38
38
  sig { params(string: T.nilable(String)).void }
39
39
  def print(string)
40
40
  return unless string
41
+
41
42
  @out.print(string)
42
43
  end
43
44
 
@@ -47,6 +48,7 @@ module Spoom
47
48
  sig { params(string: T.nilable(String), color: Color).void }
48
49
  def print_colored(string, *color)
49
50
  return unless string
51
+
50
52
  string = T.unsafe(self).colorize(string, *color)
51
53
  @out.print(string)
52
54
  end
@@ -61,6 +63,7 @@ module Spoom
61
63
  sig { params(string: T.nilable(String)).void }
62
64
  def printl(string)
63
65
  return unless string
66
+
64
67
  printt
65
68
  print(string)
66
69
  printn
@@ -76,6 +79,7 @@ module Spoom
76
79
  sig { params(string: String, color: Spoom::Color).returns(String) }
77
80
  def colorize(string, *color)
78
81
  return string unless @colors
82
+
79
83
  T.unsafe(self).set_color(string, *color)
80
84
  end
81
85
  end
@@ -4,10 +4,16 @@
4
4
  module Spoom
5
5
  module Sorbet
6
6
  module Errors
7
- extend T::Sig
8
-
9
7
  DEFAULT_ERROR_URL_BASE = "https://srb.help/"
10
8
 
9
+ class << self
10
+ extend T::Sig
11
+
12
+ sig { params(errors: T::Array[Error]).returns(T::Array[Error]) }
13
+ def sort_errors_by_code(errors)
14
+ errors.sort_by { |e| [e.code, e.file, e.line, e.message] }
15
+ end
16
+ end
11
17
  # Parse errors from Sorbet output
12
18
  class Parser
13
19
  extend T::Sig
@@ -20,10 +26,14 @@ module Spoom
20
26
  "or set SORBET_SILENCE_DEV_MESSAGE=1 in your shell environment.",
21
27
  ], T::Array[String])
22
28
 
23
- sig { params(output: String, error_url_base: String).returns(T::Array[Error]) }
24
- def self.parse_string(output, error_url_base: DEFAULT_ERROR_URL_BASE)
25
- parser = Spoom::Sorbet::Errors::Parser.new(error_url_base: error_url_base)
26
- parser.parse(output)
29
+ class << self
30
+ extend T::Sig
31
+
32
+ sig { params(output: String, error_url_base: String).returns(T::Array[Error]) }
33
+ def parse_string(output, error_url_base: DEFAULT_ERROR_URL_BASE)
34
+ parser = Spoom::Sorbet::Errors::Parser.new(error_url_base: error_url_base)
35
+ parser.parse(output)
36
+ end
27
37
  end
28
38
 
29
39
  sig { params(error_url_base: String).void }
@@ -85,12 +95,14 @@ module Spoom
85
95
  sig { params(error: Error).void }
86
96
  def open_error(error)
87
97
  raise "Error: Already parsing an error!" if @current_error
98
+
88
99
  @current_error = error
89
100
  end
90
101
 
91
102
  sig { void }
92
103
  def close_error
93
104
  raise "Error: Not already parsing an error!" unless @current_error
105
+
94
106
  @errors << @current_error
95
107
  @current_error = nil
96
108
  end
@@ -98,6 +110,11 @@ module Spoom
98
110
  sig { params(line: String).void }
99
111
  def append_error(line)
100
112
  raise "Error: Not already parsing an error!" unless @current_error
113
+
114
+ filepath_match = line.match(/^ (.*?):\d+/)
115
+ if filepath_match && filepath_match[1]
116
+ @current_error.files_from_error_sections << T.must(filepath_match[1])
117
+ end
101
118
  @current_error.more << line
102
119
  end
103
120
  end
@@ -115,6 +132,10 @@ module Spoom
115
132
  sig { returns(T::Array[String]) }
116
133
  attr_reader :more
117
134
 
135
+ # Other files associated with the error
136
+ sig { returns(T::Set[String]) }
137
+ attr_reader :files_from_error_sections
138
+
118
139
  sig do
119
140
  params(
120
141
  file: T.nilable(String),
@@ -130,12 +151,14 @@ module Spoom
130
151
  @message = message
131
152
  @code = code
132
153
  @more = more
154
+ @files_from_error_sections = T.let(Set.new, T::Set[String])
133
155
  end
134
156
 
135
157
  # By default errors are sorted by location
136
158
  sig { params(other: T.untyped).returns(Integer) }
137
159
  def <=>(other)
138
160
  return 0 unless other.is_a?(Error)
161
+
139
162
  [file, line, code, message] <=> [other.file, other.line, other.code, other.message]
140
163
  end
141
164
 
@@ -144,11 +167,6 @@ module Spoom
144
167
  "#{file}:#{line}: #{message} (#{code})"
145
168
  end
146
169
  end
147
-
148
- sig { params(errors: T::Array[Error]).returns(T::Array[Error]) }
149
- def self.sort_errors_by_code(errors)
150
- errors.sort_by { |e| [e.code, e.file, e.line, e.message] }
151
- end
152
170
  end
153
171
  end
154
172
  end
@@ -24,7 +24,7 @@ module Spoom
24
24
  def as_json
25
25
  instance_variables.each_with_object({}) do |var, obj|
26
26
  val = instance_variable_get(var)
27
- obj[var.to_s.delete('@')] = val if val
27
+ obj[var.to_s.delete("@")] = val if val
28
28
  end
29
29
  end
30
30
 
@@ -16,18 +16,23 @@ module Spoom
16
16
  sig { returns(T::Array[Diagnostic]) }
17
17
  attr_reader :diagnostics
18
18
 
19
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Diagnostics) }
20
- def self.from_json(json)
21
- Diagnostics.new(
22
- json['uri'],
23
- json['diagnostics'].map { |d| Diagnostic.from_json(d) }
24
- )
19
+ class << self
20
+ extend T::Sig
21
+
22
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Diagnostics) }
23
+ def from_json(json)
24
+ Diagnostics.new(
25
+ json["uri"],
26
+ json["diagnostics"].map { |d| Diagnostic.from_json(d) }
27
+ )
28
+ end
25
29
  end
26
30
 
27
31
  sig { params(uri: String, diagnostics: T::Array[Diagnostic]).void }
28
32
  def initialize(uri, diagnostics)
29
33
  @uri = uri
30
34
  @diagnostics = diagnostics
35
+ super()
31
36
  end
32
37
  end
33
38
  end
@@ -38,25 +43,26 @@ module Spoom
38
43
  sig { returns(Integer) }
39
44
  attr_reader :code
40
45
 
41
- sig { returns(String) }
42
- attr_reader :message
43
-
44
46
  sig { returns(T::Hash[T.untyped, T.untyped]) }
45
47
  attr_reader :data
46
48
 
47
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(ResponseError) }
48
- def self.from_json(json)
49
- ResponseError.new(
50
- json['code'],
51
- json['message'],
52
- json['data']
53
- )
49
+ class << self
50
+ extend T::Sig
51
+
52
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(ResponseError) }
53
+ def from_json(json)
54
+ ResponseError.new(
55
+ json["code"],
56
+ json["message"],
57
+ json["data"]
58
+ )
59
+ end
54
60
  end
55
61
 
56
62
  sig { params(code: Integer, message: String, data: T::Hash[T.untyped, T.untyped]).void }
57
63
  def initialize(code, message, data)
64
+ super(message)
58
65
  @code = code
59
- @message = message
60
66
  @data = data
61
67
  end
62
68
  end