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.
@@ -23,12 +23,16 @@ module Spoom
23
23
  const :contents, String
24
24
  const :range, T.nilable(Range)
25
25
 
26
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Hover) }
27
- def self.from_json(json)
28
- Hover.new(
29
- contents: json['contents']['value'],
30
- range: json['range'] ? Range.from_json(json['range']) : nil
31
- )
26
+ class << self
27
+ extend T::Sig
28
+
29
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Hover) }
30
+ def from_json(json)
31
+ Hover.new(
32
+ contents: json["contents"]["value"],
33
+ range: json["range"] ? Range.from_json(json["range"]) : nil
34
+ )
35
+ end
32
36
  end
33
37
 
34
38
  sig { override.params(printer: SymbolPrinter).void }
@@ -50,12 +54,16 @@ module Spoom
50
54
  const :line, Integer
51
55
  const :char, Integer
52
56
 
53
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Position) }
54
- def self.from_json(json)
55
- Position.new(
56
- line: json['line'].to_i,
57
- char: json['character'].to_i
58
- )
57
+ class << self
58
+ extend T::Sig
59
+
60
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Position) }
61
+ def from_json(json)
62
+ Position.new(
63
+ line: json["line"].to_i,
64
+ char: json["character"].to_i
65
+ )
66
+ end
59
67
  end
60
68
 
61
69
  sig { override.params(printer: SymbolPrinter).void }
@@ -76,12 +84,16 @@ module Spoom
76
84
  const :start, Position
77
85
  const :end, Position
78
86
 
79
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Range) }
80
- def self.from_json(json)
81
- Range.new(
82
- start: Position.from_json(json['start']),
83
- end: Position.from_json(json['end'])
84
- )
87
+ class << self
88
+ extend T::Sig
89
+
90
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Range) }
91
+ def from_json(json)
92
+ Range.new(
93
+ start: Position.from_json(json["start"]),
94
+ end: Position.from_json(json["end"])
95
+ )
96
+ end
85
97
  end
86
98
 
87
99
  sig { override.params(printer: SymbolPrinter).void }
@@ -104,12 +116,16 @@ module Spoom
104
116
  const :uri, String
105
117
  const :range, LSP::Range
106
118
 
107
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Location) }
108
- def self.from_json(json)
109
- Location.new(
110
- uri: json['uri'],
111
- range: Range.from_json(json['range'])
112
- )
119
+ class << self
120
+ extend T::Sig
121
+
122
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Location) }
123
+ def from_json(json)
124
+ Location.new(
125
+ uri: json["uri"],
126
+ range: Range.from_json(json["range"])
127
+ )
128
+ end
113
129
  end
114
130
 
115
131
  sig { override.params(printer: SymbolPrinter).void }
@@ -132,20 +148,24 @@ module Spoom
132
148
  const :doc, Object # TODO
133
149
  const :params, T::Array[T.untyped] # TODO
134
150
 
135
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(SignatureHelp) }
136
- def self.from_json(json)
137
- SignatureHelp.new(
138
- label: json['label'],
139
- doc: json['documentation'],
140
- params: json['parameters'],
141
- )
151
+ class << self
152
+ extend T::Sig
153
+
154
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(SignatureHelp) }
155
+ def from_json(json)
156
+ SignatureHelp.new(
157
+ label: json["label"],
158
+ doc: json["documentation"],
159
+ params: json["parameters"],
160
+ )
161
+ end
142
162
  end
143
163
 
144
164
  sig { override.params(printer: SymbolPrinter).void }
145
165
  def accept_printer(printer)
146
166
  printer.print(label)
147
167
  printer.print("(")
148
- printer.print(params.map { |l| "#{l['label']}: #{l['documentation']}" }.join(", "))
168
+ printer.print(params.map { |l| "#{l["label"]}: #{l["documentation"]}" }.join(", "))
149
169
  printer.print(")")
150
170
  end
151
171
 
@@ -164,14 +184,18 @@ module Spoom
164
184
  const :message, String
165
185
  const :informations, Object
166
186
 
