low_type 1.1.8 → 1.1.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 295c067ff64ba1d3ddb26243c1676a7bbe7d3a41fb65c4c51e868462a44e4f1b
4
- data.tar.gz: 2cff7d3f4f7f1dd8f6df9ab48e34737f562929cb802f798cf497a377034eee16
3
+ metadata.gz: 372ec6574de86edfe13eba87a457e290934635e88b092a711798df69d04aaa30
4
+ data.tar.gz: 3afaccd3fed8508ca0064cc9552b80bd05cf7b09ecfdd1906a93a043034ec978
5
5
  SHA512:
6
- metadata.gz: 1f697cb2610e1acb2bc89a41ec35cd1f78a381fea7f3fed90c1984166d4ae514cffb8adf4ba6ef4916cd32897b85ba22e4ee84d26e4b187cb21657d7dedc6553
7
- data.tar.gz: 54725a86fa96ac6bbf286d6c28d8a1af06090b64f1d75e5fe4ee21e398078d592aae71dd39b6c9d6170584f9ca4bd86175c827bf6d9ea1aaa0240540035080f7
6
+ metadata.gz: d59f9cf98881584fb19d3ca79748d67fdc71c5a9b55ff4ff3c4324c5658cedc88567b4037876e1d8ed5a087aafb76deff459f9493f2194ccbf002ff855a1f88b
7
+ data.tar.gz: be0a567f9212cb628c7efe852f71901ea4495cb9eb75681a259e68e7ef2fa545cfdfa8df0b01e4774872158db607f57fd950f274a4dcad32910c87f743de6208
@@ -6,11 +6,11 @@ module Low
6
6
  module Adapter
7
7
  class Loader
8
8
  class << self
9
- def load(klass:, parser:, file_path:)
9
+ def load(klass:, class_proxy:)
10
10
  adaptor = nil
11
11
 
12
12
  ancestors = klass.ancestors.map(&:to_s)
13
- adaptor = Sinatra.new(klass:, parser:, file_path:) if ancestors.include?('Sinatra::Base')
13
+ adaptor = Sinatra.new(klass:, class_proxy:) if ancestors.include?('Sinatra::Base')
14
14
 
15
15
  return if adaptor.nil?
16
16
 
@@ -11,41 +11,37 @@ module Low
11
11
  module Adapter
12
12
  # 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.
13
13
  class Sinatra < AdapterInterface
14
- def initialize(klass:, parser:, file_path:)
14
+ def initialize(klass:, class_proxy:)
15
15
  @klass = klass
16
- @parser = parser
17
- @file_path = file_path
16
+ @class_proxy = class_proxy
17
+ @file_path = class_proxy.file_path
18
18
  end
19
19
 
20
20
  def process # rubocop:disable Metrics/AbcSize
21
- method_calls = @parser.method_calls(method_names: %i[get post patch put delete options query])
21
+ method_calls = @class_proxy.method_calls(%i[get post patch put delete options query])
22
22
 
23
23
  # Type check return values.
24
- method_calls.each do |method_call|
25
- arguments_node = method_call.compact_child_nodes.first
24
+ method_calls.each do |method_node|
25
+ arguments_node = method_node.compact_child_nodes.first
26
26
  next unless arguments_node.is_a?(Prism::ArgumentsNode)
27
27
 
28
28
  pattern = arguments_node.arguments.first.content
29
+ name = "#{method_node.name.upcase} #{pattern}"
30
+ scope = name
31
+ start_line = method_node.start_line
29
32
 
30
- file = ProxyFactory.file_proxy(node: method_call, path: @file_path, scope: "#{@klass}##{method_call.name}")
31
- next unless (return_proxy = return_proxy(method_node: method_call, pattern:, file:))
33
+ next unless (return_proxy = ProxyFactory.return_proxy(method_node:, name:, file_path:, scope: pattern))
32
34
 
33
- route = "#{method_call.name.upcase} #{pattern}"
34
- params = [ParamProxy.new(expression: nil, name: :route, type: :req, position: 0, file:)]
35
- @klass.low_methods[route] = MethodProxy.new(name: method_call.name, params:, return_proxy:)
35
+ route = "#{method_node.name.upcase} #{pattern}"
36
+ name = method_node.name
37
+ param_proxies = [ParamProxy.new(expression: nil, name: :route, type: :req, file_path:, start_line:, scope:, position: 0)]
38
+ @klass.low_methods[route] = MethodProxy.new(file_path:, start_line:, scope:, name:, param_proxies:, return_proxy:)
36
39
  end
