low_type 0.7.4 → 0.8.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/lib/adapters/adapter_loader.rb +18 -0
- data/lib/adapters/sinatra_adapter.rb +100 -0
- data/lib/basic_types.rb +5 -0
- data/lib/error_types.rb +1 -0
- data/lib/interfaces/adapter_interface.rb +7 -0
- data/lib/{type_assignment.rb → local_types.rb} +17 -14
- data/lib/low_type.rb +21 -14
- data/lib/parser.rb +38 -7
- data/lib/redefiner.rb +4 -4
- data/lib/type_expression.rb +40 -9
- data/lib/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 98202b31e95d4d9d3e8199ef2a3c20ff2ff455b5267b453cc1eb8c15577dbe89
|
|
4
|
+
data.tar.gz: 02ede73b6243426beba35b1d03eb9cd0fe9421c8962b585ca7a07901b49fcdf9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e94e2e1a1e69ac8a7f812d6e08ea9bc9fc75be4b7f69f735880ca2a8664fda2a02212a6b49331b035e518d6bbfa8519f2e2eb3926630003b5140885c381b6a0
|
|
7
|
+
data.tar.gz: 25dfb56ff048028002d9636288314d2fb95d247d2d2b59a8da1fee2aa8b65e46449cc905437e45213e188cdbb7e8fff646e767f69e7fbfbd40f234a79f52d92f
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require_relative 'sinatra_adapter'
|
|
2
|
+
|
|
3
|
+
module LowType
|
|
4
|
+
class AdapterLoader
|
|
5
|
+
class << self
|
|
6
|
+
def load(klass:, parser:, file_path:)
|
|
7
|
+
adaptor = nil
|
|
8
|
+
|
|
9
|
+
ancestors = klass.ancestors.map(&:to_s)
|
|
10
|
+
adaptor = SinatraAdapter.new(klass:, parser:, file_path:) if ancestors.include?('Sinatra::Base')
|
|
11
|
+
|
|
12
|
+
return if adaptor.nil?
|
|
13
|
+
|
|
14
|
+
adaptor
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
require 'prism'
|
|
2
|
+
|
|
3
|
+
require_relative 'sinatra_return_proxy'
|
|
4
|
+
require_relative '../interfaces/adapter_interface'
|
|
5
|
+
require_relative '../proxies/file_proxy'
|
|
6
|
+
require_relative '../error_types'
|
|
7
|
+
|
|
8
|
+
module LowType
|
|
9
|
+
# We don't use https://sinatrarb.com/extensions.html because we need to type check all Ruby methods (not just Sinatra) at a lower level.
|
|
10
|
+
class SinatraAdapter < AdapterInterface
|
|
11
|
+
def initialize(klass:, parser:, file_path:)
|
|
12
|
+
@klass = klass
|
|
13
|
+
@parser = parser
|
|
14
|
+
@file_path = file_path
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def redefine_methods
|
|
18
|
+
method_calls = @parser.method_calls(method_names: [:get, :post, :patch, :put, :delete, :options, :query])
|
|
19
|
+
|
|
20
|
+
# Type check return values.
|
|
21
|
+
method_calls.each do |method_call|
|
|
22
|
+
arguments_node = method_call.compact_child_nodes.first
|
|
23
|
+
next unless arguments_node.is_a?(Prism::ArgumentsNode)
|
|
24
|
+
|
|
25
|
+
pattern = arguments_node.arguments.first.content
|
|
26
|
+
|
|
27
|
+
file = FileProxy.new(path: @file_path, line: method_call.start_line, scope: "#{@klass}##{method_call.name}")
|
|
28
|
+
params = [ParamProxy.new(type_expression: nil, name: :route, type: :req, position: 0, file:)]
|
|
29
|
+
return_proxy = return_proxy(method_node: method_call, pattern:, file:)
|
|
30
|
+
next unless return_proxy
|
|
31
|
+
|
|
32
|
+
route = "#{method_call.name.upcase} #{pattern}"
|
|
33
|
+
@klass.low_methods[route] = MethodProxy.new(name: method_call.name, params:, return_proxy:)
|
|
34
|
+
|
|
35
|
+
# We're in Sinatra now. Objects request/response are from Sinatra.
|
|
36
|
+
@klass.after pattern do
|
|
37
|
+
if (method_proxy = self.class.low_methods["#{request.request_method} #{pattern}"])
|
|
38
|
+
proxy = method_proxy.return_proxy
|
|
39
|
+
|
|
40
|
+
# Inclusive rather than exclusive validation. If one type/value is valid then there's no need to error.
|
|
41
|
+
valid_type = proxy.type_expression.types.any? do |type|
|
|
42
|
+
proxy.type_expression.validate(value: SinatraAdapter.reconstruct_return_value(type:, response:), proxy:)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# No valid types so let's return a server error to the client.
|
|
46
|
+
unless valid_type
|
|
47
|
+
proxy.type_expression.types.each do |type|
|
|
48
|
+
value = SinatraAdapter.reconstruct_return_value(type:, response:)
|
|
49
|
+
unless proxy.type_expression.validate(value:, proxy:)
|
|
50
|
+
status(500)
|
|
51
|
+
body(proxy.error_message(value: value.inspect))
|
|
52
|
+
break
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The route's String/Array/Enumerable return value populates a Rack::Response object.
|
|
62
|
+
# This response also contains values added via Sinatra DSL's header()/body() methods.
|
|
63
|
+
# So reconstruct the return value from the response object, based on the return type.
|
|
64
|
+
def self.reconstruct_return_value(type:, response:)
|
|
65
|
+
valid_types = {
|
|
66
|
+
Integer => -> (response) { response.status },
|
|
67
|
+
String => -> (response) { response.body.first },
|
|
68
|
+
HTML => -> (response) { response.body.first },
|
|
69
|
+
JSON => -> (response) { response.body.first },
|
|
70
|
+
|
|
71
|
+
# TODO: Should these be Enumerable[T] instead? How would we match a Module of a class in a hash key?
|
|
72
|
+
# NOTE: These keys represent types, not type expressions.
|
|
73
|
+
# A type lives inside a type expression and is actually an instance representing that type.
|
|
74
|
+
[String] => -> (response) { response.body },
|
|
75
|
+
[Integer, String] => -> (response) { [response.status, *response.body] },
|
|
76
|
+
[Integer, Hash, String] => -> (response) { [response.status, response.headers, *response.body] },
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
raise AllowedTypeError, 'Did you mean "Response.finish"?' if type.to_s == 'Response'
|
|
80
|
+
|
|
81
|
+
if (reconstructed_value = valid_types[type])
|
|
82
|
+
return reconstructed_value.call(response)
|
|
83
|
+
else
|
|
84
|
+
raise AllowedTypeError, "Valid Sinatra return types: #{valid_types.keys.map(&:to_s).join(' | ')}"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def return_proxy(method_node:, pattern:, file:)
|
|
91
|
+
return_type = Parser.return_type(method_node:)
|
|
92
|
+
return nil if return_type.nil?
|
|
93
|
+
|
|
94
|
+
expression = eval(return_type.slice).call
|
|
95
|
+
expression = TypeExpression.new(type: expression) unless TypeExpression === expression
|
|
96
|
+
|
|
97
|
+
SinatraReturnProxy.new(type_expression: expression, name: "#{method_node.name.upcase} #{pattern}", file:)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
data/lib/basic_types.rb
ADDED
data/lib/error_types.rb
CHANGED
|
@@ -3,24 +3,24 @@ require_relative 'proxies/local_proxy'
|
|
|
3
3
|
require_relative 'type_expression'
|
|
4
4
|
require_relative 'value_expression'
|
|
5
5
|
|
|
6
|
-
module
|
|
6
|
+
module LocalTypes
|
|
7
7
|
class AssignmentError < StandardError; end
|
|
8
8
|
|
|
9
9
|
def type(type_expression)
|
|
10
|
-
|
|
10
|
+
referenced_object = type_expression.default_value
|
|
11
11
|
|
|
12
|
-
if !LowType.value?(
|
|
13
|
-
raise AssignmentError, "Single-instance objects like #{
|
|
12
|
+
if !LowType.value?(referenced_object)
|
|
13
|
+
raise AssignmentError, "Single-instance objects like #{referenced_object} are not supported"
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
last_caller = caller_locations(1, 1).first
|
|
17
17
|
file = LowType::FileProxy.new(path: last_caller.path, line: last_caller.lineno, scope: 'local type')
|
|
18
18
|
local_proxy = LowType::LocalProxy.new(type_expression:, name: self, file:)
|
|
19
|
-
|
|
19
|
+
referenced_object.instance_variable_set('@local_proxy', local_proxy)
|
|
20
20
|
|
|
21
|
-
type_expression.validate!(value:
|
|
21
|
+
type_expression.validate!(value: referenced_object, proxy: local_proxy)
|
|
22
22
|
|
|
23
|
-
def
|
|
23
|
+
def referenced_object.with_type=(value)
|
|
24
24
|
local_proxy = self.instance_variable_get('@local_proxy')
|
|
25
25
|
type_expression = local_proxy.type_expression
|
|
26
26
|
type_expression.validate!(value:, proxy: local_proxy)
|
|
@@ -33,9 +33,9 @@ module TypeAssignment
|
|
|
33
33
|
self
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
return
|
|
36
|
+
return referenced_object.value if referenced_object.is_a?(ValueExpression)
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
referenced_object
|
|
39
39
|
end
|
|
40
40
|
alias_method :low_type, :type
|
|
41
41
|
|
|
@@ -43,19 +43,22 @@ module TypeAssignment
|
|
|
43
43
|
LowType.value(type:)
|
|
44
44
|
end
|
|
45
45
|
alias_method :low_value, :value
|
|
46
|
-
end
|
|
47
46
|
|
|
48
|
-
module
|
|
47
|
+
# Scoped to the class that includes LowTypes module.
|
|
49
48
|
class Array < ::Array
|
|
50
|
-
def self.[](
|
|
51
|
-
|
|
49
|
+
def self.[](*types)
|
|
50
|
+
if types.all? { |type| LowType.type?(type) }
|
|
51
|
+
return LowType::TypeExpression.new(type: [*types])
|
|
52
|
+
end
|
|
53
|
+
|
|
52
54
|
super
|
|
53
55
|
end
|
|
54
56
|
end
|
|
55
57
|
|
|
58
|
+
# Scoped to the class that includes LowTypes module.
|
|
56
59
|
class Hash < ::Hash
|
|
57
60
|
def self.[](type)
|
|
58
|
-
return TypeExpression.new(type:) if LowType.type?(type)
|
|
61
|
+
return LowType::TypeExpression.new(type:) if LowType.type?(type)
|
|
59
62
|
super
|
|
60
63
|
end
|
|
61
64
|
end
|
data/lib/low_type.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'adapters/adapter_loader'
|
|
4
|
+
require_relative 'basic_types'
|
|
3
5
|
require_relative 'redefiner'
|
|
4
6
|
require_relative 'type_expression'
|
|
5
7
|
require_relative 'value_expression'
|
|
@@ -10,22 +12,22 @@ module LowType
|
|
|
10
12
|
|
|
11
13
|
# Array[] class method returns a type expression only for the duration of this "included" hook.
|
|
12
14
|
array_class_method = Array.method('[]').unbind
|
|
13
|
-
Array.define_singleton_method('[]') do |
|
|
14
|
-
TypeExpression.new(type: [
|
|
15
|
+
Array.define_singleton_method('[]') do |*types|
|
|
16
|
+
TypeExpression.new(type: [*types])
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
# Hash[] class method returns a type expression only for the duration of this "included" hook.
|
|
18
20
|
hash_class_method = Hash.method('[]').unbind
|
|
19
|
-
Hash.define_singleton_method('[]') do |
|
|
21
|
+
Hash.define_singleton_method('[]') do |type|
|
|
20
22
|
# Support Pry which uses Hash[].
|
|
21
|
-
unless LowType.type?(
|
|
23
|
+
unless LowType.type?(type)
|
|
22
24
|
Hash.define_singleton_method('[]', hash_class_method)
|
|
23
|
-
result = Hash[
|
|
25
|
+
result = Hash[type]
|
|
24
26
|
Hash.method('[]').unbind
|
|
25
27
|
return result
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
TypeExpression.new(type:
|
|
30
|
+
TypeExpression.new(type:)
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
class << klass
|
|
@@ -40,6 +42,9 @@ module LowType
|
|
|
40
42
|
|
|
41
43
|
klass.prepend LowType::Redefiner.redefine_methods(method_nodes: parser.instance_methods, klass:, private_start_line:, file_path:)
|
|
42
44
|
klass.singleton_class.prepend LowType::Redefiner.redefine_methods(method_nodes: parser.class_methods, klass:, private_start_line:, file_path:)
|
|
45
|
+
|
|
46
|
+
adapter = AdapterLoader.load(klass:, parser:, file_path:)
|
|
47
|
+
adapter.redefine_methods if adapter
|
|
43
48
|
ensure
|
|
44
49
|
Array.define_singleton_method('[]', array_class_method)
|
|
45
50
|
Hash.define_singleton_method('[]', hash_class_method)
|
|
@@ -49,27 +54,31 @@ module LowType
|
|
|
49
54
|
# Public API.
|
|
50
55
|
|
|
51
56
|
def config
|
|
52
|
-
config = Struct.new(:
|
|
57
|
+
config = Struct.new(:local_types, :deep_type_check)
|
|
53
58
|
@config ||= config.new(false, false)
|
|
54
59
|
end
|
|
55
60
|
|
|
56
61
|
def configure
|
|
57
62
|
yield(config)
|
|
58
63
|
|
|
59
|
-
if config.
|
|
60
|
-
require_relative '
|
|
61
|
-
include
|
|
64
|
+
if config.local_types
|
|
65
|
+
require_relative 'local_types'
|
|
66
|
+
include LocalTypes
|
|
62
67
|
end
|
|
63
68
|
end
|
|
64
69
|
|
|
65
70
|
# Internal API.
|
|
66
71
|
|
|
67
72
|
def file_path(klass:)
|
|
68
|
-
|
|
73
|
+
# Remove module namespaces from class.
|
|
74
|
+
class_name = klass.to_s.split(':').last
|
|
75
|
+
# The first class found regardless of namespace will be the class that did the include.
|
|
76
|
+
caller.find { |callee| callee.end_with?("<class:#{class_name}>'") }.split(':').first
|
|
69
77
|
end
|
|
70
78
|
|
|
79
|
+
# TODO: Unit test this.
|
|
71
80
|
def type?(type)
|
|
72
|
-
type.respond_to?(:new) || type == Integer || (type.is_a?(::Hash) && type.keys.first.respond_to?(:new) && type.values.first.respond_to?(:new))
|
|
81
|
+
type.respond_to?(:new) || type == Integer || type == Symbol || (type.is_a?(::Hash) && type.keys.first.respond_to?(:new) && type.values.first.respond_to?(:new))
|
|
73
82
|
end
|
|
74
83
|
|
|
75
84
|
def value?(expression)
|
|
@@ -80,6 +89,4 @@ module LowType
|
|
|
80
89
|
TypeExpression.new(default_value: ValueExpression.new(value: type))
|
|
81
90
|
end
|
|
82
91
|
end
|
|
83
|
-
|
|
84
|
-
class Boolean; end # TrueClass or FalseClass
|
|
85
92
|
end
|
data/lib/parser.rb
CHANGED
|
@@ -5,23 +5,37 @@ module LowType
|
|
|
5
5
|
attr_reader :parent_map, :instance_methods, :class_methods, :private_start_line
|
|
6
6
|
|
|
7
7
|
def initialize(file_path:)
|
|
8
|
-
root_node = Prism.parse_file(file_path).value
|
|
8
|
+
@root_node = Prism.parse_file(file_path).value
|
|
9
9
|
|
|
10
10
|
parent_mapper = ParentMapper.new
|
|
11
|
-
parent_mapper.visit(root_node)
|
|
11
|
+
parent_mapper.visit(@root_node)
|
|
12
12
|
@parent_map = parent_mapper.parent_map
|
|
13
13
|
|
|
14
|
-
method_visitor =
|
|
15
|
-
root_node.accept(method_visitor)
|
|
14
|
+
method_visitor = MethodDefVisitor.new(@parent_map)
|
|
15
|
+
@root_node.accept(method_visitor)
|
|
16
16
|
|
|
17
17
|
@instance_methods = method_visitor.instance_methods
|
|
18
18
|
@class_methods = method_visitor.class_methods
|
|
19
19
|
@private_start_line = method_visitor.private_start_line
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def
|
|
23
|
-
|
|
22
|
+
def method_calls(method_names:)
|
|
23
|
+
block_visitor = MethodCallVisitor.new(parent_map: @parent_map, method_names:)
|
|
24
|
+
@root_node.accept(block_visitor)
|
|
25
|
+
block_visitor.method_calls
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Only a lambda defined immediately after a method's parameters/block is considered a return type expression.
|
|
29
|
+
def self.return_type(method_node:)
|
|
30
|
+
# Method statements.
|
|
24
31
|
statements_node = method_node.compact_child_nodes.find { |node| node.is_a?(Prism::StatementsNode) }
|
|
32
|
+
|
|
33
|
+
# Block statements.
|
|
34
|
+
if statements_node.nil?
|
|
35
|
+
block_node = method_node.compact_child_nodes.find { |node| node.is_a?(Prism::BlockNode) }
|
|
36
|
+
statements_node = block_node.compact_child_nodes.find { |node| node.is_a?(Prism::StatementsNode) } if block_node
|
|
37
|
+
end
|
|
38
|
+
|
|
25
39
|
return nil if statements_node.nil? # Sometimes developers define methods without code inside them.
|
|
26
40
|
|
|
27
41
|
node = statements_node.body.first
|
|
@@ -31,7 +45,7 @@ module LowType
|
|
|
31
45
|
end
|
|
32
46
|
end
|
|
33
47
|
|
|
34
|
-
class
|
|
48
|
+
class MethodDefVisitor < Prism::Visitor
|
|
35
49
|
attr_reader :class_methods, :instance_methods, :private_start_line
|
|
36
50
|
|
|
37
51
|
def initialize(parent_map)
|
|
@@ -70,6 +84,23 @@ module LowType
|
|
|
70
84
|
end
|
|
71
85
|
end
|
|
72
86
|
|
|
87
|
+
class MethodCallVisitor < Prism::Visitor
|
|
88
|
+
attr_reader :method_calls
|
|
89
|
+
|
|
90
|
+
def initialize(parent_map:, method_names:)
|
|
91
|
+
@parent_map = parent_map
|
|
92
|
+
@method_names = method_names
|
|
93
|
+
|
|
94
|
+
@method_calls = []
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def visit_call_node(node)
|
|
98
|
+
@method_calls << node if @method_names.include?(node.name)
|
|
99
|
+
|
|
100
|
+
super # Continue walking the tree.
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
73
104
|
class ParentMapper < Prism::Visitor
|
|
74
105
|
attr_reader :parent_map
|
|
75
106
|
|
data/lib/redefiner.rb
CHANGED
|
@@ -79,7 +79,7 @@ module LowType
|
|
|
79
79
|
RUBY
|
|
80
80
|
)
|
|
81
81
|
|
|
82
|
-
#
|
|
82
|
+
# Called with only required args present (as nil) and optional args omitted, to evaluate type expressions (which are stored as default values).
|
|
83
83
|
typed_method.call(*required_args, **required_kwargs)
|
|
84
84
|
|
|
85
85
|
# TODO: Write spec for this.
|
|
@@ -88,10 +88,10 @@ module LowType
|
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
def return_proxy(method_node:, file:)
|
|
91
|
-
|
|
92
|
-
return nil if
|
|
91
|
+
return_type = Parser.return_type(method_node:)
|
|
92
|
+
return nil if return_type.nil?
|
|
93
93
|
|
|
94
|
-
expression = eval(
|
|
94
|
+
expression = eval(return_type.slice).call
|
|
95
95
|
expression = TypeExpression.new(type: expression) unless expression.is_a?(TypeExpression)
|
|
96
96
|
|
|
97
97
|
ReturnProxy.new(type_expression: expression, name: method_node.name, file:)
|
data/lib/type_expression.rb
CHANGED
|
@@ -29,6 +29,26 @@ module LowType
|
|
|
29
29
|
@default_value == :LOW_TYPE_UNDEFINED
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# Called in situations where we want to be inclusive rather than exclusive and pass validation if one of the types is valid.
|
|
33
|
+
def validate(value:, proxy:)
|
|
34
|
+
if value.nil?
|
|
35
|
+
return true if @default_value.nil?
|
|
36
|
+
return false if required?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@types.each do |type|
|
|
40
|
+
return true if LowType.type?(type) && type <= value.class # Example: HTML is a subclass of String and should pass as a String.
|
|
41
|
+
|
|
42
|
+
# TODO: Shallow validation of enumerables could be made deeper with user config.
|
|
43
|
+
return true if type.class == ::Array && value.class == ::Array && type.first == value.first.class
|
|
44
|
+
if type.class == ::Hash && value.class == ::Hash && type.keys[0] == value.keys[0].class && type.values[0] == value.values[0].class
|
|
45
|
+
return true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
false
|
|
50
|
+
end
|
|
51
|
+
|
|
32
52
|
def validate!(value:, proxy:)
|
|
33
53
|
if value.nil?
|
|
34
54
|
return true if @default_value.nil?
|
|
@@ -37,8 +57,9 @@ module LowType
|
|
|
37
57
|
|
|
38
58
|
@types.each do |type|
|
|
39
59
|
return true if LowType.type?(type) && type == value.class
|
|
40
|
-
|
|
41
|
-
|
|
60
|
+
return true if type.class == ::Array && value.class == ::Array && array_types_match_values?(types: type, values: value)
|
|
61
|
+
|
|
62
|
+
# TODO: Shallow validation of hash could be made deeper with user config.
|
|
42
63
|
if type.class == ::Hash && value.class == ::Hash && type.keys[0] == value.keys[0].class && type.values[0] == value.values[0].class
|
|
43
64
|
return true
|
|
44
65
|
end
|
|
@@ -50,6 +71,23 @@ module LowType
|
|
|
50
71
|
raise proxy.error_type, e.message, backtrace_with_proxy(file_paths:, backtrace: e.backtrace, proxy:)
|
|
51
72
|
end
|
|
52
73
|
|
|
74
|
+
def valid_types
|
|
75
|
+
types = @types.map { |type| type.inspect.to_s }
|
|
76
|
+
types = types + ['nil'] if @default_value.nil?
|
|
77
|
+
types.join(' | ')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def array_types_match_values?(types:, values:)
|
|
83
|
+
# TODO: Probably better to use an each that breaks early when types run out.
|
|
84
|
+
types.zip(values) do |type, value|
|
|
85
|
+
return false unless type === value
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
true
|
|
89
|
+
end
|
|
90
|
+
|
|
53
91
|
def backtrace_with_proxy(file_paths:, proxy:, backtrace:)
|
|
54
92
|
# Remove LowType file paths from the backtrace.
|
|
55
93
|
filtered_backtrace = backtrace.reject { |line| file_paths.find { |file_path| line.include?(file_path) } }
|
|
@@ -61,13 +99,6 @@ module LowType
|
|
|
61
99
|
|
|
62
100
|
[proxy_file_backtrace, *filtered_backtrace]
|
|
63
101
|
end
|
|
64
|
-
|
|
65
|
-
def valid_types
|
|
66
|
-
types = @types.map { |type| type.inspect.to_s }
|
|
67
|
-
types = types + ['nil'] if @default_value.nil?
|
|
68
|
-
|
|
69
|
-
types.join(' | ')
|
|
70
|
-
end
|
|
71
102
|
end
|
|
72
103
|
end
|
|
73
104
|
|
data/lib/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: low_type
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
@@ -17,8 +17,13 @@ executables: []
|
|
|
17
17
|
extensions: []
|
|
18
18
|
extra_rdoc_files: []
|
|
19
19
|
files:
|
|
20
|
+
- lib/adapters/adapter_loader.rb
|
|
21
|
+
- lib/adapters/sinatra_adapter.rb
|
|
22
|
+
- lib/basic_types.rb
|
|
20
23
|
- lib/error_types.rb
|
|
24
|
+
- lib/interfaces/adapter_interface.rb
|
|
21
25
|
- lib/interfaces/error_interface.rb
|
|
26
|
+
- lib/local_types.rb
|
|
22
27
|
- lib/low_type.rb
|
|
23
28
|
- lib/parser.rb
|
|
24
29
|
- lib/proxies/file_proxy.rb
|
|
@@ -27,7 +32,6 @@ files:
|
|
|
27
32
|
- lib/proxies/param_proxy.rb
|
|
28
33
|
- lib/proxies/return_proxy.rb
|
|
29
34
|
- lib/redefiner.rb
|
|
30
|
-
- lib/type_assignment.rb
|
|
31
35
|
- lib/type_expression.rb
|
|
32
36
|
- lib/value_expression.rb
|
|
33
37
|
- lib/version.rb
|