167
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Diagnostic) }
168
- def self.from_json(json)
169
- Diagnostic.new(
170
- range: Range.from_json(json['range']),
171
- code: json['code'].to_i,
172
- message: json['message'],
173
- informations: json['relatedInformation']
174
- )
187
+ class << self
188
+ extend T::Sig
189
+
190
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(Diagnostic) }
191
+ def from_json(json)
192
+ Diagnostic.new(
193
+ range: Range.from_json(json["range"]),
194
+ code: json["code"].to_i,
195
+ message: json["message"],
196
+ informations: json["relatedInformation"]
197
+ )
198
+ end
175
199
  end
176
200
 
177
201
  sig { override.params(printer: SymbolPrinter).void }
@@ -196,35 +220,40 @@ module Spoom
196
220
  const :range, T.nilable(Range)
197
221
  const :children, T::Array[DocumentSymbol]
198
222
 
199
- sig { params(json: T::Hash[T.untyped, T.untyped]).returns(DocumentSymbol) }
200
- def self.from_json(json)
201
- DocumentSymbol.new(
202
- name: json['name'],
203
- detail: json['detail'],
204
- kind: json['kind'],
205
- location: json['location'] ? Location.from_json(json['location']) : nil,
206
- range: json['range'] ? Range.from_json(json['range']) : nil,
207
- children: json['children'] ? json['children'].map { |symbol| DocumentSymbol.from_json(symbol) } : [],
208
- )
223
+ class << self
224
+ extend T::Sig
225
+
226
+ sig { params(json: T::Hash[T.untyped, T.untyped]).returns(DocumentSymbol) }
227
+ def from_json(json)
228
+ DocumentSymbol.new(
229
+ name: json["name"],
230
+ detail: json["detail"],
231
+ kind: json["kind"],
232
+ location: json["location"] ? Location.from_json(json["location"]) : nil,
233
+ range: json["range"] ? Range.from_json(json["range"]) : nil,
234
+ children: json["children"] ? json["children"].map { |symbol| DocumentSymbol.from_json(symbol) } : [],
235
+ )
236
+ end
209
237
  end
210
238
 
211
239
  sig { override.params(printer: SymbolPrinter).void }
212
240
  def accept_printer(printer)
213
241
  h = serialize.hash
214
242
  return if printer.seen.include?(h)
243
+
215
244
  printer.seen.add(h)
216
245
 
217
246
  printer.printt
218
247
  printer.print(kind_string)
219
- printer.print(' ')
248
+ printer.print(" ")
220
249
  printer.print_colored(name, Color::BLUE, Color::BOLD)
221
- printer.print_colored(' (', Color::LIGHT_BLACK)
250
+ printer.print_colored(" (", Color::LIGHT_BLACK)
222
251
  if range
223
252
  printer.print_object(range)
224
253
  elsif location
225
254
  printer.print_object(location)
226
255
  end
227
- printer.print_colored(')', Color::LIGHT_BLACK)
256
+ printer.print_colored(")", Color::LIGHT_BLACK)
228
257
  printer.printn
229
258
  unless children.empty?
230
259
  printer.indent
@@ -303,6 +332,7 @@ module Spoom
303
332
  sig { params(object: T.nilable(PrintableSymbol)).void }
304
333
  def print_object(object)
305
334
  return unless object
335
+
306
336
  object.accept_printer(self)
307
337
  end
308
338
 
@@ -315,6 +345,7 @@ module Spoom
315
345
  def clean_uri(uri)
316
346
  prefix = self.prefix
317
347
  return uri unless prefix
348
+
318
349
  uri.delete_prefix(prefix)
319
350
  end
320
351
 
@@ -322,7 +353,7 @@ module Spoom
322
353
  def print_list(objects)
323
354
  objects.each do |object|
324
355
  printt
325
- print "* "
356
+ print("* ")
326
357
  print_object(object)
327
358
  printn
328
359
  end
@@ -1,12 +1,12 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'open3'
5
- require 'json'
4
+ require "open3"
5
+ require "json"
6
6
 
7
- require_relative 'lsp/base'
8
- require_relative 'lsp/structures'
9
- require_relative 'lsp/errors'
7
+ require_relative "lsp/base"
8
+ require_relative "lsp/structures"
9
+ require_relative "lsp/errors"
10
10
 