37
40
  end
38
41
 
39
- def return_proxy(method_node:, pattern:, file:)
40
- return_type = FileParser.return_type(method_node:)
41
- return nil if return_type.nil?
42
+ private
42
43
 
43
- # Not a security risk because the code comes from a trusted source; the file that did the include. Does the file trust itself?
44
- expression = eval(return_type.slice).call # rubocop:disable Security/Eval
45
- expression = TypeExpression.new(type: expression) unless expression.is_a?(TypeExpression)
46
-
47
- ReturnProxy.new(type_expression: expression, name: "#{method_node.name.upcase} #{pattern}", file:)
48
- end
44
+ attr_reader :file_path
49
45
  end
50
46
 
51
47
  module Methods
@@ -10,22 +10,22 @@ module Low
10
10
  # Redefine methods to have their arguments and return values type checked.
11
11
  class Redefiner
12
12
  class << self
13
- def redefine(method_nodes:, class_proxy:, file_path:)
14
- method_proxies = build_methods(method_nodes:, klass: class_proxy.klass, file_path:)
13
+ def redefine(method_nodes:, class_proxy:, klass:)
14
+ method_proxies = build_methods(method_nodes:, klass:, file_path: class_proxy.file_path)
15
15
 
16
16
  if LowType.config.type_checking
17
- typed_methods(method_proxies:, class_proxy:)
17
+ typed_methods(method_proxies:, class_proxy:, klass:)
18
18
  else
19
- untyped_methods(method_proxies:, class_proxy:)
19
+ untyped_methods(method_proxies:, class_proxy:, klass:)
20
20
  end
21
21
  end
22
22
 
23
- def redefinable?(method_proxy:, class_proxy:)
24
- method_has_types?(method_proxy:, class_proxy:) && method_within_class_bounds?(method_proxy:, class_proxy:)
23
+ def redefinable?(method_proxy:, class_proxy:, klass:)
24
+ method_has_types?(method_proxy:, klass:) && method_within_class_bounds?(method_proxy:, class_proxy:, klass:)
25
25
  end
26
26
 
27
27
  def untyped_args(args:, kwargs:, method_proxy:) # rubocop:disable Metrics/AbcSize
28
- method_proxy.params.each do |param_proxy|
28
+ method_proxy.param_proxies.each do |param_proxy|
29
29
  value = param_proxy.position ? args[param_proxy.position] : kwargs[param_proxy.name]
30
30
 
31
31
  next unless value.nil?
@@ -44,11 +44,12 @@ module Low
44
44
  def build_methods(method_nodes:, klass:, file_path:)
45
45
  method_nodes.each do |name, method_node|
46
46
  begin # rubocop:disable Style/RedundantBegin
47
- file = ProxyFactory.file_proxy(path: file_path, node: method_node, scope: "#{klass}##{name}")
47
+ name = method_node.name
48
+ scope = name
48
49
 
49
- param_proxies = ProxyFactory.param_proxies(method_node:, file:)
50
- return_proxy = ProxyFactory.return_proxy(method_node:, file:)
51
- method_proxy = MethodProxy.new(name:, params: param_proxies, return_proxy:, file:)
50
+ param_proxies = ProxyFactory.param_proxies(method_node:, file_path:, scope:)
51
+ return_proxy = ProxyFactory.return_proxy(method_node:, name:, file_path:, scope:)
52
+ method_proxy = MethodProxy.new(file_path:, start_line: method_node.start_line, scope:, name:, param_proxies:, return_proxy:)
52
53
 
53
54
  Repository.save(method: method_proxy, klass:)
54
55
  # When we can't parse the method's params or return type then skip it.
@@ -60,17 +61,17 @@ module Low
60
61
  Repository.all(klass:)
61
62
  end
62
63
 
