rigortype 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -2
  3. data/data/builtins/ruby_core/array.yml +6 -6
  4. data/data/builtins/ruby_core/hash.yml +1 -1
  5. data/data/builtins/ruby_core/io.yml +3 -3
  6. data/data/builtins/ruby_core/numeric.yml +1 -1
  7. data/data/builtins/ruby_core/pathname.yml +100 -100
  8. data/data/builtins/ruby_core/proc.yml +1 -1
  9. data/data/builtins/ruby_core/range.yml +6 -4
  10. data/data/builtins/ruby_core/string.yml +15 -10
  11. data/data/builtins/ruby_core/time.yml +3 -3
  12. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +116 -0
  13. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +123 -0
  14. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +118 -0
  15. data/lib/rigor/analysis/check_rules.rb +346 -18
  16. data/lib/rigor/analysis/rule_catalog.rb +343 -0
  17. data/lib/rigor/analysis/runner.rb +90 -6
  18. data/lib/rigor/builtins/regex_refinement.rb +104 -0
  19. data/lib/rigor/cli/diff_command.rb +169 -0
  20. data/lib/rigor/cli/explain_command.rb +129 -0
  21. data/lib/rigor/cli/type_of_command.rb +3 -3
  22. data/lib/rigor/cli/type_scan_command.rb +4 -4
  23. data/lib/rigor/cli.rb +29 -5
  24. data/lib/rigor/configuration/severity_profile.rb +18 -3
  25. data/lib/rigor/configuration.rb +186 -13
  26. data/lib/rigor/environment.rb +12 -4
  27. data/lib/rigor/inference/expression_typer.rb +3 -1
  28. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +31 -0
  29. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +43 -2
  30. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +104 -12
  31. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +68 -2
  32. data/lib/rigor/inference/method_dispatcher.rb +50 -1
  33. data/lib/rigor/inference/narrowing.rb +150 -6
  34. data/lib/rigor/inference/scope_indexer.rb +220 -17
  35. data/lib/rigor/inference/statement_evaluator.rb +29 -0
  36. data/lib/rigor/plugin/base.rb +43 -0
  37. data/lib/rigor/plugin/fact_store.rb +92 -0
  38. data/lib/rigor/plugin/io_boundary.rb +92 -19
  39. data/lib/rigor/plugin/load_error.rb +14 -2
  40. data/lib/rigor/plugin/loader.rb +116 -0
  41. data/lib/rigor/plugin/manifest.rb +75 -6
  42. data/lib/rigor/plugin/services.rb +14 -2
  43. data/lib/rigor/plugin/trust_policy.rb +30 -7
  44. data/lib/rigor/plugin.rb +1 -0
  45. data/lib/rigor/scope.rb +30 -5
  46. data/lib/rigor/trinary.rb +1 -1
  47. data/lib/rigor/type/integer_range.rb +6 -2
  48. data/lib/rigor/version.rb +1 -1
  49. data/sig/rigor/environment.rbs +3 -2
  50. data/sig/rigor/scope.rbs +3 -0
  51. data/sig/rigor.rbs +8 -2
  52. metadata +9 -1
@@ -11,12 +11,24 @@ module Rigor
11
11
  # to be isolated at the analyzer boundary; this class is the
12
12
  # carrier for that contract on the loading side.
13
13
  class LoadError < StandardError
14
- attr_reader :plugin_ref, :cause_class
14
+ attr_reader :plugin_ref, :cause_class, :reason
15
15
 
16
- def initialize(message, plugin_ref:, cause: nil)
16
+ # ADR-9 slice 5 introduces two new reason codes alongside the
17
+ # implicit "load failure" used for require / configuration /
18
+ # init failures:
19
+ #
20
+ # - `:missing-producer` — a non-optional `manifest(consumes:)`
21
+ # entry names a `(plugin_id, name)` no loaded plugin
22
+ # produces.
23
+ # - `:dependency-cycle` — the consumes graph forms a cycle.
24
+ #
25
+ # Older callers omit `reason:` and the field defaults to nil
26
+ # (the legacy "load failure" envelope).
27
+ def initialize(message, plugin_ref:, cause: nil, reason: nil)
17
28
  super(message)
