rigortype 0.1.1 → 0.1.3
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 +10 -0
- data/data/builtins/ruby_core/range.yml +6 -4
- data/data/builtins/ruby_core/string.yml +15 -10
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +116 -0
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +123 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +118 -0
- data/lib/rigor/analysis/check_rules.rb +346 -18
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +87 -0
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +72 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +110 -0
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +200 -0
- data/lib/rigor/analysis/dependency_source_inference.rb +37 -0
- data/lib/rigor/analysis/rule_catalog.rb +343 -0
- data/lib/rigor/analysis/runner.rb +96 -6
- data/lib/rigor/cache/descriptor.rb +58 -5
- data/lib/rigor/cli/diff_command.rb +169 -0
- data/lib/rigor/cli/explain_command.rb +129 -0
- data/lib/rigor/cli.rb +18 -1
- data/lib/rigor/configuration/dependencies.rb +235 -0
- data/lib/rigor/configuration/severity_profile.rb +18 -3
- data/lib/rigor/configuration.rb +53 -13
- data/lib/rigor/environment.rb +16 -4
- data/lib/rigor/flow_contribution/merger.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +104 -12
- data/lib/rigor/inference/method_dispatcher.rb +87 -0
- data/lib/rigor/inference/scope_indexer.rb +171 -2
- data/lib/rigor/inference/statement_evaluator.rb +65 -1
- data/lib/rigor/plugin/io_boundary.rb +92 -19
- data/lib/rigor/plugin/manifest.rb +26 -5
- data/lib/rigor/plugin/trust_policy.rb +30 -7
- data/lib/rigor/scope.rb +30 -5
- data/lib/rigor/version.rb +1 -1
- data/sig/rigor/environment.rbs +3 -2
- data/sig/rigor/scope.rbs +3 -0
- metadata +13 -1
|
@@ -785,6 +785,7 @@ module Rigor
|
|
|
785
785
|
evaluate_block_if_present(node)
|
|
786
786
|
post_scope = record_closure_escape_if_any(node)
|
|
787
787
|
post_scope = apply_rbs_extended_assertions(node, post_scope)
|
|
788
|
+
post_scope = apply_plugin_assertions(node, post_scope)
|
|
788
789
|
post_scope = apply_rspec_matcher_narrowing(node, post_scope)
|
|
789
790
|
[call_type, post_scope]
|
|
790
791
|
end
|
|
@@ -978,6 +979,61 @@ module Rigor
|
|
|
978
979
|
end
|
|
979
980
|
end
|
|
980
981
|
|
|
982
|
+
# ADR-7 § "Slice 4-A" / T.bind priority slice 2 — applies
|
|
983
|
+
# the post-return facts plugin contributions produce. This
|
|
984
|
+
# is the sibling of {apply_rbs_extended_assertions}: the
|
|
985
|
+
# carrier (`Rigor::FlowContribution::Fact`) and the
|
|
986
|
+
# downstream narrowing path (`apply_post_return_fact` →
|
|
987
|
+
# `apply_self_post_return_fact`) are the same; only the
|
|
988
|
+
# *source* of the bundle changes (RBS::Extended vs the
|
|
989
|
+
# registered plugins' `flow_contribution_for`).
|
|
990
|
+
#
|
|
991
|
+
# `:self`-targeted facts narrow `scope.self_type` for the
|
|
992
|
+
# surrounding scope. In a block body, the surrounding
|
|
993
|
+
# scope is the block's own scope, so the narrowing applies
|
|
994
|
+
# to the rest of the block — exactly the contract Sorbet's
|
|
995
|
+
# `T.bind(self, T)` commits to.
|
|
996
|
+
#
|
|
997
|
+
# `:parameter`-targeted facts only land when the called
|
|
998
|
+
# method has an authoritative RBS sig (via
|
|
999
|
+
# `resolve_call_method`); plugins recognising their own
|
|
1000
|
+
# synthetic call shapes (e.g. `T.assert_type!`) have no
|
|
1001
|
+
# method_def and the parameter facts silently skip — the
|
|
1002
|
+
# plugin's own diagnostics_for_file path covers those
|
|
1003
|
+
# cases. The full plugin-side parameter-targeting story
|
|
1004
|
+
# (PHPStan-style Type-Specifying Extensions on
|
|
1005
|
+
# plugin-recognised calls) lives behind a follow-up slice
|
|
1006
|
+
# that introduces `:local` / `:argument_at` target kinds.
|
|
1007
|
+
def apply_plugin_assertions(call_node, current_scope)
|
|
1008
|
+
registry = current_scope.environment&.plugin_registry
|
|
1009
|
+
return current_scope if registry.nil? || registry.empty?
|
|
1010
|
+
|
|
1011
|
+
contributions = collect_plugin_contributions(registry, call_node, current_scope)
|
|
1012
|
+
return current_scope if contributions.empty?
|
|
1013
|
+
|
|
1014
|
+
result = Rigor::FlowContribution::Merger.merge(contributions)
|
|
1015
|
+
post_return = result.post_return_facts
|
|
1016
|
+
return current_scope if post_return.empty?
|
|
1017
|
+
|
|
1018
|
+
method_def = resolve_call_method(call_node, current_scope)
|
|
1019
|
+
post_return.reduce(current_scope) do |scope_acc, fact|
|
|
1020
|
+
apply_post_return_fact(fact, call_node, scope_acc, method_def)
|
|
1021
|
+
end
|
|
1022
|
+
end
|
|
1023
|
+
|
|
1024
|
+
# Walks the registry and collects each plugin's
|
|
1025
|
+
# `flow_contribution_for` result, swallowing per-plugin
|
|
1026
|
+
# exceptions so a buggy plugin can't abort the assertion
|
|
1027
|
+
# path. Mirrors `MethodDispatcher.collect_plugin_contributions`
|
|
1028
|
+
# exactly — the two paths consume the same hook.
|
|
1029
|
+
def collect_plugin_contributions(registry, call_node, current_scope)
|
|
1030
|
+
registry.plugins.filter_map do |plugin|
|
|
1031
|
+
plugin.flow_contribution_for(call_node: call_node, scope: current_scope)
|
|
1032
|
+
rescue StandardError
|
|
1033
|
+
nil
|
|
1034
|
+
end
|
|
1035
|
+
end
|
|
1036
|
+
|
|
981
1037
|
def resolve_call_method(call_node, current_scope) # rubocop:disable Metrics/PerceivedComplexity
|
|
982
1038
|
receiver_node = call_node.receiver
|
|
983
1039
|
receiver_type =
|
|
@@ -1064,7 +1120,15 @@ module Rigor
|
|
|
1064
1120
|
end
|
|
1065
1121
|
end
|
|
1066
1122
|
|
|
1067
|
-
def lookup_post_return_arg(call_node, method_def, target_name)
|
|
1123
|
+
def lookup_post_return_arg(call_node, method_def, target_name) # rubocop:disable Metrics/CyclomaticComplexity
|
|
1124
|
+
# Plugin-source contributions arrive without an
|
|
1125
|
+
# authoritative method_def (the plugin recognised the
|
|
1126
|
+
# call shape directly). Parameter-targeting falls back
|
|
1127
|
+
# to "no narrow" in that case — the wider plugin-side
|
|
1128
|
+
# parameter mapping (`:local` / `:argument_at`) is a
|
|
1129
|
+
# follow-up slice.
|
|
1130
|
+
return nil if method_def.nil?
|
|
1131
|
+
|
|
1068
1132
|
arguments = call_node.arguments&.arguments || []
|
|
1069
1133
|
method_def.method_types.each do |mt|
|
|
1070
1134
|
params = mt.type.required_positionals + mt.type.optional_positionals
|
|
@@ -27,20 +27,37 @@ module Rigor
|
|
|
27
27
|
# the file's contents, and adds a digest-keyed
|
|
28
28
|
# {Cache::Descriptor::FileEntry} to the boundary's
|
|
29
29
|
# accumulated descriptor.
|
|
30
|
-
# - `#open_url(url)` —
|
|
31
|
-
# `network_policy
|
|
32
|
-
#
|
|
33
|
-
#
|
|
30
|
+
# - `#open_url(url)` — fetches the URL when the policy
|
|
31
|
+
# permits it (`network_policy: :allowlist` plus an
|
|
32
|
+
# `allowed_url_hosts` match) and raises
|
|
33
|
+
# {AccessDeniedError} otherwise. v0.1.2 ships the
|
|
34
|
+
# allowlist surface; the default project policy still
|
|
35
|
+
# has `network_policy: :disabled` so plugins that want
|
|
36
|
+
# network access opt in explicitly through
|
|
37
|
+
# `.rigor.yml`'s `plugins_io.network: allowlist` plus
|
|
38
|
+
# `plugins_io.allowed_url_hosts: [...]`. The HTTP fetch
|
|
39
|
+
# is GET-only over HTTPS, capped at {URL_TIMEOUT_SECONDS}
|
|
40
|
+
# wall time and {URL_MAX_BYTES} body size; non-2xx
|
|
41
|
+
# responses raise {AccessDeniedError} so plugin code
|
|
42
|
+
# doesn't have to rescue mid-build.
|
|
34
43
|
# - `#cache_descriptor` — flushes the accumulated entries into
|
|
35
44
|
# a fresh {Cache::Descriptor} for the contribution that
|
|
36
|
-
# built it.
|
|
45
|
+
# built it. URL fetches contribute `ConfigEntry` rows
|
|
46
|
+
# keyed `"url:#{url}"` with the response body's SHA-256
|
|
47
|
+
# so contributions invalidate when the remote document
|
|
48
|
+
# changes.
|
|
37
49
|
class IoBoundary
|
|
50
|
+
URL_TIMEOUT_SECONDS = 10
|
|
51
|
+
URL_MAX_BYTES = 10 * 1024 * 1024
|
|
52
|
+
|
|
38
53
|
attr_reader :policy, :plugin_id
|
|
39
54
|
|
|
40
|
-
def initialize(policy:, plugin_id:)
|
|
55
|
+
def initialize(policy:, plugin_id:, http_client: DefaultHttpClient.new)
|
|
41
56
|
@policy = policy
|
|
42
57
|
@plugin_id = plugin_id.to_s.dup.freeze
|
|
43
58
|
@file_entries = {}
|
|
59
|
+
@config_entries = {}
|
|
60
|
+
@http_client = http_client
|
|
44
61
|
@mutex = Mutex.new
|
|
45
62
|
end
|
|
46
63
|
|
|
@@ -64,30 +81,39 @@ module Rigor
|
|
|
64
81
|
contents
|
|
65
82
|
end
|
|
66
83
|
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
# the
|
|
70
|
-
# the
|
|
84
|
+
# Fetches the URL when the policy permits it. Returns the
|
|
85
|
+
# response body. Raises {AccessDeniedError} when the policy
|
|
86
|
+
# is `:disabled`, the URL scheme is not `https`, the host is
|
|
87
|
+
# not on the allowlist, the response is non-2xx, the body
|
|
88
|
+
# exceeds {URL_MAX_BYTES}, or the request times out
|
|
89
|
+
# ({URL_TIMEOUT_SECONDS}). On success, records a
|
|
90
|
+
# `ConfigEntry` keyed `"url:#{url}"` with the body's
|
|
91
|
+
# SHA-256 so the cache descriptor invalidates if the remote
|
|
92
|
+
# document changes.
|
|
71
93
|
def open_url(url)
|
|
72
|
-
|
|
94
|
+
url_string = url.to_s
|
|
95
|
+
unless @policy.allow_url?(url_string)
|
|
73
96
|
raise AccessDeniedError.new(
|
|
74
97
|
"plugin #{@plugin_id.inspect} cannot open URL #{url.inspect}: " \
|
|
75
|
-
"
|
|
98
|
+
"URL is not permitted by the active TrustPolicy " \
|
|
99
|
+
"(network_policy=#{@policy.network_policy} allowed_url_hosts=#{@policy.allowed_url_hosts.inspect})",
|
|
76
100
|
reason: :network_disabled,
|
|
77
|
-
resource:
|
|
101
|
+
resource: url_string
|
|
78
102
|
)
|
|
79
103
|
end
|
|
80
104
|
|
|
81
|
-
|
|
105
|
+
body = @http_client.get(url_string, timeout: URL_TIMEOUT_SECONDS, max_bytes: URL_MAX_BYTES)
|
|
106
|
+
record_url_entry(url_string, body)
|
|
107
|
+
body
|
|
82
108
|
end
|
|
83
109
|
|
|
84
110
|
# @return [Rigor::Cache::Descriptor] frozen snapshot of every
|
|
85
|
-
# file the boundary has read so far. Calling this
|
|
86
|
-
# times yields equal descriptors; subsequent
|
|
87
|
-
# the underlying record
|
|
111
|
+
# file / URL the boundary has read so far. Calling this
|
|
112
|
+
# multiple times yields equal descriptors; subsequent
|
|
113
|
+
# reads expand the underlying record tables.
|
|
88
114
|
def cache_descriptor
|
|
89
|
-
|
|
90
|
-
Cache::Descriptor.new(files:
|
|
115
|
+
files, configs = @mutex.synchronize { [@file_entries.values.dup, @config_entries.values.dup] }
|
|
116
|
+
Cache::Descriptor.new(files: files, configs: configs)
|
|
91
117
|
end
|
|
92
118
|
|
|
93
119
|
private
|
|
@@ -97,6 +123,53 @@ module Rigor
|
|
|
97
123
|
entry = Cache::Descriptor::FileEntry.new(path: path, comparator: :digest, value: digest)
|
|
98
124
|
@mutex.synchronize { @file_entries[path] = entry }
|
|
99
125
|
end
|
|
126
|
+
|
|
127
|
+
def record_url_entry(url, body)
|
|
128
|
+
digest = Digest::SHA256.hexdigest(body)
|
|
129
|
+
key = "url:#{url}"
|
|
130
|
+
entry = Cache::Descriptor::ConfigEntry.new(key: key, value_hash: digest)
|
|
131
|
+
@mutex.synchronize { @config_entries[key] = entry }
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Default HTTP client wrapping `Net::HTTP`. Wraps a single
|
|
136
|
+
# `GET` over HTTPS. Specs inject a fake client that conforms
|
|
137
|
+
# to the same `#get(url, timeout:, max_bytes:)` shape so the
|
|
138
|
+
# tests don't require network access.
|
|
139
|
+
class DefaultHttpClient
|
|
140
|
+
# rubocop:disable Metrics/MethodLength
|
|
141
|
+
def get(url, timeout:, max_bytes:)
|
|
142
|
+
require "net/http"
|
|
143
|
+
require "uri"
|
|
144
|
+
|
|
145
|
+
uri = URI.parse(url)
|
|
146
|
+
body = +""
|
|
147
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true,
|
|
148
|
+
open_timeout: timeout,
|
|
149
|
+
read_timeout: timeout) do |http|
|
|
150
|
+
http.request_get(uri.request_uri) do |response|
|
|
151
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
152
|
+
raise Plugin::AccessDeniedError.new(
|
|
153
|
+
"URL #{url.inspect} returned non-success status #{response.code}",
|
|
154
|
+
reason: :url_fetch_failed,
|
|
155
|
+
resource: url
|
|
156
|
+
)
|
|
157
|
+
end
|
|
158
|
+
response.read_body do |chunk|
|
|
159
|
+
body << chunk
|
|
160
|
+
if body.bytesize > max_bytes
|
|
161
|
+
raise Plugin::AccessDeniedError.new(
|
|
162
|
+
"URL #{url.inspect} body exceeds #{max_bytes} bytes",
|
|
163
|
+
reason: :url_body_too_large,
|
|
164
|
+
resource: url
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
body
|
|
171
|
+
end
|
|
172
|
+
# rubocop:enable Metrics/MethodLength
|
|
100
173
|
end
|
|
101
174
|
end
|
|
102
175
|
end
|
|
@@ -37,26 +37,29 @@ module Rigor
|
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
attr_reader :id, :version, :description, :protocols, :config_schema, :produces, :consumes
|
|
40
|
+
attr_reader :id, :version, :description, :protocols, :config_schema, :produces, :consumes,
|
|
41
|
+
:owns_receivers
|
|
41
42
|
|
|
42
43
|
def initialize( # rubocop:disable Metrics/ParameterLists
|
|
43
44
|
id:, version:,
|
|
44
45
|
description: nil, protocols: [], config_schema: {},
|
|
45
|
-
produces: [], consumes: []
|
|
46
|
+
produces: [], consumes: [], owns_receivers: []
|
|
46
47
|
)
|
|
47
48
|
validate_id!(id)
|
|
48
49
|
validate_version!(version)
|
|
49
50
|
validate_protocols!(protocols)
|
|
50
51
|
validate_config_schema!(config_schema)
|
|
51
52
|
validate_produces!(produces)
|
|
53
|
+
validate_owns_receivers!(owns_receivers)
|
|
52
54
|
|
|
53
|
-
assign_fields(id, version, description, protocols, config_schema, produces, consumes)
|
|
55
|
+
assign_fields(id, version, description, protocols, config_schema, produces, consumes, owns_receivers)
|
|
54
56
|
freeze
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
private
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
# rubocop:disable Metrics/ParameterLists,Metrics/AbcSize
|
|
62
|
+
def assign_fields(id, version, description, protocols, config_schema, produces, consumes, owns_receivers)
|
|
60
63
|
@id = id.dup.freeze
|
|
61
64
|
@version = version.dup.freeze
|
|
62
65
|
@description = description.nil? ? nil : description.to_s.dup.freeze
|
|
@@ -64,7 +67,9 @@ module Rigor
|
|
|
64
67
|
@config_schema = config_schema.to_h { |k, v| [k.to_s.dup.freeze, v.to_sym] }.freeze
|
|
65
68
|
@produces = produces.map(&:to_sym).freeze
|
|
66
69
|
@consumes = coerce_consumes(consumes)
|
|
70
|
+
@owns_receivers = owns_receivers.map { |c| c.to_s.dup.freeze }.freeze
|
|
67
71
|
end
|
|
72
|
+
# rubocop:enable Metrics/ParameterLists,Metrics/AbcSize
|
|
68
73
|
|
|
69
74
|
public
|
|
70
75
|
|
|
@@ -99,7 +104,8 @@ module Rigor
|
|
|
99
104
|
"protocols" => protocols.map(&:to_s),
|
|
100
105
|
"config_schema" => config_schema.to_h { |k, v| [k, v.to_s] },
|
|
101
106
|
"produces" => produces.map(&:to_s),
|
|
102
|
-
"consumes" => consumes.map { |c| consumption_hash(c) }
|
|
107
|
+
"consumes" => consumes.map { |c| consumption_hash(c) },
|
|
108
|
+
"owns_receivers" => owns_receivers
|
|
103
109
|
}
|
|
104
110
|
end
|
|
105
111
|
|
|
@@ -166,6 +172,21 @@ module Rigor
|
|
|
166
172
|
raise ArgumentError, "plugin manifest produces must be an Array of Symbol/String, got #{produces.inspect}"
|
|
167
173
|
end
|
|
168
174
|
|
|
175
|
+
# ADR-10 5a — `owns_receivers:` declares the class names
|
|
176
|
+
# this plugin claims sole ownership of. The dispatcher's
|
|
177
|
+
# dependency-source-inference tier consults this list
|
|
178
|
+
# before consulting its own catalog: receivers owned by a
|
|
179
|
+
# registered plugin (directly or via subclass) decline,
|
|
180
|
+
# so plugin contributions stay authoritative for those
|
|
181
|
+
# types.
|
|
182
|
+
def validate_owns_receivers!(owns_receivers)
|
|
183
|
+
return if owns_receivers.is_a?(Array) && owns_receivers.all? { |c| c.is_a?(String) && !c.empty? }
|
|
184
|
+
|
|
185
|
+
raise ArgumentError,
|
|
186
|
+
"plugin manifest owns_receivers must be an Array of non-empty String, " \
|
|
187
|
+
"got #{owns_receivers.inspect}"
|
|
188
|
+
end
|
|
189
|
+
|
|
169
190
|
def coerce_consumes(consumes)
|
|
170
191
|
unless consumes.is_a?(Array)
|
|
171
192
|
raise ArgumentError, "plugin manifest consumes must be an Array, got #{consumes.inspect}"
|
|
@@ -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`:
|
|
36
|
-
#
|
|
37
|
-
#
|
|
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/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/version.rb
CHANGED
data/sig/rigor/environment.rbs
CHANGED
|
@@ -7,11 +7,12 @@ module Rigor
|
|
|
7
7
|
attr_reader class_registry: ClassRegistry
|
|
8
8
|
attr_reader rbs_loader: RbsLoader?
|
|
9
9
|
attr_reader plugin_registry: untyped?
|
|
10
|
+
attr_reader dependency_source_index: untyped?
|
|
10
11
|
|
|
11
12
|
def self.default: () -> Environment
|
|
12
|
-
def self.for_project: (?root: String, ?libraries: Array[String], ?signature_paths: Array[String | _ToPath]?, ?cache_store: untyped?, ?plugin_registry: untyped?) -> Environment
|
|
13
|
+
def self.for_project: (?root: String, ?libraries: Array[String], ?signature_paths: Array[String | _ToPath]?, ?cache_store: untyped?, ?plugin_registry: untyped?, ?dependency_source_index: untyped?) -> Environment
|
|
13
14
|
|
|
14
|
-
def initialize: (?class_registry: ClassRegistry, ?rbs_loader: RbsLoader?, ?plugin_registry: untyped?) -> void
|
|
15
|
+
def initialize: (?class_registry: ClassRegistry, ?rbs_loader: RbsLoader?, ?plugin_registry: untyped?, ?dependency_source_index: untyped?) -> void
|
|
15
16
|
def nominal_for_name: (String | Symbol name) -> Type::Nominal?
|
|
16
17
|
def singleton_for_name: (String | Symbol name) -> Type::Singleton?
|
|
17
18
|
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
|
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.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rigor contributors
|
|
@@ -184,9 +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
|
|
190
|
+
- lib/rigor/analysis/dependency_source_inference.rb
|
|
191
|
+
- lib/rigor/analysis/dependency_source_inference/builder.rb
|
|
192
|
+
- lib/rigor/analysis/dependency_source_inference/gem_resolver.rb
|
|
193
|
+
- lib/rigor/analysis/dependency_source_inference/index.rb
|
|
194
|
+
- lib/rigor/analysis/dependency_source_inference/walker.rb
|
|
187
195
|
- lib/rigor/analysis/diagnostic.rb
|
|
188
196
|
- lib/rigor/analysis/fact_store.rb
|
|
189
197
|
- lib/rigor/analysis/result.rb
|
|
198
|
+
- lib/rigor/analysis/rule_catalog.rb
|
|
190
199
|
- lib/rigor/analysis/runner.rb
|
|
191
200
|
- lib/rigor/ast.rb
|
|
192
201
|
- lib/rigor/ast/type_node.rb
|
|
@@ -203,12 +212,15 @@ files:
|
|
|
203
212
|
- lib/rigor/cache/rbs_known_class_names.rb
|
|
204
213
|
- lib/rigor/cache/store.rb
|
|
205
214
|
- lib/rigor/cli.rb
|
|
215
|
+
- lib/rigor/cli/diff_command.rb
|
|
216
|
+
- lib/rigor/cli/explain_command.rb
|
|
206
217
|
- lib/rigor/cli/type_of_command.rb
|
|
207
218
|
- lib/rigor/cli/type_of_renderer.rb
|
|
208
219
|
- lib/rigor/cli/type_scan_command.rb
|
|
209
220
|
- lib/rigor/cli/type_scan_renderer.rb
|
|
210
221
|
- lib/rigor/cli/type_scan_report.rb
|
|
211
222
|
- lib/rigor/configuration.rb
|
|
223
|
+
- lib/rigor/configuration/dependencies.rb
|
|
212
224
|
- lib/rigor/configuration/severity_profile.rb
|
|
213
225
|
- lib/rigor/environment.rb
|
|
214
226
|
- lib/rigor/environment/class_registry.rb
|