63
- def typed_methods(method_proxies:, class_proxy:) # rubocop:disable Metrics
64
+ def typed_methods(method_proxies:, class_proxy:, klass:) # rubocop:disable Metrics
64
65
  Module.new do
65
66
  method_proxies.each do |name, method_proxy|
66
- next unless Low::Redefiner.redefinable?(method_proxy:, class_proxy:)
67
+ next unless Low::Redefiner.redefinable?(method_proxy:, class_proxy:, klass:)
67
68
 
68
69
  # You are now in the binding of the includer class (`name` is also available here).
69
70
  define_method(name) do |*args, **kwargs|
70
71
  # Inlined version of Repository.load() for performance increase.
71
72
  method_proxy = instance_of?(Class) ? low_methods[name] : self.class.low_methods[name] || Object.low_methods[name]
72
73
 
73
- method_proxy.params.each do |param_proxy|
74
+ method_proxy.param_proxies.each do |param_proxy|
74
75
  value = param_proxy.position ? args[param_proxy.position] : kwargs[param_proxy.name]
75
76
  value = param_proxy.expression.default_value if value.nil? && !param_proxy.required?
76
77
 
@@ -93,10 +94,10 @@ module Low
93
94
  end
94
95
  end
95
96
 
96
- def untyped_methods(method_proxies:, class_proxy:)
97
+ def untyped_methods(method_proxies:, class_proxy:, klass:)
97
98
  Module.new do
98
99
  method_proxies.each do |name, method_proxy|
99
- next unless Low::Redefiner.redefinable?(method_proxy:, class_proxy:)
100
+ next unless Low::Redefiner.redefinable?(method_proxy:, class_proxy:, klass:)
100
101
 
101
102
  # You are now in the binding of the includer class (`name` is also available here).
102
103
  define_method(name) do |*args, **kwargs|
@@ -111,19 +112,20 @@ module Low
111
112
  end
112
113
  end
113
114
 
114
- def method_has_types?(method_proxy:, class_proxy:)
115
- if method_proxy.params == [] && method_proxy.return_proxy.nil?
116
- Low::Repository.delete(name: method_proxy.name, klass: class_proxy.klass)
115
+ def method_has_types?(method_proxy:, klass:)
116
+ if method_proxy.param_proxies == [] && method_proxy.return_proxy.nil?
117
+ Low::Repository.delete(name: method_proxy.name, klass:)
117
118
  return false
118
119
  end
119
120
 
120
121
  true
121
122
  end
122
123
 
123
- def method_within_class_bounds?(method_proxy:, class_proxy:)
124
- within_bounds = method_proxy.start_line > class_proxy.start_line && method_proxy.end_line <= class_proxy.end_line
125
- if method_proxy.lines? && class_proxy.lines? && !within_bounds
126
- Low::Repository.delete(name: method_proxy.name, klass: class_proxy.klass)
124
+ def method_within_class_bounds?(method_proxy:, class_proxy:, klass:)
125
+ within_bounds = method_proxy.start_line > class_proxy.start_line && method_proxy.start_line <= class_proxy.end_line
126
+
127
+ unless within_bounds
128
+ Low::Repository.delete(name: method_proxy.name, klass:)
127
129
  return false
128
130
  end
129
131
 
@@ -9,31 +9,37 @@ module Low
9
9
  def type_reader(named_expressions)
10
10
  named_expressions.each do |name, exp|
11
11
  last_caller = caller_locations(1, 1).first
12
- file = FileProxy.new(path: last_caller.path, start_line: last_caller.lineno, scope: "#{self}##{name}")
12
+ file_path = last_caller.path
13
+ start_line = last_caller.lineno
14
+ scope = "#{self}##{name}"
13
15
 
14
- expression = expression(exp)
15
- @low_methods[name] = MethodProxy.new(name:, return_proxy: ReturnProxy.new(type_expression: expression, name:, file:))
16
+ type_expression = type_expression(exp)
17
+ return_proxy = ReturnProxy.new(type_expression:, name:, file_path:, start_line:, scope:)
18
+
19
+ @low_methods[name] = MethodProxy.new(file_path:, start_line:, scope:, name:, return_proxy:)
16
20
 
17
21
  define_method(name) do
18
22
  method_proxy = self.class.low_methods[name]
19
23
  value = instance_variable_get("@#{name}")