18
29
  @plugin_ref = plugin_ref
19
30
  @cause_class = cause&.class
31
+ @reason = reason&.to_sym
20
32
  end
21
33
  end
22
34
  end
@@ -63,6 +63,15 @@ module Rigor
63
63
  end
64
64
  end
65
65
 
66
+ # ADR-9 slice 5 — topological sort by `manifest(consumes:)`
67
+ # so producers run before consumers, plus early
68
+ # `missing-producer` validation. Cycles surface as
69
+ # `dependency-cycle` LoadErrors. When validation fails, the
70
+ # offending plugin(s) drop from the returned plugins list
71
+ # and the LoadError surfaces alongside any earlier failure.
72
+ plugins, sort_errors = topo_sort_plugins(plugins)
73
+ load_errors.concat(sort_errors)
74
+
66
75
  Registry.new(plugins: plugins, load_errors: load_errors)
67
76
  end
68
77
 
@@ -186,6 +195,113 @@ module Rigor
186
195
  rescue StandardError
187
196
  plugin_class.to_s
188
197
  end
198
+
199
+ # ADR-9 slice 5 — topological sort of plugins by their
200
+ # `manifest(consumes:)` declarations. Returns `[sorted_plugins,
201
+ # load_errors]`. Determinism: when no dependency relation
202
+ # forces an order, plugins are visited alphabetically by
203
+ # manifest id. A non-optional consume of a `(plugin_id, name)`
204
+ # whose producer is missing emits a `:missing-producer`
205
+ # LoadError and drops the consumer; cycles emit a
206
+ # `:dependency-cycle` LoadError naming the offending chain.
207
+ def topo_sort_plugins(plugins)
208
+ # If no plugin opts into the cross-plugin API the loader's
209
+ # legacy configuration-order contract is preserved
210
+ # unchanged. Topo sort and missing-producer validation only
211
+ # run when at least one plugin declares `consumes:`.
212
+ return [plugins, []] unless plugins.any? { |p| p.manifest.consumes.any? }
213
+
214
+ index = plugins.to_h { |plugin| [plugin.manifest.id, plugin] }
215
+ errors = validate_missing_producers(plugins, index)
216
+ sortable = plugins.reject { |p| errors.any? { |e| e.plugin_ref == p.manifest.id } }
217
+ config_order = plugins.each_with_index.to_h { |plugin, i| [plugin.manifest.id, i] }
218
+
219
+ sort_in_topo_order(sortable, index, errors, config_order)
220
+ end
221
+
222
+ def validate_missing_producers(plugins, index)
223
+ errors = []
224
+ plugins.each do |plugin|
225
+ plugin.manifest.consumes.each do |consume|
226
+ next if consume.optional
227
+ next if index.key?(consume.plugin_id) && producer_provides?(index[consume.plugin_id], consume.name)
228
+
229
+ errors << LoadError.new(
230
+ "plugin #{plugin.manifest.id.inspect} consumes " \
231
+ "#{consume.plugin_id.inspect}/#{consume.name} but no loaded plugin " \
232
+ "with that id declares `produces: [#{consume.name.inspect}]`",
233
+ plugin_ref: plugin.manifest.id,
234
+ reason: :"missing-producer"
235
+ )
236
+ end
237
+ end
238
+ errors
239
+ end
240
+
241
+ def producer_provides?(producer, name)
242
+ producer.manifest.produces.include?(name)
243
+ end
244
+
245
+ # Kahn's algorithm with `Configuration#plugins`-order
246
+ # tie-break. Edges go from producer -> consumer (producer
247
+ # must visit first). When two plugins are simultaneously
248
+ # ready, the configuration-order index decides the visit
249
+ # order — preserves the v0.1.0 legacy contract for plugins
250
+ # without dependencies.
251
+ def sort_in_topo_order(plugins, index, errors, config_order)
252
+ in_degree, forward = build_consumes_graph(plugins, index, errors)
253
+ ordered, cycle_errors = kahn_walk(plugins, in_degree, forward, config_order)
254
+ [ordered, errors + cycle_errors]
255
+ end
256
+
257
+ def build_consumes_graph(plugins, index, errors)
258
+ in_degree = Hash.new(0)
259
+ forward = Hash.new { |h, k| h[k] = [] }
260
+ plugins.each do |consumer|
261
+ consumer.manifest.consumes.each do |consume|
262
+ next unless index.key?(consume.plugin_id)
263
+ next if errors.any? { |e| e.plugin_ref == consume.plugin_id }
264
+
265
+ forward[consume.plugin_id] << consumer.manifest.id
266
+ in_degree[consumer.manifest.id] += 1
267
+ end
268
+ end
269
+ [in_degree, forward]
270
+ end
271
+
272
+ def kahn_walk(plugins, in_degree, forward, config_order)
273
+ order = ->(plugin) { config_order.fetch(plugin.manifest.id, Float::INFINITY) }
274
+ ready = plugins.select { |p| in_degree[p.manifest.id].zero? }.sort_by(&order)
275
+ result = kahn_collect(plugins, in_degree, forward, ready, order)
276
+
277
+ return [result, []] if result.size == plugins.size
278
+
279
+ cycled = plugins - result
280
+ [result, [dependency_cycle_error(cycled)]]
281
+ end
282
+
283
+ def kahn_collect(plugins, in_degree, forward, ready, order)
284
+ result = []
285
+ until ready.empty?
286
+ plugin = ready.shift
287
+ result << plugin
288
+ forward[plugin.manifest.id].each do |consumer_id|
289
+ in_degree[consumer_id] -= 1
290
+ ready << plugins.find { |p| p.manifest.id == consumer_id } if in_degree[consumer_id].zero?
291
+ end
292
+ ready.sort_by!(&order)
293
+ end
294
+ result
295
+ end
296
+
297
+ def dependency_cycle_error(cycled)
298
+ ids = cycled.map { |p| p.manifest.id }.sort
299
+ LoadError.new(
300
+ "plugin dependency cycle through `manifest(consumes:)`: #{ids.inspect}",
301
+ plugin_ref: ids.first,
302
+ reason: :"dependency-cycle"
303
+ )
304
+ end
189
305
  end
