spoom 1.1.11 → 1.1.12

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.
@@ -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