20
- expression.validate!(value:, proxy: method_proxy.return_proxy)
24
+ type_expression.validate!(value:, proxy: method_proxy.return_proxy)
21
25
  value
22
26
  end
23
27
  end
24
28
  end
25
29
 
26
30
  def type_writer(named_expressions) # rubocop:disable Metrics/AbcSize
27
- named_expressions.each do |name, exp|
31
+ named_expressions.each do |name, expression|
28
32
  last_caller = caller_locations(1, 1).first
29
- file = FileProxy.new(path: last_caller.path, start_line: last_caller.lineno, scope: "#{self}##{name}")
33
+ file_path = last_caller.path
34
+ start_line = last_caller.lineno
35
+ scope = "#{self}##{name}"
30
36
 
31
- params = [ParamProxy.new(expression: expression(exp), name:, type: :hashreq, file:)]
32
- @low_methods["#{name}="] = MethodProxy.new(name:, params:)
37
+ param_proxies = [ParamProxy.new(expression: type_expression(expression), name:, type: :hashreq, file_path:, start_line:, scope:)]
38
+ @low_methods["#{name}="] = MethodProxy.new(file_path:, start_line:, scope:, name:, param_proxies:)
33
39
 
34
40
  define_method("#{name}=") do |value|
35
41
  method_proxy = self.class.low_methods["#{name}="]
36
- method_proxy.params.first.expression.validate!(value:, proxy: method_proxy.params.first)
42
+ method_proxy.param_proxies.first.expression.validate!(value:, proxy: method_proxy.param_proxies.first)
37
43
  instance_variable_set("@#{name}", value)
38
44
  end
39
45
  end
@@ -48,7 +54,7 @@ module Low
48
54
 
49
55
  private
50
56
 
51
- def expression(expression)
57
+ def type_expression(expression)
52
58
  if expression.is_a?(::Expressions::Expression)
53
59
  expression
54
60
  elsif ::Low::TypeQuery.type?(expression)
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../factories/expression_factory'
4
- require_relative '../proxies/file_proxy'
5
4
  require_relative '../proxies/local_proxy'
6
5
  require_relative '../types/error_types'
7
6
 
@@ -11,8 +10,9 @@ module Low
11
10
  value = type_expression.default_value
12
11
 
13
12
  last_caller = caller_locations(1, 1).first
14
- file = FileProxy.new(path: last_caller.path, start_line: last_caller.lineno, scope: 'local type')
15
- proxy = LocalProxy.new(type_expression:, name: self, file:)
13
+ file_path = last_caller.path
14
+ start_line = last_caller.lineno
15
+ proxy = LocalProxy.new(type_expression:, name: self, file_path:, start_line:, scope: 'local type')
16
16
 
17
17
  type_expression.validate!(value:, proxy:)
18
18
 
@@ -1,13 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'expressions'
4
+ require 'lowkey'
4
5
 
5
6
  require_relative '../expressions/expressions'
6
7
  require_relative '../expressions/type_expression'
7
- require_relative '../proxies/file_proxy'
8
8
  require_relative '../proxies/param_proxy'
9
9
  require_relative '../proxies/return_proxy'
10
- require_relative '../queries/file_parser'
11
10
  require_relative '../syntax/syntax'
12
11
  require_relative '../types/complex_types'
13
12
  require_relative '../types/status'
@@ -20,15 +19,8 @@ module Low
20
19
  include Low::Expressions
21
20
  include Low::Types
22
21
 
23
- def file_proxy(node:, path:, scope:)
24
- start_line = node.respond_to?(:start_line) ? node.start_line : nil
25
- end_line = node.respond_to?(:end_line) ? node.end_line : nil
26
-
27
- FileProxy.new(path:, start_line:, end_line:, scope:)
28
- end
29
-
30
22
  # The evals below aren't a security risk because the code comes from a trusted source; the file itself that did the include.
31
- def param_proxies(method_node:, file:)
23
+ def param_proxies(method_node:, file_path:, scope:)
32
24
  return [] if method_node.parameters.nil?
33
25
 
34
26
  params_without_block = method_node.parameters.slice.delete_suffix(', &block')