190
306
  end
191
307
  end
@@ -11,7 +11,7 @@ module Rigor
11
11
  # The fields are pinned by ADR-2 § "Registration, Configuration,
12
12
  # and Caching"; the v0.1.0 plugin contract surface treats this
13
13
  # struct as the public manifest shape.
14
- class Manifest
14
+ class Manifest # rubocop:disable Metrics/ClassLength
15
15
  # Same regex {Rigor::Cache::Store::VALID_PRODUCER_ID} uses,
16
16
  # so plugin ids round-trip through cache producer ids and
17
17
  # `plugin.<id>.<rule>` diagnostic identifiers without escape.
@@ -23,23 +23,51 @@ module Rigor
23
23
  # the v0.1.0 protocol slices need them.
24
24
  VALID_VALUE_KINDS = %i[string boolean integer array hash any].freeze
25
25
 
26
- attr_reader :id, :version, :description, :protocols, :config_schema
26
+ # ADR-9 slice 4 declared cross-plugin fact dependencies.
27
+ # `produces:` lists the names this plugin publishes through
28
+ # its `#prepare(services)` hook. `consumes:` lists the
29
+ # `(plugin_id, name)` pairs this plugin reads from
30
+ # `services.fact_store`. The loader uses both for
31
+ # topological sort + missing-producer detection (slice 5);
32
+ # slice 4 carries the declarations on the manifest but the
33
+ # loader does not yet enforce them.
34
+ Consumption = Data.define(:plugin_id, :name, :optional) do
35
+ def initialize(plugin_id:, name:, optional: false)
36
+ super(plugin_id: plugin_id.to_s, name: name.to_sym, optional: optional ? true : false)
37
+ end
38
+ end
27
39
 
28
- def initialize(id:, version:, description: nil, protocols: [], config_schema: {})
40
+ attr_reader :id, :version, :description, :protocols, :config_schema, :produces, :consumes
41
+
42
+ def initialize( # rubocop:disable Metrics/ParameterLists
43
+ id:, version:,
44
+ description: nil, protocols: [], config_schema: {},
45
+ produces: [], consumes: []
46
+ )
29
47
  validate_id!(id)
