rubylens 0.1.0 → 0.2.0
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 +67 -39
- data/assets/runtime/report.js +1607 -251
- data/assets/shells/report.html +9 -7
- data/assets/shells/showcase.html +1 -0
- data/assets/styles/report.css +51 -25
- data/assets/styles/showcase.css +3 -0
- data/lib/rubylens/art_model_builder.rb +62 -10
- data/lib/rubylens/artifact_marker.rb +17 -0
- data/lib/rubylens/atomic_output.rb +27 -0
- data/lib/rubylens/cli.rb +53 -36
- data/lib/rubylens/clip/chrome_page.rb +183 -0
- data/lib/rubylens/clip/deadline_io.rb +28 -0
- data/lib/rubylens/clip/renderer.rb +113 -0
- data/lib/rubylens/clip/toolchain.rb +82 -0
- data/lib/rubylens/clip/web_socket_channel.rb +116 -0
- data/lib/rubylens/clip_generator.rb +102 -0
- data/lib/rubylens/default_output.rb +24 -0
- data/lib/rubylens/generator.rb +9 -10
- data/lib/rubylens/git_repository.rb +13 -7
- data/lib/rubylens/index/manifest.rb +8 -7
- data/lib/rubylens/index/rspec_extractor.rb +12 -10
- data/lib/rubylens/index/rubydex_adapter.rb +290 -140
- data/lib/rubylens/index/source_path.rb +12 -11
- data/lib/rubylens/model/dependency_aggregation.rb +9 -7
- data/lib/rubylens/morphology_classifier.rb +52 -12
- data/lib/rubylens/report_writer.rb +5 -20
- data/lib/rubylens/showcase_generator.rb +5 -9
- data/lib/rubylens/showcase_model.rb +12 -14
- data/lib/rubylens/showcase_writer.rb +0 -6
- data/lib/rubylens/version.rb +1 -1
- data/lib/rubylens.rb +7 -0
- metadata +10 -1
|
@@ -10,28 +10,24 @@ module RubyLens
|
|
|
10
10
|
module Index
|
|
11
11
|
class RubydexAdapter
|
|
12
12
|
TEST_SEGMENTS = %w[test tests spec specs feature features].freeze
|
|
13
|
+
CONSTANT_REFERENCE_LINK_LIMIT = 1_024
|
|
14
|
+
CONSTANT_REFERENCE_ATTRIBUTION_LIMIT = CONSTANT_REFERENCE_LINK_LIMIT * 2
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
# single pass so each definition's location crosses the Rubydex boundary once.
|
|
16
|
-
DefinitionSummary = Data.define(
|
|
17
|
-
:workspace_count,
|
|
18
|
-
:workspace_relatives,
|
|
19
|
-
:canonical_relatives,
|
|
20
|
-
:canonical_site_keys,
|
|
21
|
-
:package_site_keys,
|
|
22
|
-
)
|
|
16
|
+
ReferenceSummary = Data.define(:inbound_counts, :links)
|
|
23
17
|
|
|
24
18
|
def initialize(manifest)
|
|
25
19
|
@manifest = manifest
|
|
26
20
|
@source_path_cache = {}
|
|
27
21
|
@workspace_location_cache = {}
|
|
22
|
+
@definition_scope_by_uri = {}
|
|
23
|
+
@package_index_by_uri = {}
|
|
28
24
|
@indexed_package_document_paths = Set.new
|
|
29
25
|
end
|
|
30
26
|
|
|
31
27
|
def index
|
|
32
28
|
graph = Rubydex::Graph.new(workspace_path: @manifest.root.to_s)
|
|
33
29
|
index_errors = graph.index_all(@manifest.files)
|
|
34
|
-
@indexed_package_document_paths =
|
|
30
|
+
@indexed_package_document_paths, documents_with_paths = resolve_documents(graph)
|
|
35
31
|
graph.resolve
|
|
36
32
|
integrity_failures = graph.check_integrity
|
|
37
33
|
collected = collect_declarations(graph.declarations)
|
|
@@ -39,22 +35,31 @@ module RubyLens
|
|
|
39
35
|
graph: graph,
|
|
40
36
|
manifest: @manifest,
|
|
41
37
|
package_document_paths: @indexed_package_document_paths,
|
|
38
|
+
documents_with_paths: documents_with_paths,
|
|
42
39
|
).call
|
|
43
|
-
workspace = workspace_namespaces(
|
|
44
|
-
|
|
40
|
+
workspace = workspace_namespaces(
|
|
41
|
+
collected.fetch(:workspace_records),
|
|
42
|
+
rspec.groups,
|
|
43
|
+
collected.fetch(:workspace_definition_ranges),
|
|
44
|
+
)
|
|
45
|
+
reference_summary = workspace_reference_summary(
|
|
46
|
+
graph,
|
|
47
|
+
workspace,
|
|
48
|
+
collected.fetch(:dependency_ordinal_by_name),
|
|
49
|
+
)
|
|
45
50
|
category_stats = collected.fetch(:category_stats)
|
|
46
51
|
category_stats.fetch("tests")[0] += rspec.groups.length
|
|
47
52
|
category_stats.fetch("tests")[2] += rspec.method_count
|
|
48
53
|
|
|
49
54
|
{
|
|
50
|
-
"schema" => "rubylens.snapshot.
|
|
55
|
+
"schema" => "rubylens.snapshot.v9",
|
|
51
56
|
"project_name" => project_name,
|
|
52
|
-
"components" => workspace.fetch(:component_counts),
|
|
53
57
|
"namespace_names" => workspace.fetch(:namespace_names),
|
|
54
|
-
"namespaces" => build_workspace_rows(workspace,
|
|
58
|
+
"namespaces" => build_workspace_rows(workspace, reference_summary.inbound_counts),
|
|
59
|
+
"constant_reference_links" => reference_summary.links,
|
|
55
60
|
"category_stats" => category_stats,
|
|
56
|
-
"dependency_signal_maxima" => collected.fetch(:
|
|
57
|
-
"packages" => build_package_rows(collected.fetch(:
|
|
61
|
+
"dependency_signal_maxima" => collected.fetch(:dependency_signal_maxima),
|
|
62
|
+
"packages" => build_package_rows(collected.fetch(:dependency_packages)),
|
|
58
63
|
"dependency_systems" => build_dependency_system_rows,
|
|
59
64
|
"dependency_warnings" => @manifest.respond_to?(:dependency_warnings) ? @manifest.dependency_warnings : [],
|
|
60
65
|
"warning_counts" => {
|
|
@@ -71,86 +76,138 @@ module RubyLens
|
|
|
71
76
|
records = []
|
|
72
77
|
category_stats = { "core" => Array.new(4, 0), "tests" => Array.new(4, 0) }
|
|
73
78
|
aggregation = Model::DependencyAggregation.new(package_count: @manifest.packages.length)
|
|
79
|
+
workspace_definition_ranges = Hash.new { |hash, uri| hash[uri] = [] }
|
|
80
|
+
dependency_positions = {}
|
|
74
81
|
|
|
75
82
|
declarations.each do |declaration|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
name = declaration.name
|
|
84
|
+
next unless model_eligible_declaration?(declaration, name)
|
|
85
|
+
|
|
86
|
+
is_namespace = declaration.is_a?(Rubydex::Namespace)
|
|
87
|
+
construct_index = ruby_construct_index(declaration)
|
|
88
|
+
workspace_count, tests_only, canonical_site_keys, canonical_scope, package_site_keys =
|
|
89
|
+
summarize_definitions(declaration, is_namespace)
|
|
90
|
+
reference_count = safe_length(declaration, :references) if package_site_keys
|
|
91
|
+
|
|
92
|
+
if canonical_site_keys
|
|
93
|
+
canonical_site_keys = canonical_site_keys.uniq if canonical_site_keys.length > 1
|
|
94
|
+
ordinal = records.length
|
|
95
|
+
canonical_site_keys.each do |uri, start_line, start_column, end_line, end_column|
|
|
96
|
+
workspace_definition_ranges[uri] << [start_line, start_column, end_line, end_column, ordinal]
|
|
97
|
+
end
|
|
98
|
+
records << [declaration, canonical_site_keys.length, canonical_scope, name]
|
|
86
99
|
end
|
|
87
|
-
|
|
88
|
-
|
|
100
|
+
if construct_index && workspace_count.positive?
|
|
101
|
+
category_stats.fetch(tests_only ? "tests" : "core")[construct_index] += 1
|
|
102
|
+
end
|
|
103
|
+
collect_dependency_declaration(
|
|
104
|
+
aggregation,
|
|
105
|
+
dependency_positions,
|
|
106
|
+
declaration,
|
|
107
|
+
name,
|
|
108
|
+
is_namespace,
|
|
109
|
+
construct_index,
|
|
110
|
+
package_site_keys,
|
|
111
|
+
reference_count,
|
|
112
|
+
)
|
|
89
113
|
end
|
|
90
114
|
|
|
91
|
-
|
|
115
|
+
dependency_packages = aggregation.packages
|
|
116
|
+
dependency_offsets = []
|
|
117
|
+
offset = 0
|
|
118
|
+
dependency_packages.each do |package|
|
|
119
|
+
dependency_offsets << offset
|
|
120
|
+
offset += package.fetch(:declarations).length
|
|
121
|
+
end
|
|
122
|
+
dependency_ordinal_by_name = dependency_positions.transform_values! do |position|
|
|
123
|
+
package_index, local_index = position
|
|
124
|
+
dependency_offsets.fetch(package_index) + local_index
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
{
|
|
128
|
+
workspace_records: records,
|
|
129
|
+
workspace_definition_ranges:,
|
|
130
|
+
category_stats:,
|
|
131
|
+
dependency_packages:,
|
|
132
|
+
dependency_signal_maxima: aggregation.signal_maxima,
|
|
133
|
+
dependency_ordinal_by_name: dependency_ordinal_by_name.freeze,
|
|
134
|
+
}
|
|
92
135
|
end
|
|
93
136
|
|
|
94
|
-
|
|
95
|
-
|
|
137
|
+
# One pass over a declaration's definitions gathers everything the
|
|
138
|
+
# collectors need, so each definition's location and its per-URI
|
|
139
|
+
# workspace/scope/package answers cross the Rubydex boundary once.
|
|
140
|
+
# Canonical site keys and package site keys stay nil until a definition
|
|
141
|
+
# actually contributes to them, since most declarations produce neither.
|
|
142
|
+
def summarize_definitions(declaration, is_namespace)
|
|
96
143
|
workspace_count = 0
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
144
|
+
tests_seen = false
|
|
145
|
+
core_seen = false
|
|
146
|
+
canonical_tests_seen = false
|
|
147
|
+
canonical_core_seen = false
|
|
148
|
+
canonical_site_keys = nil
|
|
149
|
+
package_site_keys = nil
|
|
101
150
|
|
|
102
151
|
declaration.definitions.each do |definition|
|
|
103
152
|
location = definition.location
|
|
153
|
+
site_key = nil
|
|
104
154
|
if workspace_location?(location)
|
|
105
155
|
workspace_count += 1
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
156
|
+
scope = definition_scope(location.uri)
|
|
157
|
+
if scope == 1
|
|
158
|
+
tests_seen = true
|
|
159
|
+
elsif scope
|
|
160
|
+
core_seen = true
|
|
161
|
+
end
|
|
162
|
+
if is_namespace && canonical_namespace_definition?(declaration, definition)
|
|
163
|
+
(canonical_site_keys ||= []) << (site_key = location.comparable_values)
|
|
164
|
+
if scope == 1
|
|
165
|
+
canonical_tests_seen = true
|
|
166
|
+
elsif scope
|
|
167
|
+
canonical_core_seen = true
|
|
168
|
+
end
|
|
111
169
|
end
|
|
112
170
|
end
|
|
113
171
|
package_index = package_index_for_location(location)
|
|
114
|
-
|
|
172
|
+
if package_index
|
|
173
|
+
((package_site_keys ||= {})[package_index] ||= []) << (site_key || location.comparable_values)
|
|
174
|
+
end
|
|
115
175
|
end
|
|
116
176
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
workspace_relatives:,
|
|
120
|
-
canonical_relatives:,
|
|
121
|
-
canonical_site_keys:,
|
|
122
|
-
package_site_keys:,
|
|
123
|
-
)
|
|
177
|
+
canonical_scope = canonical_tests_seen ? (canonical_core_seen ? 2 : 1) : 0
|
|
178
|
+
[workspace_count, tests_seen && !core_seen, canonical_site_keys, canonical_scope, package_site_keys]
|
|
124
179
|
end
|
|
125
180
|
|
|
126
|
-
def workspace_namespaces(records, rspec_groups)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
181
|
+
def workspace_namespaces(records, rspec_groups, definition_ranges = {})
|
|
182
|
+
namespace_names = []
|
|
183
|
+
ordinal_by_name = {}
|
|
184
|
+
records.each_with_index do |record, index|
|
|
185
|
+
name = record.fetch(3)
|
|
186
|
+
namespace_names << name
|
|
187
|
+
ordinal_by_name[name] = index
|
|
188
|
+
end
|
|
132
189
|
{
|
|
133
190
|
records: records,
|
|
191
|
+
definition_ranges: definition_ranges,
|
|
134
192
|
rspec_groups: rspec_groups,
|
|
135
|
-
namespace_names:
|
|
193
|
+
namespace_names: namespace_names + rspec_groups,
|
|
136
194
|
ordinal_by_name: ordinal_by_name,
|
|
137
|
-
component_ids: component_ids,
|
|
138
|
-
components: components,
|
|
139
|
-
component_counts: all_components.tally.sort_by { |name, _count| component_ids.fetch(name) }.map(&:last),
|
|
140
195
|
}
|
|
141
196
|
end
|
|
142
197
|
|
|
143
198
|
def build_workspace_rows(workspace, inbound_references)
|
|
144
|
-
|
|
199
|
+
ordinal_by_name = workspace.fetch(:ordinal_by_name)
|
|
200
|
+
rows = workspace.fetch(:records).each_with_index.map do |record, index|
|
|
201
|
+
declaration, sites, scope, name = record
|
|
145
202
|
descendants = declaration.descendants.count do |descendant|
|
|
146
|
-
|
|
203
|
+
descendant_name = descendant.name
|
|
204
|
+
descendant_name != name && ordinal_by_name.key?(descendant_name)
|
|
147
205
|
end
|
|
148
206
|
member_count, ruby_counts, instance_variable_count = member_statistics(
|
|
149
207
|
declaration,
|
|
150
208
|
count_instance_variables: declaration.is_a?(Rubydex::Class) && scope != 1,
|
|
151
209
|
)
|
|
152
210
|
[
|
|
153
|
-
workspace.fetch(:component_ids).fetch(component),
|
|
154
211
|
declaration.is_a?(Rubydex::Class) ? 0 : 1,
|
|
155
212
|
scope,
|
|
156
213
|
[declaration.ancestors.count - 1, 0].max,
|
|
@@ -163,27 +220,20 @@ module RubyLens
|
|
|
163
220
|
instance_variable_count,
|
|
164
221
|
]
|
|
165
222
|
end
|
|
166
|
-
rows.concat(workspace.fetch(:rspec_groups).map {
|
|
223
|
+
rows.concat(workspace.fetch(:rspec_groups).map { build_rspec_row })
|
|
167
224
|
end
|
|
168
225
|
|
|
169
|
-
def build_rspec_row
|
|
170
|
-
[
|
|
171
|
-
workspace.fetch(:component_ids).fetch(group.component),
|
|
172
|
-
0,
|
|
173
|
-
1,
|
|
174
|
-
*Array.new(11, 0),
|
|
175
|
-
]
|
|
226
|
+
def build_rspec_row
|
|
227
|
+
[0, 1, *Array.new(11, 0)]
|
|
176
228
|
end
|
|
177
229
|
|
|
178
|
-
def build_package_rows(
|
|
179
|
-
aggregates = aggregation.packages
|
|
230
|
+
def build_package_rows(aggregates)
|
|
180
231
|
@manifest.packages.each_with_index.map do |package, index|
|
|
181
232
|
aggregate = aggregates.fetch(index)
|
|
182
233
|
{
|
|
183
234
|
"name" => package.name,
|
|
184
235
|
"role" => package.role == "direct" ? 0 : 1,
|
|
185
236
|
"location" => package.location == "workspace" ? 0 : 1,
|
|
186
|
-
"declaration_count" => aggregate.fetch(:declaration_count),
|
|
187
237
|
"ruby_counts" => aggregate.fetch(:ruby_counts),
|
|
188
238
|
"declarations" => aggregate.fetch(:declarations),
|
|
189
239
|
}
|
|
@@ -202,62 +252,173 @@ module RubyLens
|
|
|
202
252
|
end
|
|
203
253
|
end
|
|
204
254
|
|
|
205
|
-
def collect_dependency_declaration(
|
|
206
|
-
|
|
207
|
-
|
|
255
|
+
def collect_dependency_declaration(
|
|
256
|
+
aggregation,
|
|
257
|
+
positions,
|
|
258
|
+
declaration,
|
|
259
|
+
name,
|
|
260
|
+
is_namespace,
|
|
261
|
+
construct_index,
|
|
262
|
+
package_site_keys,
|
|
263
|
+
reference_count
|
|
264
|
+
)
|
|
265
|
+
return unless package_site_keys
|
|
266
|
+
|
|
267
|
+
package_index = nil
|
|
268
|
+
site_keys = nil
|
|
269
|
+
package_site_keys.each do |index, keys|
|
|
270
|
+
if site_keys.nil? || keys.length > site_keys.length ||
|
|
271
|
+
(keys.length == site_keys.length && index < package_index)
|
|
272
|
+
package_index = index
|
|
273
|
+
site_keys = keys
|
|
274
|
+
end
|
|
208
275
|
end
|
|
209
|
-
return unless package_index
|
|
210
276
|
|
|
211
|
-
sites = site_keys.uniq.length
|
|
277
|
+
sites = site_keys.length > 1 ? site_keys.uniq.length : site_keys.length
|
|
212
278
|
row = [
|
|
213
279
|
namespace_kind(declaration),
|
|
214
|
-
|
|
280
|
+
is_namespace ? [declaration.ancestors.count - 1, 0].max : 0,
|
|
215
281
|
sites,
|
|
216
282
|
[sites - 1, 0].max,
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
]
|
|
221
|
-
aggregation.add(
|
|
283
|
+
is_namespace ? [declaration.descendants.count - 1, 0].max : 0,
|
|
284
|
+
reference_count,
|
|
285
|
+
is_namespace ? safe_length(declaration, :members) : 0,
|
|
286
|
+
].freeze
|
|
287
|
+
local_index = aggregation.add(
|
|
222
288
|
package_index:,
|
|
223
289
|
row:,
|
|
224
|
-
construct_index
|
|
290
|
+
construct_index:,
|
|
225
291
|
)
|
|
292
|
+
if reference_count.positive? && constant_reference_target?(declaration, is_namespace)
|
|
293
|
+
positions[name] = [package_index, local_index]
|
|
294
|
+
end
|
|
226
295
|
end
|
|
227
296
|
|
|
228
|
-
def
|
|
229
|
-
|
|
297
|
+
def workspace_reference_summary(graph, workspace, dependency_ordinal_by_name = {})
|
|
298
|
+
ordinal_by_name = workspace.fetch(:ordinal_by_name)
|
|
299
|
+
namespace_count = workspace.fetch(:namespace_names, ordinal_by_name).length
|
|
300
|
+
workspace_ranges = workspace.fetch(:definition_ranges, {})
|
|
301
|
+
inbound_counts = Hash.new(0)
|
|
302
|
+
workspace.fetch(:records).each_with_index do |record, index|
|
|
303
|
+
record.fetch(0).references.each do |reference|
|
|
304
|
+
location = begin
|
|
305
|
+
reference.location
|
|
306
|
+
rescue StandardError
|
|
307
|
+
next
|
|
308
|
+
end
|
|
309
|
+
inbound_counts[index] += 1 if workspace_location?(location)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
links = []
|
|
313
|
+
retained_links = Set.new
|
|
314
|
+
attribution_count = 0
|
|
315
|
+
|
|
316
|
+
graph.constant_references.each do |reference|
|
|
317
|
+
break if links.length >= CONSTANT_REFERENCE_LINK_LIMIT ||
|
|
318
|
+
attribution_count >= CONSTANT_REFERENCE_ATTRIBUTION_LIMIT
|
|
230
319
|
next unless reference.is_a?(Rubydex::ResolvedConstantReference)
|
|
231
320
|
|
|
232
|
-
|
|
233
|
-
next unless
|
|
321
|
+
referenced_name = resolved_constant_reference_name(reference)
|
|
322
|
+
next unless referenced_name
|
|
323
|
+
referenced_ordinal = ordinal_by_name[referenced_name]
|
|
324
|
+
dependency_ordinal = dependency_ordinal_by_name[referenced_name] unless referenced_ordinal
|
|
325
|
+
next unless referenced_ordinal || dependency_ordinal
|
|
326
|
+
|
|
327
|
+
location = begin
|
|
328
|
+
reference.location
|
|
329
|
+
rescue StandardError
|
|
330
|
+
next
|
|
331
|
+
end
|
|
332
|
+
next unless workspace_location?(location)
|
|
333
|
+
|
|
334
|
+
attribution_count += 1
|
|
335
|
+
|
|
336
|
+
referenced_endpoint = referenced_ordinal || namespace_count + dependency_ordinal
|
|
337
|
+
|
|
338
|
+
location_values = begin
|
|
339
|
+
location.comparable_values
|
|
340
|
+
rescue StandardError
|
|
341
|
+
next
|
|
342
|
+
end
|
|
343
|
+
referring_ordinal = enclosing_definition_ordinal(location_values, workspace_ranges)
|
|
344
|
+
next unless referring_ordinal
|
|
345
|
+
next if referenced_endpoint == referring_ordinal
|
|
346
|
+
|
|
347
|
+
link = [referring_ordinal, referenced_endpoint]
|
|
348
|
+
next unless retained_links.add?(link)
|
|
234
349
|
|
|
235
|
-
|
|
236
|
-
rescue StandardError
|
|
237
|
-
next
|
|
350
|
+
links << link
|
|
238
351
|
end
|
|
352
|
+
|
|
353
|
+
ReferenceSummary.new(inbound_counts.freeze, links.freeze)
|
|
239
354
|
end
|
|
240
355
|
|
|
241
|
-
def
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
356
|
+
def resolved_constant_reference_name(reference)
|
|
357
|
+
reference.declaration.name
|
|
358
|
+
rescue StandardError
|
|
359
|
+
nil
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def enclosing_definition_ordinal(location_values, ranges_by_uri)
|
|
363
|
+
uri, start_line, start_column, end_line, end_column = location_values
|
|
364
|
+
best = nil
|
|
365
|
+
ambiguous = false
|
|
366
|
+
ranges_by_uri.fetch(uri, []).each do |range|
|
|
367
|
+
range_start_line, range_start_column, range_end_line, range_end_column, ordinal = range
|
|
368
|
+
next if position_compare(range_start_line, range_start_column, start_line, start_column).positive?
|
|
369
|
+
next if position_compare(range_end_line, range_end_column, end_line, end_column).negative?
|
|
370
|
+
|
|
371
|
+
start_comparison = best ? position_compare(range_start_line, range_start_column, best[0], best[1]) : 1
|
|
372
|
+
end_comparison = best && start_comparison.zero? ? position_compare(range_end_line, range_end_column, best[2], best[3]) : 0
|
|
373
|
+
if start_comparison.positive? || (start_comparison.zero? && end_comparison.negative?)
|
|
374
|
+
best = range
|
|
375
|
+
ambiguous = false
|
|
376
|
+
elsif start_comparison.zero? && end_comparison.zero? && ordinal != best[4]
|
|
377
|
+
ambiguous = true
|
|
378
|
+
end
|
|
379
|
+
end
|
|
245
380
|
|
|
246
|
-
|
|
381
|
+
ambiguous ? nil : best&.fetch(4)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def position_compare(left_line, left_column, right_line, right_column)
|
|
385
|
+
line_comparison = left_line <=> right_line
|
|
386
|
+
line_comparison.zero? ? left_column <=> right_column : line_comparison
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def package_index_for_location(location)
|
|
390
|
+
uri = location.uri
|
|
391
|
+
@package_index_by_uri.fetch(uri) do
|
|
392
|
+
path = source_path(uri)
|
|
393
|
+
@package_index_by_uri[uri] =
|
|
394
|
+
if path && @indexed_package_document_paths.include?(path)
|
|
395
|
+
@manifest.package_index_for(path)
|
|
396
|
+
end
|
|
397
|
+
end
|
|
247
398
|
end
|
|
248
399
|
|
|
249
|
-
|
|
400
|
+
# One sweep over the indexed documents resolves each URI once, yielding
|
|
401
|
+
# both the package-audit set and the [document, path] pairs the RSpec
|
|
402
|
+
# extractor reuses without re-enumerating documents or re-parsing URIs.
|
|
403
|
+
def resolve_documents(graph)
|
|
250
404
|
audited = @manifest.packages.flat_map(&:files).to_set
|
|
251
|
-
|
|
405
|
+
package_paths = Set.new
|
|
406
|
+
documents_with_paths = []
|
|
407
|
+
graph.documents.each do |document|
|
|
252
408
|
path = source_path(document.uri)
|
|
253
|
-
|
|
409
|
+
next unless path
|
|
410
|
+
|
|
411
|
+
documents_with_paths << [document, path]
|
|
412
|
+
package_paths << path if audited.include?(path)
|
|
254
413
|
end
|
|
414
|
+
[package_paths, documents_with_paths]
|
|
255
415
|
end
|
|
256
416
|
|
|
257
417
|
# One sweep over a namespace's direct and singleton members yields the
|
|
258
418
|
# workspace member count, the method/constant construct counts, and the
|
|
259
419
|
# instance-variable count that previously took three walks each re-reading
|
|
260
|
-
# every member's definitions.
|
|
420
|
+
# every member's definitions. Membership comes from each member's own
|
|
421
|
+
# definition locations; the per-URI caches keep that scan cheap.
|
|
261
422
|
def member_statistics(declaration, count_instance_variables:)
|
|
262
423
|
member_count = 0
|
|
263
424
|
instance_variable_count = 0
|
|
@@ -266,7 +427,10 @@ module RubyLens
|
|
|
266
427
|
ruby_counts[own_construct] += 1 if own_construct == 0 || own_construct == 1
|
|
267
428
|
|
|
268
429
|
seen_names = Set.new
|
|
269
|
-
|
|
430
|
+
member_groups = [[declaration.members, true]]
|
|
431
|
+
singleton = declaration.singleton_class
|
|
432
|
+
member_groups << [singleton.members, false] if singleton
|
|
433
|
+
member_groups.each do |members, direct|
|
|
270
434
|
members.each do |member|
|
|
271
435
|
next unless seen_names.add?(member.name)
|
|
272
436
|
next unless member.definitions.any? { |definition| workspace_location?(definition.location) }
|
|
@@ -279,60 +443,46 @@ module RubyLens
|
|
|
279
443
|
end
|
|
280
444
|
end
|
|
281
445
|
end
|
|
282
|
-
walk.call(declaration.members, true)
|
|
283
|
-
walk.call(declaration.singleton_class.members, false) if declaration.singleton_class
|
|
284
446
|
[member_count, ruby_counts, instance_variable_count]
|
|
285
447
|
rescue StandardError
|
|
286
448
|
[member_count, ruby_counts, instance_variable_count]
|
|
287
449
|
end
|
|
288
450
|
|
|
289
|
-
def collect_category_stat(stats, declaration, summary)
|
|
290
|
-
construct_index = ruby_construct_index(declaration)
|
|
291
|
-
return unless construct_index
|
|
292
|
-
return if summary.workspace_count.zero?
|
|
293
|
-
|
|
294
|
-
category = scope_from(summary.workspace_relatives) == 1 ? "tests" : "core"
|
|
295
|
-
stats.fetch(category)[construct_index] += 1
|
|
296
|
-
end
|
|
297
|
-
|
|
298
451
|
def workspace_location?(location)
|
|
299
452
|
uri = location.uri
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
453
|
+
@workspace_location_cache.fetch(uri) do
|
|
454
|
+
path = source_path(uri)
|
|
455
|
+
@workspace_location_cache[uri] = path ? @manifest.workspace_path?(path) : false
|
|
456
|
+
end
|
|
304
457
|
rescue StandardError
|
|
305
458
|
false
|
|
306
459
|
end
|
|
307
460
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
def site_key(location)
|
|
321
|
-
location.comparable_values
|
|
461
|
+
# 1 for a test-tree file, 0 for any other workspace file, nil when the
|
|
462
|
+
# URI has no workspace-relative path to classify.
|
|
463
|
+
def definition_scope(uri)
|
|
464
|
+
@definition_scope_by_uri.fetch(uri) do
|
|
465
|
+
relative = @manifest.relative_workspace_path(source_path(uri))
|
|
466
|
+
@definition_scope_by_uri[uri] =
|
|
467
|
+
if relative
|
|
468
|
+
relative.split(File::SEPARATOR).any? { |segment| TEST_SEGMENTS.include?(segment) } ? 1 : 0
|
|
469
|
+
end
|
|
470
|
+
end
|
|
322
471
|
end
|
|
323
472
|
|
|
324
|
-
def
|
|
325
|
-
|
|
473
|
+
def constant_reference_target?(declaration, is_namespace)
|
|
474
|
+
is_namespace ||
|
|
475
|
+
declaration.is_a?(Rubydex::Constant) ||
|
|
476
|
+
declaration.is_a?(Rubydex::ConstantAlias)
|
|
326
477
|
end
|
|
327
478
|
|
|
328
|
-
def model_eligible_declaration?(declaration)
|
|
329
|
-
name = declaration.name
|
|
479
|
+
def model_eligible_declaration?(declaration, name = declaration.name)
|
|
330
480
|
return false if name.nil? || name.empty? || name.include?("<anonymous>")
|
|
331
481
|
return false if declaration.is_a?(Rubydex::Todo)
|
|
332
482
|
return true unless declaration.is_a?(Rubydex::SingletonClass)
|
|
333
483
|
|
|
334
|
-
|
|
335
|
-
|
|
484
|
+
attached = declaration.attached_class
|
|
485
|
+
!attached.is_a?(Rubydex::SingletonClass) && !attached.is_a?(Rubydex::Todo)
|
|
336
486
|
end
|
|
337
487
|
|
|
338
488
|
def canonical_namespace_definition?(declaration, definition)
|
|
@@ -359,9 +509,9 @@ module RubyLens
|
|
|
359
509
|
end
|
|
360
510
|
|
|
361
511
|
def source_path(uri_string)
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
512
|
+
@source_path_cache.fetch(uri_string) do
|
|
513
|
+
@source_path_cache[uri_string] = SourcePath.from_file_uri(uri_string)
|
|
514
|
+
end
|
|
365
515
|
end
|
|
366
516
|
|
|
367
517
|
def safe_length(object, method)
|
|
@@ -6,13 +6,24 @@ module RubyLens
|
|
|
6
6
|
module Index
|
|
7
7
|
# Maps Rubydex document and location URIs back to workspace source paths.
|
|
8
8
|
module SourcePath
|
|
9
|
-
|
|
9
|
+
# The canonical shape Rubydex emits: an empty authority and a
|
|
10
|
+
# percent-encoded absolute path built from RFC 2396 path characters.
|
|
11
|
+
# Anything else falls back to full URI parsing below.
|
|
12
|
+
ENCODED_FILE_URI = %r{\Afile://(/(?:[A-Za-z0-9\-_.!~*'()/:@&=+$,;]|%[0-9A-Fa-f]{2})*)\z}
|
|
10
13
|
|
|
11
14
|
module_function
|
|
12
15
|
|
|
13
16
|
# Rubydex 0.2.9 percent-encodes file URIs and Location#to_file_path
|
|
14
17
|
# returns the path still encoded, so decode the URI ourselves.
|
|
15
18
|
def from_file_uri(uri_string)
|
|
19
|
+
match = ENCODED_FILE_URI.match(uri_string)
|
|
20
|
+
if match
|
|
21
|
+
path = match[1]
|
|
22
|
+
path = URI::RFC2396_PARSER.unescape(path) if path.include?("%")
|
|
23
|
+
path = path.delete_prefix("/") if Gem.win_platform?
|
|
24
|
+
return path
|
|
25
|
+
end
|
|
26
|
+
|
|
16
27
|
uri = URI.parse(uri_string)
|
|
17
28
|
return unless uri.scheme == "file"
|
|
18
29
|
return if uri.host && !uri.host.empty? && uri.host != "localhost"
|
|
@@ -24,16 +35,6 @@ module RubyLens
|
|
|
24
35
|
rescue URI::InvalidURIError
|
|
25
36
|
nil
|
|
26
37
|
end
|
|
27
|
-
|
|
28
|
-
def component_for(relative)
|
|
29
|
-
segments = relative.split(File::SEPARATOR)
|
|
30
|
-
first = segments.first || "root"
|
|
31
|
-
if COMPONENT_ROOTS.include?(first)
|
|
32
|
-
"#{first}/#{segments[1] || "root"}"
|
|
33
|
-
else
|
|
34
|
-
first
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
38
|
end
|
|
38
39
|
end
|
|
39
40
|
end
|