@@ -38,7 +30,7 @@ module Low
38
30
  # Local variable names are prefixed with __lt or __rb where needed to avoid being overridden by method parameters.
39
31
  typed_method = <<~RUBY
40
32
  -> (#{params_without_block}, __rb_method:, __lt_file:) {
41
- param_proxies_for_expressions(ruby_method: __rb_method, file: __lt_file, method_binding: binding)
33
+ param_proxies_for_expressions(ruby_method: __rb_method, file_path: __lt_file, start_line: method_node.start_line, scope:, method_binding: binding)
42
34
  }
43
35
  RUBY
44
36
 
@@ -46,27 +38,29 @@ module Low
46
38
 
47
39
  # Called with only required args (as nil) and optional args omitted, to evaluate expressions stored as default values.
48
40
  eval(typed_method, binding, __FILE__, __LINE__) # rubocop:disable Security/Eval
49
- .call(*required_args, **required_kwargs, __rb_method: ruby_method, __lt_file: file)
41
+ .call(*required_args, **required_kwargs, __rb_method: ruby_method, __lt_file: file_path)
50
42
 
51
43
  # TODO: Unit test this.
52
44
  rescue ArgumentError => e
53
45
  raise ArgumentError, "Incorrect param syntax: #{e.message}"
54
46
  end
55
47
 
56
- def return_proxy(method_node:, file:)
57
- return_type = FileParser.return_type(method_node:)
48
+ def return_proxy(method_node:, name:, file_path:, scope:)
49
+ return_type = Lowkey::ClassProxy.return_type(method_node:)
58
50
  return nil if return_type.nil?
59
51
 
52
+ start_line = method_node.start_line
53
+
60
54
  begin
61
55
  # Not a security risk because the code comes from a trusted source; the file that did the include. Does the file trust itself?
62
56
  expression = eval(return_type.slice, binding, __FILE__, __LINE__).call # rubocop:disable Security/Eval
63
57
  rescue NameError
64
- raise NameError, "Unknown return type '#{return_type.slice}' for #{file.scope} at #{file.path}:#{file.start_line}"
58
+ raise NameError, "Unknown return type '#{return_type.slice}' for #{scope} at #{file_path}:#{start_line}"
65
59
  end
66
60
 
67
61
  expression = TypeExpression.new(type: expression) unless expression.is_a?(TypeExpression)
68
62
 
69
- ReturnProxy.new(type_expression: expression, name: method_node.name, file:)
63
+ ReturnProxy.new(type_expression: expression, name:, file_path:, start_line:, scope:)
70
64
  end
71
65
 
72
66
  private
@@ -89,7 +83,7 @@ module Low
89
83
  [required_args, required_kwargs]
90
84
  end
91
85
 
92
- def param_proxies_for_expressions(ruby_method:, file:, method_binding:)
86
+ def param_proxies_for_expressions(ruby_method:, file_path:, start_line:, scope:, method_binding:)
93
87
  param_proxies = []
94
88
 
95
89
  ruby_method.parameters.each_with_index do |param, position|
@@ -110,7 +104,7 @@ module Low
110
104
  expression = TypeExpression.new(type: local_variable)
111
105
  end
112
106
 
113
- param_proxies << ParamProxy.new(expression:, name:, type:, position:, file:) if expression
107
+ param_proxies << ParamProxy.new(expression:, name:, type:, file_path:, start_line:, scope:, position:) if expression
114
108
  end
115
109
 
116
110
  param_proxies
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Low
4
+ # Used by proxies to output errors.
4
5
  class ErrorInterface
5
- attr_reader :file
6
+ attr_reader :file_path, :start_line, :scope
7
+
8
+ def initialize(file_path:, start_line:, scope:)
9
+ @file_path = file_path
10
+ @start_line = start_line
11
+ @scope = scope
6
12
 
7
- def initialize
8
- @file = nil
9
13
  @output_mode = LowType.config.output_mode
10
14
  @output_size = LowType.config.output_size
11
15
  end
@@ -34,8 +38,8 @@ module Low
34
38
  # Remove LowType defined method file paths from the backtrace.
35
39
  filtered_backtrace = backtrace.reject { |line| hidden_paths.find { |file_path| line.include?(file_path) } }
36
40
 