30
48
  validate_version!(version)
31
49
  validate_protocols!(protocols)
32
50
  validate_config_schema!(config_schema)
51
+ validate_produces!(produces)
52
+
53
+ assign_fields(id, version, description, protocols, config_schema, produces, consumes)
54
+ freeze
55
+ end
56
+
57
+ private
33
58
 
59
+ def assign_fields(id, version, description, protocols, config_schema, produces, consumes) # rubocop:disable Metrics/ParameterLists
34
60
  @id = id.dup.freeze
35
61
  @version = version.dup.freeze
36
62
  @description = description.nil? ? nil : description.to_s.dup.freeze
37
63
  @protocols = protocols.map(&:to_sym).freeze
38
64
  @config_schema = config_schema.to_h { |k, v| [k.to_s.dup.freeze, v.to_sym] }.freeze
39
-
40
- freeze
65
+ @produces = produces.map(&:to_sym).freeze
66
+ @consumes = coerce_consumes(consumes)
41
67
  end
42
68
 
69
+ public
70
+
43
71
  # Validates the user-supplied plugin config block against this
44
72
  # manifest's `config_schema`. Returns an array of human-readable
45
73
  # error strings (empty when the config is valid). Slice 1 checks
@@ -69,7 +97,9 @@ module Rigor
69
97
  "version" => version,
70
98
  "description" => description,
71
99
  "protocols" => protocols.map(&:to_s),
72
- "config_schema" => config_schema.to_h { |k, v| [k, v.to_s] }
100
+ "config_schema" => config_schema.to_h { |k, v| [k, v.to_s] },
101
+ "produces" => produces.map(&:to_s),
102
+ "consumes" => consumes.map { |c| consumption_hash(c) }
73
103
  }
74
104
  end
75
105
 
@@ -129,6 +159,45 @@ module Rigor
129
159
  else false
130
160
  end
131
161
  end
162
+
163
+ def validate_produces!(produces)
164
+ return if produces.is_a?(Array) && produces.all? { |p| p.is_a?(Symbol) || p.is_a?(String) }
165
+
166
+ raise ArgumentError, "plugin manifest produces must be an Array of Symbol/String, got #{produces.inspect}"
167
+ end
168
+
169
+ def coerce_consumes(consumes)
170
+ unless consumes.is_a?(Array)
171
+ raise ArgumentError, "plugin manifest consumes must be an Array, got #{consumes.inspect}"
172
+ end
173
+
174
+ consumes.map { |entry| coerce_consumption(entry) }.freeze
175
+ end
176
+
177
+ def coerce_consumption(entry)
178
+ case entry
179
+ when Consumption then entry
180
+ when Hash then build_consumption_from_hash(entry)
181
+ else raise ArgumentError,
182
+ "plugin manifest consumes entry must be a Hash or Consumption, got #{entry.inspect}"
183
+ end
184
+ end
185
+
186
+ def consumption_hash(consumption)
187
+ { "plugin_id" => consumption.plugin_id, "name" => consumption.name.to_s, "optional" => consumption.optional }
188
+ end
189
+
190
+ def build_consumption_from_hash(entry)
191
+ plugin_id = entry[:plugin_id] || entry["plugin_id"]
192
+ name = entry[:name] || entry["name"]
193
+ optional = entry.key?(:optional) ? entry[:optional] : entry["optional"]
194
+ if plugin_id.nil? || name.nil?
195
+ raise ArgumentError,
196
+ "plugin manifest consumes entry missing plugin_id/name: #{entry.inspect}"
197
+ end
198
+
199
+ Consumption.new(plugin_id: plugin_id, name: name, optional: optional || false)
200
+ end
132
201
  end
133
202
  end
134
203
  end
@@ -31,15 +31,27 @@ module Rigor
31
31
  # raw `File.read` so reads stay within the trusted scope and
32
32
  # feed cache invalidation; ADR-2 § "Plugin Trust and I/O
33
33
  # Policy" documents the trust model the boundary enforces.