11
11
  module Spoom
12
12
  module LSP
@@ -58,10 +58,10 @@ module Spoom
58
58
  json = JSON.parse(raw_string)
59
59
 
60
60
  # Handle error in the LSP protocol
61
- raise ResponseError.from_json(json['error']) if json['error']
61
+ raise ResponseError.from_json(json["error"]) if json["error"]
62
62
 
63
63
  # Handle typechecking errors
64
- raise Error::Diagnostics.from_json(json['params']) if json['method'] == "textDocument/publishDiagnostics"
64
+ raise Error::Diagnostics.from_json(json["params"]) if json["method"] == "textDocument/publishDiagnostics"
65
65
 
66
66
  json
67
67
  end
@@ -71,16 +71,17 @@ module Spoom
71
71
  sig { params(workspace_path: String).void }
72
72
  def open(workspace_path)
73
73
  raise Error::AlreadyOpen, "Error: CLI already opened" if @open
74
+
74
75
  send(Request.new(
75
76
  next_id,
76
- 'initialize',
77
+ "initialize",
77
78
  {
78
- 'rootPath' => workspace_path,
79
- 'rootUri' => "file://#{workspace_path}",
80
- 'capabilities' => {},
79
+ "rootPath" => workspace_path,
80
+ "rootUri" => "file://#{workspace_path}",
81
+ "capabilities" => {},
81
82
  },
82
83
  ))
83
- send(Notification.new('initialized', {}))
84
+ send(Notification.new("initialized", {}))
84
85
  @open = true
85
86
  end
86
87
 
@@ -88,133 +89,140 @@ module Spoom
88
89
  def hover(uri, line, column)
