rigortype 0.0.2 → 0.0.4
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 +24 -7
- data/data/builtins/ruby_core/array.yml +1470 -0
- data/data/builtins/ruby_core/file.yml +501 -0
- data/data/builtins/ruby_core/hash.yml +936 -0
- data/data/builtins/ruby_core/io.yml +1594 -0
- data/data/builtins/ruby_core/numeric.yml +1809 -0
- data/data/builtins/ruby_core/range.yml +389 -0
- data/data/builtins/ruby_core/set.yml +594 -0
- data/data/builtins/ruby_core/string.yml +1850 -0
- data/data/builtins/ruby_core/time.yml +750 -0
- data/lib/rigor/analysis/check_rules.rb +97 -4
- data/lib/rigor/analysis/runner.rb +4 -0
- data/lib/rigor/builtins/imported_refinements.rb +251 -0
- data/lib/rigor/configuration.rb +6 -1
- data/lib/rigor/inference/acceptance.rb +324 -6
- data/lib/rigor/inference/builtins/array_catalog.rb +46 -0
- data/lib/rigor/inference/builtins/hash_catalog.rb +40 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +90 -0
- data/lib/rigor/inference/builtins/numeric_catalog.rb +93 -0
- data/lib/rigor/inference/builtins/range_catalog.rb +46 -0
- data/lib/rigor/inference/builtins/set_catalog.rb +54 -0
- data/lib/rigor/inference/builtins/string_catalog.rb +39 -0
- data/lib/rigor/inference/builtins/time_catalog.rb +64 -0
- data/lib/rigor/inference/expression_typer.rb +48 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +670 -16
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +144 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +215 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +23 -7
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +240 -4
- data/lib/rigor/inference/method_dispatcher.rb +28 -21
- data/lib/rigor/inference/method_parameter_binder.rb +29 -4
- data/lib/rigor/inference/narrowing.rb +376 -4
- data/lib/rigor/inference/scope_indexer.rb +10 -2
- data/lib/rigor/inference/statement_evaluator.rb +213 -2
- data/lib/rigor/rbs_extended.rb +230 -15
- data/lib/rigor/scope.rb +14 -0
- data/lib/rigor/type/combinator.rb +159 -1
- data/lib/rigor/type/difference.rb +155 -0
- data/lib/rigor/type/integer_range.rb +137 -0
- data/lib/rigor/type/intersection.rb +135 -0
- data/lib/rigor/type/refined.rb +174 -0
- data/lib/rigor/type.rb +4 -0
- data/lib/rigor/version.rb +1 -1
- data/sig/rigor/rbs_extended.rbs +14 -0
- data/sig/rigor/scope.rbs +1 -0
- data/sig/rigor/type.rbs +91 -1
- metadata +25 -1
|
@@ -53,6 +53,7 @@ module Rigor
|
|
|
53
53
|
RULE_NIL_RECEIVER = "possible-nil-receiver"
|
|
54
54
|
RULE_DUMP_TYPE = "dump-type"
|
|
55
55
|
RULE_ASSERT_TYPE = "assert-type"
|
|
56
|
+
RULE_ALWAYS_RAISES = "always-raises"
|
|
56
57
|
|
|
57
58
|
ALL_RULES = [
|
|
58
59
|
RULE_UNDEFINED_METHOD,
|
|
@@ -60,7 +61,8 @@ module Rigor
|
|
|
60
61
|
RULE_ARGUMENT_TYPE,
|
|
61
62
|
RULE_NIL_RECEIVER,
|
|
62
63
|
RULE_DUMP_TYPE,
|
|
63
|
-
RULE_ASSERT_TYPE
|
|
64
|
+
RULE_ASSERT_TYPE,
|
|
65
|
+
RULE_ALWAYS_RAISES
|
|
64
66
|
].freeze
|
|
65
67
|
|
|
66
68
|
module_function
|
|
@@ -97,6 +99,9 @@ module Rigor
|
|
|
97
99
|
|
|
98
100
|
assert_diagnostic = assert_type_diagnostic(path, node, scope_index)
|
|
99
101
|
diagnostics << assert_diagnostic if assert_diagnostic
|
|
102
|
+
|
|
103
|
+
raises_diagnostic = always_raises_diagnostic(path, node, scope_index)
|
|
104
|
+
diagnostics << raises_diagnostic if raises_diagnostic
|
|
100
105
|
end
|
|
101
106
|
filter_suppressed(diagnostics, comments: comments, disabled_rules: disabled_rules)
|
|
102
107
|
end
|
|
@@ -333,6 +338,13 @@ module Rigor
|
|
|
333
338
|
end
|
|
334
339
|
|
|
335
340
|
def arity_eligible?(function)
|
|
341
|
+
# `RBS::Types::UntypedFunction` (used for `(?) ->`
|
|
342
|
+
# untyped sigs) does not expose the per-arity
|
|
343
|
+
# accessors. Treating it as ineligible is the
|
|
344
|
+
# correct conservative move: an untyped function
|
|
345
|
+
# has no static arity to enforce.
|
|
346
|
+
return false unless function.respond_to?(:required_keywords)
|
|
347
|
+
|
|
336
348
|
function.required_keywords.empty? && function.trailing_positionals.empty?
|
|
337
349
|
end
|
|
338
350
|
|
|
@@ -562,6 +574,74 @@ module Rigor
|
|
|
562
574
|
)
|
|
563
575
|
end
|
|
564
576
|
|
|
577
|
+
# Diagnoses calls that the analyzer can prove will always
|
|
578
|
+
# raise. Today the only triggering shape is integer
|
|
579
|
+
# division/modulo by a literal zero divisor:
|
|
580
|
+
#
|
|
581
|
+
# 5 / 0 # => ZeroDivisionError
|
|
582
|
+
# x.modulo(0) # => ZeroDivisionError when x: Integer
|
|
583
|
+
# xs.size % 0 # same — non_negative_int / Constant[0]
|
|
584
|
+
#
|
|
585
|
+
# Float divmod by zero returns Infinity/NaN at runtime, so
|
|
586
|
+
# the rule restricts to Integer-rooted receivers (`Constant`,
|
|
587
|
+
# `IntegerRange`, `Nominal[Integer]`). The argument MUST be a
|
|
588
|
+
# `Constant<Integer>` whose value is exactly zero — a
|
|
589
|
+
# `Union[Constant[0], Constant[2]]` divisor "may" raise,
|
|
590
|
+
# which we surface separately (future slice).
|
|
591
|
+
INTEGER_RAISING_OPERATORS = %i[/ % div modulo divmod].freeze
|
|
592
|
+
private_constant :INTEGER_RAISING_OPERATORS
|
|
593
|
+
|
|
594
|
+
def always_raises_diagnostic(path, call_node, scope_index)
|
|
595
|
+
return nil unless integer_zero_division?(call_node, scope_index)
|
|
596
|
+
|
|
597
|
+
build_always_raises_diagnostic(path, call_node)
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
def integer_zero_division?(call_node, scope_index)
|
|
601
|
+
return false unless raising_call_shape?(call_node)
|
|
602
|
+
|
|
603
|
+
scope = scope_index[call_node]
|
|
604
|
+
return false if scope.nil?
|
|
605
|
+
return false unless integer_rooted_for_diagnostic?(scope.type_of(call_node.receiver))
|
|
606
|
+
|
|
607
|
+
arg = single_argument(call_node)
|
|
608
|
+
arg && integer_zero_constant?(scope.type_of(arg))
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
def raising_call_shape?(call_node)
|
|
612
|
+
!call_node.receiver.nil? && INTEGER_RAISING_OPERATORS.include?(call_node.name)
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
def single_argument(call_node)
|
|
616
|
+
args = call_node.arguments&.arguments || []
|
|
617
|
+
args.size == 1 ? args.first : nil
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
def integer_rooted_for_diagnostic?(type)
|
|
621
|
+
case type
|
|
622
|
+
when Type::Constant then type.value.is_a?(Integer)
|
|
623
|
+
when Type::IntegerRange then true
|
|
624
|
+
when Type::Nominal then type.class_name == "Integer" && type.type_args.empty?
|
|
625
|
+
else false
|
|
626
|
+
end
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
def integer_zero_constant?(type)
|
|
630
|
+
type.is_a?(Type::Constant) && type.value.is_a?(Integer) && type.value.zero?
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
def build_always_raises_diagnostic(path, call_node)
|
|
634
|
+
location = call_node.message_loc || call_node.location
|
|
635
|
+
Diagnostic.new(
|
|
636
|
+
rule: RULE_ALWAYS_RAISES,
|
|
637
|
+
path: path,
|
|
638
|
+
line: location.start_line,
|
|
639
|
+
column: location.start_column + 1,
|
|
640
|
+
message: "always raises ZeroDivisionError: `#{call_node.name}' by zero on Integer receiver",
|
|
641
|
+
severity: :error
|
|
642
|
+
)
|
|
643
|
+
end
|
|
644
|
+
|
|
565
645
|
# v0.0.2 #4 — argument-type-mismatch diagnostic.
|
|
566
646
|
# Walks a call's positional arguments and checks each
|
|
567
647
|
# against the matching parameter's RBS type via
|
|
@@ -613,14 +693,16 @@ module Rigor
|
|
|
613
693
|
return nil if method_def.nil? || method_def == true
|
|
614
694
|
return nil unless method_def.method_types.size == 1
|
|
615
695
|
|
|
616
|
-
|
|
696
|
+
param_overrides = Rigor::RbsExtended.param_type_override_map(method_def)
|
|
697
|
+
mismatch = first_argument_mismatch(method_def.method_types.first, call_node, scope, param_overrides)
|
|
617
698
|
return nil if mismatch.nil?
|
|
618
699
|
|
|
619
700
|
build_argument_type_diagnostic(path, call_node, class_name, mismatch)
|
|
620
701
|
end
|
|
621
702
|
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
|
622
703
|
|
|
623
|
-
|
|
704
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
|
705
|
+
def first_argument_mismatch(method_type, call_node, scope, param_overrides)
|
|
624
706
|
function = method_type.type
|
|
625
707
|
return nil unless argument_check_eligible?(function)
|
|
626
708
|
|
|
@@ -630,7 +712,12 @@ module Rigor
|
|
|
630
712
|
param = params[index]
|
|
631
713
|
next if param.nil? # arity mismatch is the wrong-arity rule's concern.
|
|
632
714
|
|
|
633
|
-
|
|
715
|
+
# `rigor:v1:param: <name> <refinement>` annotations
|
|
716
|
+
# tighten the RBS-declared parameter type. The
|
|
717
|
+
# override is the authoritative contract when
|
|
718
|
+
# present; otherwise we translate the RBS type as
|
|
719
|
+
# before.
|
|
720
|
+
param_type = param_overrides[param.name] || translate_param_type(param.type, scope.environment)
|
|
634
721
|
next if param_type.is_a?(Type::Dynamic) || param_type.is_a?(Type::Top)
|
|
635
722
|
|
|
636
723
|
arg_type = scope.type_of(arg)
|
|
@@ -641,8 +728,14 @@ module Rigor
|
|
|
641
728
|
end
|
|
642
729
|
nil
|
|
643
730
|
end
|
|
731
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
|
644
732
|
|
|
645
733
|
def argument_check_eligible?(function)
|
|
734
|
+
# See `arity_eligible?`: `UntypedFunction` lacks
|
|
735
|
+
# the per-arity accessors. Treat it as ineligible
|
|
736
|
+
# for argument-type-mismatch diagnostics.
|
|
737
|
+
return false unless function.respond_to?(:required_keywords)
|
|
738
|
+
|
|
646
739
|
function.rest_positionals.nil? &&
|
|
647
740
|
function.required_keywords.empty? &&
|
|
648
741
|
function.optional_keywords.empty? &&
|
|
@@ -6,6 +6,7 @@ require_relative "../environment"
|
|
|
6
6
|
require_relative "../scope"
|
|
7
7
|
require_relative "../inference/coverage_scanner"
|
|
8
8
|
require_relative "../inference/scope_indexer"
|
|
9
|
+
require_relative "../inference/method_dispatcher/file_folding"
|
|
9
10
|
require_relative "check_rules"
|
|
10
11
|
require_relative "diagnostic"
|
|
11
12
|
require_relative "result"
|
|
@@ -29,6 +30,9 @@ module Rigor
|
|
|
29
30
|
# is built once at run start through `Environment.for_project`
|
|
30
31
|
# so all files share the same RBS load.
|
|
31
32
|
def run(paths = @configuration.paths)
|
|
33
|
+
Inference::MethodDispatcher::FileFolding.fold_platform_specific_paths =
|
|
34
|
+
@configuration.fold_platform_specific_paths
|
|
35
|
+
|
|
32
36
|
environment = Environment.for_project(
|
|
33
37
|
libraries: @configuration.libraries,
|
|
34
38
|
signature_paths: @configuration.signature_paths
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "strscan"
|
|
4
|
+
|
|
5
|
+
require_relative "../type"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Builtins
|
|
9
|
+
# Canonical-name registry for the imported-built-in
|
|
10
|
+
# refinement catalogue. See `imported-built-in-types.md`
|
|
11
|
+
# in `docs/type-specification/` for the full catalogue
|
|
12
|
+
# rationale and the kebab-case naming rule.
|
|
13
|
+
#
|
|
14
|
+
# Maps kebab-case names (`non-empty-string`, `positive-int`,
|
|
15
|
+
# `non-empty-array`, …) to the Rigor type each name denotes.
|
|
16
|
+
# The registry is the single integration point for:
|
|
17
|
+
#
|
|
18
|
+
# - The `rigor:v1:return:` RBS::Extended directive
|
|
19
|
+
# ([`Rigor::RbsExtended.read_return_type_override`](../rbs_extended.rb)),
|
|
20
|
+
# which overrides a method's RBS-declared return type
|
|
21
|
+
# with a refinement carrier.
|
|
22
|
+
# - Future `RBS::Extended` directives that accept a
|
|
23
|
+
# refinement name in any type position (`param:`,
|
|
24
|
+
# `assert: x is non-empty-string`, …).
|
|
25
|
+
# - The display side: `Type::Difference#describe` already
|
|
26
|
+
# recognises the same shapes and prints the kebab-case
|
|
27
|
+
# spelling without consulting the registry.
|
|
28
|
+
#
|
|
29
|
+
# Names not in the registry resolve to `nil`; callers
|
|
30
|
+
# decide whether to fall back to the RBS-declared type or
|
|
31
|
+
# raise a parse error.
|
|
32
|
+
#
|
|
33
|
+
# The registry covers two surfaces:
|
|
34
|
+
#
|
|
35
|
+
# - **No-argument refinement names** (`non-empty-string`,
|
|
36
|
+
# `non-zero-int`, `lowercase-string`, …) live in `REGISTRY`
|
|
37
|
+
# and resolve through `lookup(name)`.
|
|
38
|
+
# - **Parameterised refinement payloads** (`non-empty-array[Integer]`,
|
|
39
|
+
# `non-empty-hash[Symbol, Integer]`, `int<5, 10>`) are
|
|
40
|
+
# accepted by `parse(payload)`. The full grammar is documented
|
|
41
|
+
# on `Parser`. The two surfaces share `REGISTRY` for the
|
|
42
|
+
# no-arg head names; the parameterised head names live in
|
|
43
|
+
# `PARAMETERISED_TYPE_BUILDERS` (square-bracket form, type
|
|
44
|
+
# args) and `PARAMETERISED_INT_BUILDERS` (angle-bracket form,
|
|
45
|
+
# integer bounds).
|
|
46
|
+
module ImportedRefinements
|
|
47
|
+
REGISTRY = {
|
|
48
|
+
"non-empty-string" => -> { Type::Combinator.non_empty_string },
|
|
49
|
+
"non-zero-int" => -> { Type::Combinator.non_zero_int },
|
|
50
|
+
"non-empty-array" => -> { Type::Combinator.non_empty_array },
|
|
51
|
+
"non-empty-hash" => -> { Type::Combinator.non_empty_hash },
|
|
52
|
+
"positive-int" => -> { Type::Combinator.positive_int },
|
|
53
|
+
"non-negative-int" => -> { Type::Combinator.non_negative_int },
|
|
54
|
+
"negative-int" => -> { Type::Combinator.negative_int },
|
|
55
|
+
"non-positive-int" => -> { Type::Combinator.non_positive_int },
|
|
56
|
+
"lowercase-string" => -> { Type::Combinator.lowercase_string },
|
|
57
|
+
"uppercase-string" => -> { Type::Combinator.uppercase_string },
|
|
58
|
+
"numeric-string" => -> { Type::Combinator.numeric_string },
|
|
59
|
+
"decimal-int-string" => -> { Type::Combinator.decimal_int_string },
|
|
60
|
+
"octal-int-string" => -> { Type::Combinator.octal_int_string },
|
|
61
|
+
"hex-int-string" => -> { Type::Combinator.hex_int_string },
|
|
62
|
+
"non-empty-lowercase-string" => -> { Type::Combinator.non_empty_lowercase_string },
|
|
63
|
+
"non-empty-uppercase-string" => -> { Type::Combinator.non_empty_uppercase_string }
|
|
64
|
+
}.freeze
|
|
65
|
+
private_constant :REGISTRY
|
|
66
|
+
|
|
67
|
+
# `name[T]` / `name[K, V]` — type-arg parameterised
|
|
68
|
+
# refinements. Each builder takes an `Array<Rigor::Type>`
|
|
69
|
+
# and returns a `Rigor::Type` (or `nil` on arity / shape
|
|
70
|
+
# mismatch so the caller surfaces a parse failure).
|
|
71
|
+
PARAMETERISED_TYPE_BUILDERS = {
|
|
72
|
+
"non-empty-array" => lambda { |args|
|
|
73
|
+
return nil unless args.size == 1
|
|
74
|
+
|
|
75
|
+
Type::Combinator.non_empty_array(args.first)
|
|
76
|
+
},
|
|
77
|
+
"non-empty-hash" => lambda { |args|
|
|
78
|
+
return nil unless args.size == 2
|
|
79
|
+
|
|
80
|
+
Type::Combinator.non_empty_hash(args[0], args[1])
|
|
81
|
+
}
|
|
82
|
+
}.freeze
|
|
83
|
+
private_constant :PARAMETERISED_TYPE_BUILDERS
|
|
84
|
+
|
|
85
|
+
# `name<min, max>` — integer-bound parameterised
|
|
86
|
+
# refinements. Each builder takes an `Array<Integer>` and
|
|
87
|
+
# returns a `Rigor::Type` (or `nil`). Bounds are signed
|
|
88
|
+
# integer literals; `min` MUST be ≤ `max` for the carrier
|
|
89
|
+
# to construct successfully (`Type::IntegerRange` enforces
|
|
90
|
+
# the invariant).
|
|
91
|
+
PARAMETERISED_INT_BUILDERS = {
|
|
92
|
+
"int" => lambda { |bounds|
|
|
93
|
+
return nil unless bounds.size == 2
|
|
94
|
+
|
|
95
|
+
Type::Combinator.integer_range(bounds[0], bounds[1])
|
|
96
|
+
}
|
|
97
|
+
}.freeze
|
|
98
|
+
private_constant :PARAMETERISED_INT_BUILDERS
|
|
99
|
+
|
|
100
|
+
module_function
|
|
101
|
+
|
|
102
|
+
# @param name [String] kebab-case refinement name.
|
|
103
|
+
# @return [Rigor::Type, nil] the matching refinement
|
|
104
|
+
# carrier, or `nil` if the name is not registered.
|
|
105
|
+
def lookup(name)
|
|
106
|
+
builder = REGISTRY[name.to_s]
|
|
107
|
+
builder&.call
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @param payload [String] the trailing payload of a
|
|
111
|
+
# `rigor:v1:return:` (or sibling) directive. Accepts
|
|
112
|
+
# the bare-name forms `lookup` already handles plus the
|
|
113
|
+
# parameterised forms documented on {Parser}.
|
|
114
|
+
# @return [Rigor::Type, nil] the resolved refinement
|
|
115
|
+
# carrier, or `nil` when the payload is unparseable or
|
|
116
|
+
# names a refinement / class not in the registry.
|
|
117
|
+
def parse(payload)
|
|
118
|
+
Parser.new(payload.to_s).parse
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def known?(name)
|
|
122
|
+
REGISTRY.key?(name.to_s) ||
|
|
123
|
+
PARAMETERISED_TYPE_BUILDERS.key?(name.to_s) ||
|
|
124
|
+
PARAMETERISED_INT_BUILDERS.key?(name.to_s)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def known_names
|
|
128
|
+
REGISTRY.keys + PARAMETERISED_TYPE_BUILDERS.keys + PARAMETERISED_INT_BUILDERS.keys
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Recursive-descent parser for the refinement-payload
|
|
132
|
+
# grammar:
|
|
133
|
+
#
|
|
134
|
+
# type := simple_name | parametric
|
|
135
|
+
# simple_name := /[a-z][a-z0-9-]*/
|
|
136
|
+
# parametric := simple_name '[' type_arg_list ']'
|
|
137
|
+
# | simple_name '<' int_bound_list '>'
|
|
138
|
+
# type_arg_list := type_arg (',' type_arg)*
|
|
139
|
+
# type_arg := type | class_name
|
|
140
|
+
# class_name := /[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*/
|
|
141
|
+
# int_bound_list := signed_int (',' signed_int)*
|
|
142
|
+
# signed_int := /-?\d+/
|
|
143
|
+
#
|
|
144
|
+
# Whitespace between tokens is ignored. The parser fails
|
|
145
|
+
# soft (returns `nil` from `parse`) on any deviation so the
|
|
146
|
+
# `RBS::Extended` directive site can fall back to the
|
|
147
|
+
# RBS-declared type rather than crash on a typo.
|
|
148
|
+
class Parser
|
|
149
|
+
def initialize(input)
|
|
150
|
+
@scanner = StringScanner.new(input.strip)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def parse
|
|
154
|
+
type = parse_type
|
|
155
|
+
return nil if type.nil?
|
|
156
|
+
return nil unless @scanner.eos?
|
|
157
|
+
|
|
158
|
+
type
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
SIMPLE_NAME = /[a-z][a-z0-9-]*/
|
|
164
|
+
CLASS_NAME = /[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*/
|
|
165
|
+
SIGNED_INT = /-?\d+/
|
|
166
|
+
private_constant :SIMPLE_NAME, :CLASS_NAME, :SIGNED_INT
|
|
167
|
+
|
|
168
|
+
def parse_type
|
|
169
|
+
name = @scanner.scan(SIMPLE_NAME)
|
|
170
|
+
return nil if name.nil?
|
|
171
|
+
|
|
172
|
+
case @scanner.peek(1)
|
|
173
|
+
when "[" then parse_parametric_type_args(name)
|
|
174
|
+
when "<" then parse_parametric_int_bounds(name)
|
|
175
|
+
else ImportedRefinements.lookup(name)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def parse_parametric_type_args(name)
|
|
180
|
+
builder = PARAMETERISED_TYPE_BUILDERS[name]
|
|
181
|
+
return nil if builder.nil?
|
|
182
|
+
|
|
183
|
+
@scanner.getch # consume '['
|
|
184
|
+
args = parse_type_arg_list
|
|
185
|
+
return nil if args.nil?
|
|
186
|
+
return nil unless @scanner.getch == "]"
|
|
187
|
+
|
|
188
|
+
builder.call(args)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def parse_parametric_int_bounds(name)
|
|
192
|
+
builder = PARAMETERISED_INT_BUILDERS[name]
|
|
193
|
+
return nil if builder.nil?
|
|
194
|
+
|
|
195
|
+
@scanner.getch # consume '<'
|
|
196
|
+
bounds = parse_int_bound_list
|
|
197
|
+
return nil if bounds.nil?
|
|
198
|
+
return nil unless @scanner.getch == ">"
|
|
199
|
+
|
|
200
|
+
builder.call(bounds)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def parse_type_arg_list
|
|
204
|
+
collect_separated_list { parse_type_arg }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def parse_int_bound_list
|
|
208
|
+
collect_separated_list { parse_int_bound }
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def collect_separated_list
|
|
212
|
+
items = []
|
|
213
|
+
loop do
|
|
214
|
+
skip_ws
|
|
215
|
+
item = yield
|
|
216
|
+
return nil if item.nil?
|
|
217
|
+
|
|
218
|
+
items << item
|
|
219
|
+
skip_ws
|
|
220
|
+
break unless @scanner.peek(1) == ","
|
|
221
|
+
|
|
222
|
+
@scanner.getch # consume ','
|
|
223
|
+
end
|
|
224
|
+
items
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def parse_type_arg
|
|
228
|
+
skip_ws
|
|
229
|
+
if (class_name = @scanner.scan(CLASS_NAME))
|
|
230
|
+
Type::Combinator.nominal_of(class_name)
|
|
231
|
+
else
|
|
232
|
+
parse_type
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def parse_int_bound
|
|
237
|
+
skip_ws
|
|
238
|
+
literal = @scanner.scan(SIGNED_INT)
|
|
239
|
+
return nil if literal.nil?
|
|
240
|
+
|
|
241
|
+
Integer(literal)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def skip_ws
|
|
245
|
+
@scanner.skip(/\s+/)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
private_constant :Parser
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
data/lib/rigor/configuration.rb
CHANGED
|
@@ -12,13 +12,14 @@ module Rigor
|
|
|
12
12
|
"disable" => [],
|
|
13
13
|
"libraries" => [],
|
|
14
14
|
"signature_paths" => nil,
|
|
15
|
+
"fold_platform_specific_paths" => false,
|
|
15
16
|
"cache" => {
|
|
16
17
|
"path" => ".rigor/cache"
|
|
17
18
|
}
|
|
18
19
|
}.freeze
|
|
19
20
|
|
|
20
21
|
attr_reader :target_ruby, :paths, :plugins, :cache_path, :disabled_rules,
|
|
21
|
-
:libraries, :signature_paths
|
|
22
|
+
:libraries, :signature_paths, :fold_platform_specific_paths
|
|
22
23
|
|
|
23
24
|
def self.load(path = DEFAULT_PATH)
|
|
24
25
|
data = if File.exist?(path)
|
|
@@ -40,6 +41,9 @@ module Rigor
|
|
|
40
41
|
@libraries = Array(data.fetch("libraries", DEFAULTS.fetch("libraries"))).map(&:to_s).freeze
|
|
41
42
|
sig_paths = data.fetch("signature_paths", DEFAULTS.fetch("signature_paths"))
|
|
42
43
|
@signature_paths = sig_paths.nil? ? nil : Array(sig_paths).map(&:to_s).freeze
|
|
44
|
+
@fold_platform_specific_paths = data.fetch(
|
|
45
|
+
"fold_platform_specific_paths", DEFAULTS.fetch("fold_platform_specific_paths")
|
|
46
|
+
) == true
|
|
43
47
|
@cache_path = cache.fetch("path").to_s
|
|
44
48
|
end
|
|
45
49
|
|
|
@@ -51,6 +55,7 @@ module Rigor
|
|
|
51
55
|
"disable" => disabled_rules,
|
|
52
56
|
"libraries" => libraries,
|
|
53
57
|
"signature_paths" => signature_paths,
|
|
58
|
+
"fold_platform_specific_paths" => fold_platform_specific_paths,
|
|
54
59
|
"cache" => {
|
|
55
60
|
"path" => cache_path
|
|
56
61
|
}
|