34
+ #
35
+ # ADR-9 slice 2 adds `fact_store`: the per-run cross-plugin
36
+ # `Plugin::FactStore`. Producer plugins publish their facts
37
+ # in `#prepare(services)` (slice 3); consumer plugins read in
38
+ # `#diagnostics_for_file` via `services.fact_store.read(...)`.
39
+ # A fresh `FactStore` instance is constructed per Services
40
+ # when none is supplied — the runner threads its own instance
41
+ # in once slice 3 wires `#prepare` invocation.
34
42
  class Services
35
- attr_reader :reflection, :type, :configuration, :cache_store, :trust_policy
43
+ attr_reader :reflection, :type, :configuration, :cache_store, :trust_policy, :fact_store
36
44
 
37
- def initialize(reflection:, type:, configuration:, cache_store: nil, trust_policy: nil)
45
+ def initialize( # rubocop:disable Metrics/ParameterLists
46
+ reflection:, type:, configuration:,
47
+ cache_store: nil, trust_policy: nil, fact_store: nil
48
+ )
38
49
  @reflection = reflection
39
50
  @type = type
40
51
  @configuration = configuration
41
52
  @cache_store = cache_store
42
53
  @trust_policy = trust_policy || default_trust_policy
54
+ @fact_store = fact_store || FactStore.new
43
55
  freeze
44
56
  end
45
57
 
@@ -32,15 +32,18 @@ module Rigor
32
32
  # `Gemfile.lock`, and each trusted gem's
33
33
  # `Gem::Specification#full_gem_path`. The user extends this
34
34
  # with `.rigor.yml`'s `plugins_io.allowed_paths:`.
35
- # - `network_policy`: `:disabled` in slice 2; the only value
36
- # accepted today. Plugin {IoBoundary#open_url} always raises
37
- # while the policy is `:disabled`.
35
+ # - `network_policy`: one of {VALID_NETWORK_POLICIES}.
36
+ # `:disabled` (default) makes {IoBoundary#open_url} always
37
+ # raise. `:allowlist` (v0.1.2) consults `allowed_url_hosts`
38
+ # on every fetch — the hostname must be on the list and
39
+ # the URL scheme MUST be `https`. The list of allowed hosts
40
+ # is exact-match (no wildcards in v0.1.2).
38
41
  class TrustPolicy
39
- VALID_NETWORK_POLICIES = %i[disabled].freeze
42
+ VALID_NETWORK_POLICIES = %i[disabled allowlist].freeze
40
43
 
41
- attr_reader :trusted_gems, :allowed_read_roots, :network_policy
44
+ attr_reader :trusted_gems, :allowed_read_roots, :network_policy, :allowed_url_hosts
42
45
 
43
- def initialize(trusted_gems: [], allowed_read_roots: [], network_policy: :disabled)
46
+ def initialize(trusted_gems: [], allowed_read_roots: [], network_policy: :disabled, allowed_url_hosts: [])
44
47
  validate_network_policy!(network_policy)
45
48
 
46
49
  @trusted_gems = trusted_gems.map { |g| g.to_s.dup.freeze }.uniq.sort.freeze
@@ -50,6 +53,7 @@ module Rigor
50
53
  .sort
51
54
  .freeze
52
55
  @network_policy = network_policy
56
+ @allowed_url_hosts = allowed_url_hosts.map { |h| h.to_s.downcase.dup.freeze }.uniq.sort.freeze
53
57
  freeze
54
58
  end
55
59
 
@@ -67,6 +71,24 @@ module Rigor
67
71
  @network_policy != :disabled
68
72
  end
69
73
 
74
+ # @param url [String, URI]
75
+ # @return [Boolean] true when the URL scheme is `https` and
76
+ # the parsed hostname is in `allowed_url_hosts`. Always
77
+ # `false` while `network_policy` is `:disabled`.
78
+ def allow_url?(url)
79
+ return false if @network_policy == :disabled
80
+ return false if @allowed_url_hosts.empty?
81
+
82
+ require "uri"
83
+ uri = url.is_a?(URI::Generic) ? url : URI.parse(url.to_s)
84
+ return false unless uri.is_a?(URI::HTTPS)
85
+ return false if uri.host.nil?
86
+
87
+ @allowed_url_hosts.include?(uri.host.downcase)
88
+ rescue URI::InvalidURIError
89
+ false
90
+ end
91
+
70
92
  def gem_trusted?(name)