37
- # Add the proxied file to the backtrace.
38
- proxy_file_backtrace = "#{file.path}:#{file.start_line}:in '#{file.scope}'"
41
+ # Add the proxied entity to the backtrace.
42
+ proxy_file_backtrace = "#{file_path}:#{start_line}:in '#{scope}'"
39
43
  from_prefix = filtered_backtrace.first.match(/\s+from /)
40
44
  proxy_file_backtrace = "#{from_prefix}#{proxy_file_backtrace}" if from_prefix
41
45
 
data/lib/low_type.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'lowkey'
4
+
3
5
  require_relative 'adapters/adapter_loader'
4
6
  require_relative 'definitions/redefiner'
5
7
  require_relative 'definitions/type_accessors'
6
8
  require_relative 'expressions/expressions'
7
- require_relative 'queries/file_parser'
8
9
  require_relative 'queries/file_query'
9
10
  require_relative 'syntax/syntax'
10
11
  require_relative 'types/complex_types'
@@ -23,15 +24,16 @@ module LowType
23
24
  file_path = Low::FileQuery.file_path(klass:)
24
25
  return unless File.exist?(file_path)
25
26
 
26
- parser = Low::FileParser.new(klass:, file_path:)
27
+ file_proxy = Lowkey.load(file_path:)
28
+ class_proxy = file_proxy.definitions[klass.name]
27
29
 
28
30
  klass.extend Low::TypeAccessors
29
31
  klass.include Low::Types
30
32
  klass.include Low::Expressions
31
- klass.prepend Low::Redefiner.redefine(method_nodes: parser.instance_methods, class_proxy: parser.class_proxy, file_path:)
32
- klass.singleton_class.prepend Low::Redefiner.redefine(method_nodes: parser.class_methods, class_proxy: parser.class_proxy, file_path:)
33
+ klass.prepend Low::Redefiner.redefine(method_nodes: class_proxy.instance_methods, class_proxy:, klass:)
34
+ klass.singleton_class.prepend Low::Redefiner.redefine(method_nodes: class_proxy.class_methods, class_proxy:, klass:)
33
35
 
34
- if (adapter = Low::Adapter::Loader.load(klass:, parser:, file_path:))
36
+ if (adapter = Low::Adapter::Loader.load(klass:, class_proxy:))
35
37
  adapter.process
36
38
  klass.prepend Low::Adapter::Methods
37
39
  end
@@ -7,12 +7,11 @@ module Low
7
7
  class LocalProxy < ErrorInterface
8
8
  attr_reader :type_expression, :name
9
9
 
10
- def initialize(type_expression:, name:, file:)
11
- super()
10
+ def initialize(type_expression:, name:, file_path:, start_line:, scope:)
11
+ super(file_path:, start_line:, scope:)
12
12
 
13
13
  @type_expression = type_expression
14
14
  @name = name
15
- @file = file
16
15
  end
17
16
 
18
17
  def error_type
@@ -20,7 +19,7 @@ module Low
20
19
  end
21
20
 
22
21
  def error_message(value:)
23
- "Invalid variable type #{output(value:)} in '#{name.class}:#{@file.start_line}'. Valid types: '#{type_expression.valid_types}'"
22
+ "Invalid variable type #{output(value:)} in '#{name.class}:#{@start_line}'. Valid types: '#{type_expression.valid_types}'"
24
23
  end
25
24
  end
26
25
  end
@@ -1,20 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
-
5
3
  module Low
6
4
  class MethodProxy
7
- extend Forwardable
8
-
9
- attr_reader :name, :file, :params, :return_proxy
5
+ attr_reader :file_path, :start_line, :scope, :name, :param_proxies, :return_proxy
10
6
 
11
- # File is queried by redefiner but not sinatra adapter nor type accessors.
12
- def_delegators :@file, :start_line, :end_line, :lines?
7
+ # TODO: Refactor file path, start line and scope into "meta scope" model.
8
+ def initialize(file_path:, start_line:, scope:, name:, param_proxies: [], return_proxy: nil) # rubocop:disable Metrics/ParameterLists
9
+ @file_path = file_path
10
+ @start_line = start_line
11
+ @scope = scope
13
12
 