89
90
  json = send(Request.new(
90
91
  next_id,
91
- 'textDocument/hover',
92
+ "textDocument/hover",
92
93
  {
93
- 'textDocument' => {
94
- 'uri' => uri,
94
+ "textDocument" => {
95
+ "uri" => uri,
95
96
  },
96
- 'position' => {
97
- 'line' => line,
98
- 'character' => column,
97
+ "position" => {
98
+ "line" => line,
99
+ "character" => column,
99
100
  },
100
101
  }
101
102
  ))
102
103
 
103
- return nil unless json && json['result']
104
- Hover.from_json(json['result'])
104
+ return nil unless json && json["result"]
105
+
106
+ Hover.from_json(json["result"])
105
107
  end
106
108
 
107
109
  sig { params(uri: String, line: Integer, column: Integer).returns(T::Array[SignatureHelp]) }
108
110
  def signatures(uri, line, column)
109
111
  json = send(Request.new(
110
112
  next_id,
111
- 'textDocument/signatureHelp',
113
+ "textDocument/signatureHelp",
112
114
  {
113
- 'textDocument' => {
114
- 'uri' => uri,
115
+ "textDocument" => {
116
+ "uri" => uri,
115
117
  },
116
- 'position' => {
117
- 'line' => line,
118
- 'character' => column,
118
+ "position" => {
119
+ "line" => line,
120
+ "character" => column,
119
121
  },
120
122
  }
121
123
  ))
122
124
 
123
- return [] unless json && json['result'] && json['result']['signatures']
124
- json['result']['signatures'].map { |loc| SignatureHelp.from_json(loc) }
125
+ return [] unless json && json["result"] && json["result"]["signatures"]
126
+
127
+ json["result"]["signatures"].map { |loc| SignatureHelp.from_json(loc) }
125
128
  end
126
129
 
127
130
  sig { params(uri: String, line: Integer, column: Integer).returns(T::Array[Location]) }
128
131
  def definitions(uri, line, column)
129
132
  json = send(Request.new(
130
133
  next_id,
131
- 'textDocument/definition',
134
+ "textDocument/definition",
132
135
  {
133
- 'textDocument' => {
134
- 'uri' => uri,
136
+ "textDocument" => {
137
+ "uri" => uri,
135
138
  },
136
- 'position' => {
137
- 'line' => line,
138
- 'character' => column,
139
+ "position" => {
140
+ "line" => line,
141
+ "character" => column,
139
142
  },
140
143
  }
141
144
  ))
142
145
 
143
- return [] unless json && json['result']
144
- json['result'].map { |loc| Location.from_json(loc) }
146
+ return [] unless json && json["result"]
147
+
148
+ json["result"].map { |loc| Location.from_json(loc) }
145
149
  end
146
150
 
147
151
  sig { params(uri: String, line: Integer, column: Integer).returns(T::Array[Location]) }
148
152
  def type_definitions(uri, line, column)
149
153
  json = send(Request.new(
150
154
  next_id,
151
- 'textDocument/typeDefinition',
155
+ "textDocument/typeDefinition",
152
156
  {
153
- 'textDocument' => {
154
- 'uri' => uri,
157
+ "textDocument" => {
158
+ "uri" => uri,
155
159
  },
156
- 'position' => {
157
- 'line' => line,
158
- 'character' => column,
160
+ "position" => {
161
+ "line" => line,
162
+ "character" => column,
159
163
  },
160
164
  }
161
165
  ))
162
166
 
163
- return [] unless json && json['result']
164
- json['result'].map { |loc| Location.from_json(loc) }
167
+ return [] unless json && json["result"]
168
+
169
+ json["result"].map { |loc| Location.from_json(loc) }
165
170
  end
166
171
 
167
172
  sig { params(uri: String, line: Integer, column: Integer, include_decl: T::Boolean).returns(T::Array[Location]) }
168
173
  def references(uri, line, column, include_decl = true)
169
174
  json = send(Request.new(
170
175
  next_id,
171
- 'textDocument/references',
176
+ "textDocument/references",
172
177
  {
173
- 'textDocument' => {
174
- 'uri' => uri,
178
+ "textDocument" => {
179
+ "uri" => uri,
175
180
  },
176
- 'position' => {
177
- 'line' => line,
178
- 'character' => column,
181
+ "position" => {
182
+ "line" => line,
183
+ "character" => column,
179
184
  },
180
- 'context' => {
181
- 'includeDeclaration' => include_decl,
185
+ "context" => {
186
+ "includeDeclaration" => include_decl,
182
187
  },
183
188
  }
184
189
  ))
185
190
 
186
- return [] unless json && json['result']
187
- json['result'].map { |loc| Location.from_json(loc) }
191
+ return [] unless json && json["result"]
192
+
193
+ json["result"].map { |loc| Location.from_json(loc) }
188
194
  end
189
195
 
190
196
  sig { params(query: String).returns(T::Array[DocumentSymbol]) }
191
197
  def symbols(query)
192
198
  json = send(Request.new(
193
199
  next_id,
194
- 'workspace/symbol',
200
+ "workspace/symbol",
195
201
  {
196
- 'query' => query,
202
+ "query" => query,
197
203
  }
198
204
  ))
199
205
 
200
- return [] unless json && json['result']
201
- json['result'].map { |loc| DocumentSymbol.from_json(loc) }
206
+ return [] unless json && json["result"]
207
+
208
+ json["result"].map { |loc| DocumentSymbol.from_json(loc) }
202
209
  end
203
210
 
204
211
  sig { params(uri: String).returns(T::Array[DocumentSymbol]) }
205
212
  def document_symbols(uri)
206
213
  json = send(Request.new(
207
214
  next_id,
208
- 'textDocument/documentSymbol',
215
+ "textDocument/documentSymbol",
209
216
  {
210
- 'textDocument' => {
211
- 'uri' => uri,
217
+ "textDocument" => {
218
+ "uri" => uri,
212
219
  },
213
220
  }
214
221
  ))
215
222
 
216
- return [] unless json && json['result']
217
- json['result'].map { |loc| DocumentSymbol.from_json(loc) }
223
+ return [] unless json && json["result"]
224
+
225
+ json["result"].map { |loc| DocumentSymbol.from_json(loc) }
218
226
  end
219
227
 
220
228
  sig { void }
@@ -6,26 +6,28 @@ require_relative "sigils"
6
6
  module Spoom
7
7
  module Sorbet
8
8
  module MetricsParser
9
- extend T::Sig
10
-
11
9
  DEFAULT_PREFIX = "ruby_typer.unknown.."
12
10
 
13
- sig { params(path: String, prefix: String).returns(T::Hash[String, Integer]) }
14
- def self.parse_file(path, prefix = DEFAULT_PREFIX)
15
- parse_string(File.read(path), prefix)
16
- end
11
+ class << self
12
+ extend T::Sig
17
13
 
18
- sig { params(string: String, prefix: String).returns(T::Hash[String, Integer]) }
19
- def self.parse_string(string, prefix = DEFAULT_PREFIX)
20
- parse_hash(JSON.parse(string), prefix)
21
- end
14
+ sig { params(path: String, prefix: String).returns(T::Hash[String, Integer]) }
15
+ def parse_file(path, prefix = DEFAULT_PREFIX)
16
+ parse_string(File.read(path), prefix)
17
+ end
18
+
19
+ sig { params(string: String, prefix: String).returns(T::Hash[String, Integer]) }
20
+ def parse_string(string, prefix = DEFAULT_PREFIX)
21
+ parse_hash(JSON.parse(string), prefix)
22
+ end
22
23
 
23
- sig { params(obj: T::Hash[String, T.untyped], prefix: String).returns(T::Hash[String, Integer]) }
24
- def self.parse_hash(obj, prefix = DEFAULT_PREFIX)
25
- obj["metrics"].each_with_object(Hash.new(0)) do |metric, metrics|
26
- name = metric["name"]
27
- name = name.sub(prefix, '')
28
- metrics[name] = metric["value"] || 0
24
+ sig { params(obj: T::Hash[String, T.untyped], prefix: String).returns(T::Hash[String, Integer]) }
25
+ def parse_hash(obj, prefix = DEFAULT_PREFIX)
26
+ obj["metrics"].each_with_object(Hash.new(0)) do |metric, metrics|
27
+ name = metric["name"]
28
+ name = name.sub(prefix, "")
29
+ metrics[name] = metric["value"] || 0
30
+ end
29
31
  end
30
32
  end
31
33
  end
@@ -27,70 +27,75 @@ module Spoom
27
27
 
28
28
  SIGIL_REGEXP = T.let(/^#[\ t]*typed[\ t]*:[ \t]*(\w*)[ \t]*/.freeze, Regexp)
29
29
 
30
- # returns the full sigil comment string for the passed strictness
31
- sig { params(strictness: String).returns(String) }
32
- def self.sigil_string(strictness)
33
- "# typed: #{strictness}"
34
- end
30
+ class << self
31
+ extend T::Sig
35
32
 
36
- # returns true if the passed string is a valid strictness (else false)
37
- sig { params(strictness: String).returns(T::Boolean) }
38
- def self.valid_strictness?(strictness)
39
- VALID_STRICTNESS.include?(strictness.strip)
40
- end
33
+ # returns the full sigil comment string for the passed strictness
34
+ sig { params(strictness: String).returns(String) }
35
+ def sigil_string(strictness)
36
+ "# typed: #{strictness}"
37
+ end
41
38
 
42
- # returns the strictness of a sigil in the passed file content string (nil if no sigil)
43
- sig { params(content: String).returns(T.nilable(String)) }
44
- def self.strictness_in_content(content)
45
- SIGIL_REGEXP.match(content)&.[](1)
46
- end
39
+ # returns true if the passed string is a valid strictness (else false)
40
+ sig { params(strictness: String).returns(T::Boolean) }
41
+ def valid_strictness?(strictness)
42
+ VALID_STRICTNESS.include?(strictness.strip)
43
+ end
47
44
 
48
- # returns a string which is the passed content but with the sigil updated to a new strictness
49
- sig { params(content: String, new_strictness: String).returns(String) }
50
- def self.update_sigil(content, new_strictness)
51
- content.sub(SIGIL_REGEXP, sigil_string(new_strictness))
52
- end
45
+ # returns the strictness of a sigil in the passed file content string (nil if no sigil)
46
+ sig { params(content: String).returns(T.nilable(String)) }
47
+ def strictness_in_content(content)
48
+ SIGIL_REGEXP.match(content)&.[](1)
49
+ end
53
50
 
54
- # returns a string containing the strictness of a sigil in a file at the passed path
55
- # * returns nil if no sigil
56
- sig { params(path: T.any(String, Pathname)).returns(T.nilable(String)) }
57
- def self.file_strictness(path)
58
- return nil unless File.file?(path)
59
- content = File.read(path, encoding: Encoding::ASCII_8BIT)
60
- strictness_in_content(content)
61
- end
51
+ # returns a string which is the passed content but with the sigil updated to a new strictness
52
+ sig { params(content: String, new_strictness: String).returns(String) }
53
+ def update_sigil(content, new_strictness)
54
+ content.sub(SIGIL_REGEXP, sigil_string(new_strictness))
55
+ end
62
56
 
63
- # changes the sigil in the file at the passed path to the specified new strictness
64
- sig { params(path: T.any(String, Pathname), new_strictness: String).returns(T::Boolean) }
65
- def self.change_sigil_in_file(path, new_strictness)
66
- content = File.read(path, encoding: Encoding::ASCII_8BIT)
67
- new_content = update_sigil(content, new_strictness)
57
+ # returns a string containing the strictness of a sigil in a file at the passed path
58
+ # * returns nil if no sigil
59
+ sig { params(path: T.any(String, Pathname)).returns(T.nilable(String)) }
60
+ def file_strictness(path)
61
+ return nil unless File.file?(path)
68
62
 
69
- File.write(path, new_content, encoding: Encoding::ASCII_8BIT)
63
+ content = File.read(path, encoding: Encoding::ASCII_8BIT)
64
+ strictness_in_content(content)
65
+ end
70
66
 
71
- strictness_in_content(new_content) == new_strictness
72
- end
67
+ # changes the sigil in the file at the passed path to the specified new strictness
68
+ sig { params(path: T.any(String, Pathname), new_strictness: String).returns(T::Boolean) }
69
+ def change_sigil_in_file(path, new_strictness)
70
+ content = File.read(path, encoding: Encoding::ASCII_8BIT)
71
+ new_content = update_sigil(content, new_strictness)
73
72
 
74
- # changes the sigil to have a new strictness in a list of files
75
- sig { params(path_list: T::Array[String], new_strictness: String).returns(T::Array[String]) }
76
- def self.change_sigil_in_files(path_list, new_strictness)
77
- path_list.filter do |path|
78
- change_sigil_in_file(path, new_strictness)
73
+ File.write(path, new_content, encoding: Encoding::ASCII_8BIT)
74
+
75
+ strictness_in_content(new_content) == new_strictness
79
76
  end
80
- end
81
77
 
82
- # finds all files in the specified directory with the passed strictness
83
- sig do
84
- params(
85
- directory: T.any(String, Pathname),
86
- strictness: String,
87
- extension: String
88
- ).returns(T::Array[String])
89
- end
90
- def self.files_with_sigil_strictness(directory, strictness, extension: ".rb")
91
- paths = Dir.glob("#{File.expand_path(directory)}/**/*#{extension}").sort.uniq
92
- paths.filter do |path|
93
- file_strictness(path) == strictness
78
+ # changes the sigil to have a new strictness in a list of files
79
+ sig { params(path_list: T::Array[String], new_strictness: String).returns(T::Array[String]) }
80
+ def change_sigil_in_files(path_list, new_strictness)
81
+ path_list.filter do |path|
82
+ change_sigil_in_file(path, new_strictness)
83
+ end
84
+ end
85
+
86
+ # finds all files in the specified directory with the passed strictness
87
+ sig do
88
+ params(
89
+ directory: T.any(String, Pathname),
90
+ strictness: String,
91
+ extension: String
92
+ ).returns(T::Array[String])
93
+ end
94
+ def files_with_sigil_strictness(directory, strictness, extension: ".rb")
95
+ paths = Dir.glob("#{File.expand_path(directory)}/**/*#{extension}").sort.uniq
96
+ paths.filter do |path|
97
+ file_strictness(path) == strictness
98
+ end
94
99
  end
95
100
  end
96
101
  end