71
93
  @trusted_gems.include?(name.to_s)
72
94
  end
@@ -75,7 +97,8 @@ module Rigor
75
97
  {
76
98
  "trusted_gems" => trusted_gems,
77
99
  "allowed_read_roots" => allowed_read_roots,
78
- "network_policy" => network_policy.to_s
100
+ "network_policy" => network_policy.to_s,
101
+ "allowed_url_hosts" => allowed_url_hosts
79
102
  }
80
103
  end
81
104
 
data/lib/rigor/plugin.rb CHANGED
@@ -4,6 +4,7 @@ require_relative "plugin/manifest"
4
4
  require_relative "plugin/access_denied_error"
5
5
  require_relative "plugin/trust_policy"
6
6
  require_relative "plugin/io_boundary"
7
+ require_relative "plugin/fact_store"
7
8
  require_relative "plugin/services"
8
9
  require_relative "plugin/base"
9
10
  require_relative "plugin/registry"
data/lib/rigor/scope.rb CHANGED
@@ -20,7 +20,7 @@ module Rigor
20
20
  :ivars, :cvars, :globals,
21
21
  :class_ivars, :class_cvars, :program_globals,
22
22
  :discovered_classes, :in_source_constants, :discovered_methods,
23
- :discovered_def_nodes
23
+ :discovered_def_nodes, :discovered_method_visibilities
24
24
 
25
25
  EMPTY_DECLARED_TYPES = {}.compare_by_identity.freeze
26
26
  EMPTY_VAR_BINDINGS = {}.freeze
@@ -47,7 +47,8 @@ module Rigor
47
47
  discovered_classes: EMPTY_VAR_BINDINGS,
48
48
  in_source_constants: EMPTY_VAR_BINDINGS,
49
49
  discovered_methods: EMPTY_CLASS_BINDINGS,
50
- discovered_def_nodes: EMPTY_CLASS_BINDINGS
50
+ discovered_def_nodes: EMPTY_CLASS_BINDINGS,
51
+ discovered_method_visibilities: EMPTY_CLASS_BINDINGS
51
52
  )
52
53
  @environment = environment
53
54
  @locals = locals
@@ -64,6 +65,7 @@ module Rigor
64
65
  @in_source_constants = in_source_constants
65
66
  @discovered_methods = discovered_methods
66
67
  @discovered_def_nodes = discovered_def_nodes
68
+ @discovered_method_visibilities = discovered_method_visibilities
67
69
  freeze
68
70
  end
69
71
 
@@ -268,6 +270,26 @@ module Rigor
268
270
  rebuild(discovered_def_nodes: table)
269
271
  end
270
272
 
273
+ # v0.1.2 — per-class table mapping `method_name (Symbol) →
274
+ # :public | :private | :protected`. Populated by
275
+ # `ScopeIndexer` for every `def` it sees inside a class
276
+ # body, with the visibility taken from the surrounding
277
+ # `private` / `protected` / `public` modifier state plus
278
+ # any post-hoc `private :name, ...` named-argument calls.
279
+ # Consumed by the `def.method-visibility-mismatch` rule
280
+ # so explicit-non-self calls to a private method surface
281
+ # a diagnostic.
282
+ def discovered_method_visibility(class_name, method_name)
283
+ table = @discovered_method_visibilities[class_name.to_s]
284
+ return nil unless table
285
+
286
+ table[method_name.to_sym]
287
+ end
288
+
289
+ def with_discovered_method_visibilities(table)
290
+ rebuild(discovered_method_visibilities: table)
291
+ end
292
+
271
293
  def facts_for(target: nil, bucket: nil)
272
294
  fact_store.facts_for(target: target, bucket: bucket)
273
295
  end
@@ -334,7 +356,8 @@ module Rigor
334
356
  declared_types: @declared_types, ivars: @ivars, cvars: @cvars, globals: @globals,
335
357
  class_ivars: @class_ivars, class_cvars: @class_cvars, program_globals: @program_globals,