14
- def initialize(name:, file: nil, params: [], return_proxy: nil)
15
13
  @name = name
16
- @file = file
17
- @params = params
14
+ @param_proxies = param_proxies
18
15
  @return_proxy = return_proxy
19
16
  end
20
17
  end
@@ -7,14 +7,14 @@ module Low
7
7
  class ParamProxy < ErrorInterface
8
8
  attr_reader :expression, :name, :type, :position
9
9
 
10
- def initialize(expression:, name:, type:, file:, position: nil)
11
- super()
10
+ # TODO: Refactor file path, start line and scope into "meta scope" model.
11
+ def initialize(expression:, name:, type:, file_path:, start_line:, scope:, position: nil) # rubocop:disable Metrics/ParameterLists
12
+ super(file_path:, start_line:, scope:)
12
13
 
13
14
  @expression = expression
14
15
  @name = name
15
16
  @type = type
16
17
  @position = position
17
- @file = file
18
18
  end
19
19
 
20
20
  def required?
@@ -7,12 +7,11 @@ module Low
7
7
  class ReturnProxy < ErrorInterface
8
8
  attr_reader :type_expression, :name
9
9
 
10
- def initialize(type_expression:, name:, file:)
11
- super()
10
+ def initialize(type_expression:, name:, file_path:, start_line:, scope:)
11
+ super(file_path:, start_line:, scope:)
12
12
 
13
13
  @type_expression = type_expression
14
14
  @name = name
15
- @file = file
16
15
  end
17
16
 
18
17
  def error_type
data/lib/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Low
4
4
  module Type
5
- VERSION = '1.1.8'
5
+ VERSION = '1.1.9'
6
6
  end
7
7
  end
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: 1.1.8
4
+ version: 1.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: lowkey
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.2'
26
40
  description: An elegant and simple way to define types in Ruby, only when you need
27
41
  them.
28
42
  email:
@@ -45,13 +59,10 @@ files:
45
59
  - lib/interfaces/adapter_interface.rb
46
60
  - lib/interfaces/error_interface.rb
47
61
  - lib/low_type.rb
48
- - lib/proxies/class_proxy.rb
49
- - lib/proxies/file_proxy.rb
50
62
  - lib/proxies/local_proxy.rb
51
63
  - lib/proxies/method_proxy.rb
52
64
  - lib/proxies/param_proxy.rb
53
65
  - lib/proxies/return_proxy.rb
54
- - lib/queries/file_parser.rb
55
66
  - lib/queries/file_query.rb
56
67
  - lib/queries/type_query.rb