336
358
  discovered_classes: @discovered_classes, in_source_constants: @in_source_constants,
337
- discovered_methods: @discovered_methods, discovered_def_nodes: @discovered_def_nodes
359
+ discovered_methods: @discovered_methods, discovered_def_nodes: @discovered_def_nodes,
360
+ discovered_method_visibilities: @discovered_method_visibilities
338
361
  )
339
362
  self.class.new(
340
363
  environment: environment, locals: locals,
@@ -346,7 +369,8 @@ module Rigor
346
369
  discovered_classes: discovered_classes,
347
370
  in_source_constants: in_source_constants,
348
371
  discovered_methods: discovered_methods,
349
- discovered_def_nodes: discovered_def_nodes
372
+ discovered_def_nodes: discovered_def_nodes,
373
+ discovered_method_visibilities: discovered_method_visibilities
350
374
  )
351
375
  end
352
376
 
@@ -371,7 +395,8 @@ module Rigor
371
395
  discovered_classes: discovered_classes,
372
396
  in_source_constants: in_source_constants,
373
397
  discovered_methods: discovered_methods,
374
- discovered_def_nodes: discovered_def_nodes
398
+ discovered_def_nodes: discovered_def_nodes,
399
+ discovered_method_visibilities: discovered_method_visibilities
375
400
  )
376
401
  end
377
402
  end
data/lib/rigor/trinary.rb CHANGED
@@ -58,7 +58,7 @@ module Rigor
58
58
  case value
59
59
  when :yes then self.class.no
60
60
  when :no then self.class.yes
61
- when :maybe then self.class.maybe
61
+ else self.class.maybe
62
62
  end
63
63
  end
64
64
 
@@ -66,12 +66,16 @@ module Rigor
66
66
  # `:neg_infinity` directly with an `Integer`.
67
67
  def lower
68
68
  m = min
69
- m.is_a?(Symbol) ? -Float::INFINITY : m
69
+ return m if m.is_a?(Integer)
70
+
71
+ -Float::INFINITY
70
72
  end
71
73
 
72
74
  def upper
73
75
  m = max
74
- m.is_a?(Symbol) ? Float::INFINITY : m
76
+ return m if m.is_a?(Integer)
77
+
78
+ Float::INFINITY
75
79
  end
76
80
 
77
81
  ALIAS_NAMES = {
data/lib/rigor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rigor
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
@@ -6,11 +6,12 @@ module Rigor
6
6
 
7
7
  attr_reader class_registry: ClassRegistry
8
8
  attr_reader rbs_loader: RbsLoader?
9
+ attr_reader plugin_registry: untyped?
9
10
 
10
11
  def self.default: () -> Environment
11
- def self.for_project: (?root: String, ?libraries: Array[String], ?signature_paths: Array[String | _ToPath]?) -> Environment
12
+ def self.for_project: (?root: String, ?libraries: Array[String], ?signature_paths: Array[String | _ToPath]?, ?cache_store: untyped?, ?plugin_registry: untyped?) -> Environment
12
13
 
13
- def initialize: (?class_registry: ClassRegistry, ?rbs_loader: RbsLoader?) -> void
14
+ def initialize: (?class_registry: ClassRegistry, ?rbs_loader: RbsLoader?, ?plugin_registry: untyped?) -> void
14
15
  def nominal_for_name: (String | Symbol name) -> Type::Nominal?
15
16
  def singleton_for_name: (String | Symbol name) -> Type::Singleton?
16
17
  def constant_for_name: (String | Symbol name) -> Type::t?
data/sig/rigor/scope.rbs CHANGED
@@ -15,6 +15,7 @@ module Rigor
15
15
  attr_reader in_source_constants: Hash[String, Type::t]
16
16
  attr_reader discovered_methods: Hash[String, Hash[Symbol, Symbol]]
17
17
  attr_reader discovered_def_nodes: Hash[String, Hash[Symbol, untyped]]
18
+ attr_reader discovered_method_visibilities: Hash[String, Hash[Symbol, Symbol]]
18
19
 
19
20
  def self.empty: (?environment: Environment) -> Scope
20
21
 
@@ -39,6 +40,8 @@ module Rigor
39
40
  def with_discovered_def_nodes: (Hash[String, Hash[Symbol, untyped]] table) -> Scope
40
41
  def user_def_for: (String | Symbol class_name, String | Symbol method_name) -> untyped?
41
42
  def top_level_def_for: (String | Symbol method_name) -> untyped?
43
+ def with_discovered_method_visibilities: (Hash[String, Hash[Symbol, Symbol]] table) -> Scope
44
+ def discovered_method_visibility: (String | Symbol class_name, String | Symbol method_name) -> Symbol?
42
45
  def with_fact: (Analysis::FactStore::Fact fact) -> Scope
43
46
  def with_self_type: (Type::t? type) -> Scope
44
47
  def with_declared_types: (Hash[untyped, Type::t] table) -> Scope
data/sig/rigor.rbs CHANGED
@@ -64,8 +64,14 @@ module Rigor
64
64
  class Runner
65
65
  RUBY_GLOB: String
66
66
  DEFAULT_CACHE_ROOT: String
67
- attr_reader cache_store: Rigor::Cache::Store?
68
- def initialize: (configuration: Configuration, ?explain: bool, ?cache_store: Rigor::Cache::Store?) -> void
67
+ # `Rigor::Cache::Store` itself is not yet sig-covered (the
68
+ # cache namespace is in `UNSIGNED_NAMESPACES` per
69
+ # `spec/rigor/public_api_drift_spec.rb`), so reference it as
70
+ # `untyped` until the full Cache::Store sig lands. Steep
71
+ # otherwise raises `RBS::UnknownTypeName` for the named type.
72
+ attr_reader cache_store: untyped
73
+ attr_reader plugin_registry: untyped
74
+ def initialize: (configuration: Configuration, ?explain: bool, ?cache_store: untyped, ?plugin_requirer: untyped) -> void
69
75
  def run: (?Array[String] paths) -> Result
70
76
  end
71
77
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rigortype
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rigor contributors
@@ -184,13 +184,18 @@ files:
184
184
  - exe/rigor
185
185
  - lib/rigor.rb
186
186
  - lib/rigor/analysis/check_rules.rb
187
+ - lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb
188
+ - lib/rigor/analysis/check_rules/dead_assignment_collector.rb
189
+ - lib/rigor/analysis/check_rules/ivar_write_collector.rb
187
190
  - lib/rigor/analysis/diagnostic.rb
188
191
  - lib/rigor/analysis/fact_store.rb
189
192
  - lib/rigor/analysis/result.rb
193
+ - lib/rigor/analysis/rule_catalog.rb
190
194
  - lib/rigor/analysis/runner.rb
191
195
  - lib/rigor/ast.rb
192
196
  - lib/rigor/ast/type_node.rb
193
197
  - lib/rigor/builtins/imported_refinements.rb
198
+ - lib/rigor/builtins/regex_refinement.rb
194
199
  - lib/rigor/cache/descriptor.rb
195
200
  - lib/rigor/cache/rbs_class_ancestor_table.rb
196
201
  - lib/rigor/cache/rbs_class_type_param_names.rb
@@ -202,6 +207,8 @@ files:
202
207
  - lib/rigor/cache/rbs_known_class_names.rb
203
208
  - lib/rigor/cache/store.rb
204
209
  - lib/rigor/cli.rb
210
+ - lib/rigor/cli/diff_command.rb
211
+ - lib/rigor/cli/explain_command.rb
205
212
  - lib/rigor/cli/type_of_command.rb
206
213
  - lib/rigor/cli/type_of_renderer.rb
207
214
  - lib/rigor/cli/type_scan_command.rb
@@ -265,6 +272,7 @@ files:
265
272
  - lib/rigor/plugin.rb
266
273
  - lib/rigor/plugin/access_denied_error.rb
267
274
  - lib/rigor/plugin/base.rb
275
+ - lib/rigor/plugin/fact_store.rb
268
276
  - lib/rigor/plugin/io_boundary.rb
269
277
  - lib/rigor/plugin/load_error.rb
270
278
  - lib/rigor/plugin/loader.rb