57
68
  - lib/syntax/syntax.rb
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'forwardable'
4
-
5
- module Low
6
- class ClassProxy
7
- extend Forwardable
8
-
9
- attr_reader :name, :klass, :file, :private_start_line
10
-
11
- def_delegators :@file, :start_line, :end_line, :lines?
12
-
13
- def initialize(klass:, file:, private_start_line:)
14
- @name = klass.to_s
15
- @klass = klass
16
- @file = file
17
- @private_start_line = private_start_line
18
- end
19
- end
20
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Low
4
- class FileProxy
5
- attr_reader :path, :scope
6
- attr_accessor :start_line, :end_line
7
-
8
- def initialize(path:, scope:, start_line:, end_line: nil)
9
- @path = path
10
- @start_line = start_line
11
- @end_line = end_line || start_line
12
- @scope = scope
13
- end
14
-
15
- def lines?
16
- start_line && end_line
17
- end
18
- end
19
- end
@@ -1,147 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'prism'
4
- require_relative '../proxies/class_proxy'
5
-
6
- module Low
7
- class FileParser
8
- attr_reader :parent_map, :instance_methods, :class_methods, :class_proxy
9
-
10
- def initialize(klass:, file_path:)
11
- @root_node = Prism.parse_file(file_path).value
12
-
13
- parent_mapper = ParentMapper.new
14
- parent_mapper.visit(@root_node)
15
- @parent_map = parent_mapper.parent_map
16
-
17
- method_visitor = MethodDefVisitor.new(root_node: @root_node, parent_map:, klass:, file_path:)
18
- @root_node.accept(method_visitor)
19
-
20
- @instance_methods = method_visitor.instance_methods
21
- @class_methods = method_visitor.class_methods
22
- @class_proxy = ClassProxy.new(klass:, file: method_visitor.file_proxy, private_start_line: method_visitor.private_start_line)
23
- end
24
-
25
- def method_calls(method_names:)
26
- block_visitor = MethodCallVisitor.new(parent_map:, method_names:)
27
- @root_node.accept(block_visitor)
28
- block_visitor.method_calls
29
- end
30
-
31
- class << self
32
- # Only a lambda defined immediately after a method's parameters/block is considered a return type expression.
33
- def return_type(method_node:)
34
- # Method statements.
35
- statements_node = method_node.compact_child_nodes.find { |node| node.is_a?(Prism::StatementsNode) }
36
-
37
- # Block statements.
38
- if statements_node.nil?
39
- block_node = method_node.compact_child_nodes.find { |node| node.is_a?(Prism::BlockNode) }
40
- statements_node = block_node.compact_child_nodes.find { |node| node.is_a?(Prism::StatementsNode) } if block_node
41
- end
42
-
43
- return nil if statements_node.nil? # Sometimes developers define methods without code inside them.
44
-
45
- node = statements_node.body.first
46
- return node if node.is_a?(Prism::LambdaNode)
47
-
48
- nil
49
- end
50
- end
51
- end
52
-
53
- class MethodDefVisitor < Prism::Visitor
54
- attr_reader :class_methods, :instance_methods, :file_proxy, :private_start_line
55
-
56
- def initialize(root_node:, parent_map:, klass:, file_path:)
57
- @parent_map = parent_map
58
- @klass = klass
59
-
60
- @instance_methods = {}
61
- @class_methods = {}
62
-
63
- end_line = root_node.respond_to?(:end_line) ? root_node.end_line : nil
64
- @file_proxy = FileProxy.new(path: file_path, start_line: 0, end_line:, scope: klass.to_s)
65
- @private_start_line = nil
66
- end
67
-
68
- def visit_def_node(node)
69
- if class_method?(node)
70
- @class_methods[node.name] = node
71
- else
72
- @instance_methods[node.name] = node
73
- end
74
-
75
- super # Continue walking the tree.
76
- end
77
-
78
- def visit_call_node(node)
79
- return super unless node.name == :private && node.respond_to?(:start_line) && file_proxy.start_line && file_proxy.end_line
80
-
81
- @private_start_line = node.start_line if node.start_line > file_proxy.start_line && node.start_line < file_proxy.end_line
82
-
83
- super
84
- end
85
-
86
- def visit_class_node(node)
87
- if node.name == @klass.to_s.to_sym
88
- file_proxy.start_line = node.class_keyword_loc.start_line
89
- file_proxy.end_line = node.end_keyword_loc.end_line
90
- end
91
-
92
- super
93
- end
94
-
95
- private
96
-
97
- def class_method?(node)
98
- return true if node.is_a?(::Prism::DefNode) && node.receiver.instance_of?(Prism::SelfNode) # self.method_name
99
- return true if node.is_a?(::Prism::SingletonClassNode) # class << self
100
-
101
- if (parent_node = @parent_map[node])
102
- return class_method?(parent_node)
103
- end
104
-
105
- false
106
- end
107
- end
108
-
109
- class MethodCallVisitor < Prism::Visitor
110
- attr_reader :method_calls
111
-
112
- def initialize(parent_map:, method_names:)
113
- @parent_map = parent_map
114
- @method_names = method_names
115
-
116
- @method_calls = []
117
- end
118
-
119
- def visit_call_node(node)
120
- @method_calls << node if @method_names.include?(node.name)
121
-
122
- super # Continue walking the tree.
123
- end
124
- end
125
-
126
- class ParentMapper < Prism::Visitor
127
- attr_reader :parent_map
128
-
129
- def initialize
130
- @parent_map = {}
131
- @current_parent = nil
132
- end
133
-
134
- def visit(node)
135
- @parent_map[node] = @current_parent
136
-
137
- old_parent = @current_parent
138
- @current_parent = node
139
-
140
- node.compact_child_nodes.each do |n|
141
- visit(n)
142
- end
143
-
144
- @current_parent = old_parent
145
- end
146
- end
147
- end