low_type 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5872bc6f373425ddfc4342d0421dd09e4e3bd6a5e7e0b4d3ba1d1c19cd077a5c
4
- data.tar.gz: 9b5543ba7bded1c2a043966376956316624408a101f2b30ec9a6b16d07976973
3
+ metadata.gz: 9a921b130ff71b7911409536acb53ad6ef7abebf91d789ce7ce27fde24b90e95
4
+ data.tar.gz: 9138f98a23846c1bb2474d2bb7b163dd0b0575e7c5911dd4b7ba05fc9deacb48
5
5
  SHA512:
6
- metadata.gz: c3a31c05871cc0d4b5fde6fca3c03c94fbc7e2ccc2696eb7eab401f8817e0b0a1abbd0bf4aa91d72454a61c60265b22ba9997148161f7d3e796d2e9c790e9c81
7
- data.tar.gz: 3629f1ac45bc9cbfce9979f85e933bb3b12b731747b714fb8eba6b7399cf139ac6098eb37667b97b624e95eda4e02c104ce9d7cd2b6013ea3fbb639e1e4a0125
6
+ metadata.gz: a440d93c18f7e444293f6987b5655644856d15ebba9a32b6d01818e9546339d48d8b48f5b09023e3039271195c790ce6ec713ca2b05498202cfd45d628c12e2a
7
+ data.tar.gz: '039ca399f75d3ee60be9c73399dfb15cadaf62d200254cdc1c873130b595c7715917eda4fa2e926e986c66817e6de972701c5b21cdc2e68ec282da71d040b46a'
@@ -5,7 +5,7 @@ require 'prism'
5
5
  require_relative '../interfaces/adapter_interface'
6
6
  require_relative '../proxies/file_proxy'
7
7
  require_relative '../proxies/return_proxy'
8
- require_relative '../error_types'
8
+ require_relative '../types/error_types'
9
9
 
10
10
  module LowType
11
11
  module Adapter
@@ -27,7 +27,7 @@ module LowType
27
27
 
28
28
  pattern = arguments_node.arguments.first.content
29
29
 
30
- line = Parser.line_number(node: method_call)
30
+ line = FileParser.line_number(node: method_call)
31
31
  file = FileProxy.new(path: @file_path, line:, scope: "#{@klass}##{method_call.name}")
32
32
  next unless (return_proxy = return_proxy(method_node: method_call, pattern:, file:))
33
33
 
@@ -38,7 +38,7 @@ module LowType
38
38
  end
39
39
 
40
40
  def return_proxy(method_node:, pattern:, file:)
41
- return_type = Parser.return_type(method_node:)
41
+ return_type = FileParser.return_type(method_node:)
42
42
  return nil if return_type.nil?
43
43
 
44
44
  # Not a security risk because the code comes from a trusted source; the file that did the include. Does the file trust itself?
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../proxies/return_proxy'
4
- require_relative '../parser'
4
+ require_relative '../file_parser'
5
5
  require_relative '../type_expression'
6
6
 
7
7
  module LowType
8
8
  class ProxyFactory
9
9
  class << self
10
10
  def return_proxy(method_node:, file:)
11
- return_type = Parser.return_type(method_node:)
11
+ return_type = FileParser.return_type(method_node:)
12
12
  return nil if return_type.nil?
13
13
 
14
14
  # Not a security risk because the code comes from a trusted source; the file that did the include. Does the file trust itself?
@@ -3,26 +3,26 @@
3
3
  require 'prism'
4
4
 
5
5
  module LowType
6
- class Parser
7
- attr_reader :parent_map, :instance_methods, :class_methods, :private_start_line
6
+ class FileParser
7
+ attr_reader :parent_map, :instance_methods, :class_methods, :line_numbers
8
8
 
9
- def initialize(file_path:)
9
+ def initialize(klass:, file_path:)
10
10
  @root_node = Prism.parse_file(file_path).value
11
11
 
12
12
  parent_mapper = ParentMapper.new
13
13
  parent_mapper.visit(@root_node)
14
14
  @parent_map = parent_mapper.parent_map
15
15
 
16
- method_visitor = MethodDefVisitor.new(@parent_map)
16
+ method_visitor = MethodDefVisitor.new(root_node: @root_node, parent_map:, klass:)
17
17
  @root_node.accept(method_visitor)
18
18
 
19
19
  @instance_methods = method_visitor.instance_methods
20
20
  @class_methods = method_visitor.class_methods
21
- @private_start_line = method_visitor.private_start_line
21
+ @line_numbers = method_visitor.line_numbers
22
22
  end
23
23
 
24
24
  def method_calls(method_names:)
25
- block_visitor = MethodCallVisitor.new(parent_map: @parent_map, method_names:)
25
+ block_visitor = MethodCallVisitor.new(parent_map:, method_names:)
26
26
  @root_node.accept(block_visitor)
27
27
  block_visitor.method_calls
28
28
  end
@@ -54,14 +54,15 @@ module LowType
54
54
  end
55
55
 
56
56
  class MethodDefVisitor < Prism::Visitor
57
- attr_reader :class_methods, :instance_methods, :private_start_line
57
+ attr_reader :class_methods, :instance_methods, :line_numbers
58
58
 
59
- def initialize(parent_map)
59
+ def initialize(root_node:, parent_map:, klass:)
60
60
  @parent_map = parent_map
61
+ @klass = klass
61
62
 
62
63
  @instance_methods = []
63
64
  @class_methods = []
64
- @private_start_line = nil
65
+ @line_numbers = { class_start: 0, class_end: root_node.end_line }
65
66
  end
66
67
 
67
68
  def visit_def_node(node)
@@ -75,7 +76,23 @@ module LowType
75
76
  end
76
77
 
77
78
  def visit_call_node(node)
78
- @private_start_line = node.start_line if node.name == :private && node.respond_to?(:start_line)
79
+ start_line = node.name == :private && node.respond_to?(:start_line) && node.start_line || nil
80
+ class_start = @line_numbers[:class_start]
81
+ class_end = @line_numbers[:class_end]
82
+
83
+ if start_line && class_start && class_end && start_line > class_start && start_line < class_end
84
+ @line_numbers[:private_start] = node.start_line
85
+ end
86
+
87
+ super
88
+ end
89
+
90
+ def visit_class_node(node)
91
+ if node.name == @klass.to_s.to_sym
92
+ @line_numbers = { class_start: node.class_keyword_loc.start_line, class_end: node.end_keyword_loc.end_line }
93
+ end
94
+
95
+ super
79
96
  end
80
97
 
81
98
  private
data/lib/local_types.rb CHANGED
@@ -2,27 +2,32 @@
2
2
 
3
3
  require_relative 'proxies/file_proxy'
4
4
  require_relative 'proxies/local_proxy'
5
+ require_relative 'types/error_types'
5
6
  require_relative 'type_expression'
6
7
  require_relative 'value_expression'
7
8
 
8
- module LocalTypes
9
- def type(type_expression)
10
- value = type_expression.default_value
9
+ module LowType
10
+ module LocalTypes
11
+ def type(type_expression)
12
+ value = type_expression.default_value
11
13
 
12
- last_caller = caller_locations(1, 1).first
13
- file = LowType::FileProxy.new(path: last_caller.path, line: last_caller.lineno, scope: 'local type')
14
- proxy = LowType::LocalProxy.new(type_expression:, name: self, file:)
14
+ last_caller = caller_locations(1, 1).first
15
+ file = FileProxy.new(path: last_caller.path, line: last_caller.lineno, scope: 'local type')
16
+ proxy = LocalProxy.new(type_expression:, name: self, file:)
15
17
 
16
- type_expression.validate!(value:, proxy:)
18
+ type_expression.validate!(value:, proxy:)
17
19
 
18
- return value.value if value.is_a?(ValueExpression)
20
+ return value.value if value.is_a?(ValueExpression)
19
21
 
20
- value
21
- end
22
- alias low_type type
22
+ value
23
+ rescue NoMethodError
24
+ raise ConfigError, "Invalid type expression, likely because you didn't add 'using LowType::Syntax'"
25
+ end
26
+ alias low_type type
23
27
 
24
- def value(type)
25
- LowType.value(type:)
28
+ def value(type)
29
+ LowType.value(type:)
30
+ end
31
+ alias low_value value
26
32
  end
27
- alias low_value value
28
33
  end
data/lib/low_type.rb CHANGED
@@ -1,32 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'adapters/adapter_loader'
4
- require_relative 'basic_types'
4
+ require_relative 'types/complex_types'
5
5
  require_relative 'instance_types'
6
6
  require_relative 'local_types'
7
7
  require_relative 'redefiner'
8
- require_relative 'subclasses'
8
+ require_relative 'syntax'
9
9
  require_relative 'type_expression'
10
10
  require_relative 'value_expression'
11
11
 
12
- # Include this module into your class to define and check types.
13
12
  module LowType
14
13
  # We do as much as possible on class load rather than on instantiation to be thread-safe and efficient.
15
- def self.included(klass) # rubocop:disable Metrics/AbcSize
14
+ def self.included(klass)
16
15
  class << klass
17
16
  def low_methods
18
17
  @low_methods ||= {}
19
18
  end
20
19
  end
21
20
 
22
- file_path = LowType.file_path(klass:)
23
- parser = LowType::Parser.new(file_path:)
24
- private_start_line = parser.private_start_line
21
+ file_path = LowType.file_path
22
+ parser = FileParser.new(klass:, file_path:)
23
+ line_numbers = parser.line_numbers
25
24
 
26
25
  klass.extend InstanceTypes
27
26
  klass.include LocalTypes
28
- klass.prepend LowType::Redefiner.redefine(method_nodes: parser.instance_methods, klass:, private_start_line:, file_path:)
29
- klass.singleton_class.prepend LowType::Redefiner.redefine(method_nodes: parser.class_methods, klass:, private_start_line:, file_path:)
27
+ klass.prepend LowType::Redefiner.redefine(method_nodes: parser.instance_methods, klass:, line_numbers:, file_path:)
28
+ klass.singleton_class.prepend LowType::Redefiner.redefine(method_nodes: parser.class_methods, klass:, line_numbers:, file_path:)
30
29
 
31
30
  if (adapter = Adapter::Loader.load(klass:, parser:, file_path:))
32
31
  adapter.process
@@ -48,11 +47,9 @@ module LowType
48
47
 
49
48
  # Internal API.
50
49
 
51
- def file_path(klass:)
52
- # Remove module namespaces from class.
53
- class_name = klass.to_s.split(':').last
54
- # The first class found regardless of namespace will be the class that did the include.
55
- caller.find { |callee| callee.end_with?("<class:#{class_name}>'") }.split(':').first
50
+ def file_path
51
+ includer_file = caller.find { |callee| callee.end_with?("in 'Module#include'") || callee.end_with?("in 'include'") }
52
+ includer_file.split(':').first
56
53
  end
57
54
 
58
55
  # TODO: Unit test.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../interfaces/error_interface'
4
- require_relative '../error_types'
4
+ require_relative '../types/error_types'
5
5
 
6
6
  module LowType
7
7
  class LocalProxy < ErrorInterface
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../interfaces/error_interface'
4
- require_relative '../error_types'
4
+ require_relative '../types/error_types'
5
5
 
6
6
  module LowType
7
7
  class ParamProxy < ErrorInterface
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../interfaces/error_interface'
4
- require_relative '../error_types'
4
+ require_relative '../types/error_types'
5
5
 
6
6
  module LowType
7
7
  class ReturnProxy < ErrorInterface
data/lib/redefiner.rb CHANGED
@@ -4,58 +4,80 @@ require_relative 'factories/proxy_factory'
4
4
  require_relative 'proxies/file_proxy'
5
5
  require_relative 'proxies/method_proxy'
6
6
  require_relative 'proxies/param_proxy'
7
+ require_relative 'syntax'
7
8
  require_relative 'type_expression'
8
9
 
9
10
  module LowType
10
11
  # Redefine methods to have their arguments and return values type checked.
11
12
  class Redefiner
13
+ using Syntax
14
+
12
15
  class << self
13
- def redefine(method_nodes:, klass:, private_start_line:, file_path:) # rubocop:disable Metrics
16
+ def redefine(method_nodes:, klass:, line_numbers:, file_path:)
17
+ create_proxies(method_nodes:, klass:, file_path:)
18
+ define_methods(method_nodes:, line_numbers:)
19
+ end
20
+
21
+ private
22
+
23
+ def create_proxies(method_nodes:, klass:, file_path:)
24
+ method_nodes.each do |method_node|
25
+ name = method_node.name
26
+ line = FileParser.line_number(node: method_node)
27
+ file = FileProxy.new(path: file_path, line:, scope: "#{klass}##{name}")
28
+ params = param_proxies(method_node:, file:)
29
+ return_proxy = ProxyFactory.return_proxy(method_node:, file:)
30
+
31
+ klass.low_methods[name] = MethodProxy.new(name:, params:, return_proxy:)
32
+ end
33
+ end
34
+
35
+ def define_methods(method_nodes:, line_numbers:) # rubocop:disable Metrics
36
+ class_start = line_numbers[:class_start]
37
+ class_end = line_numbers[:class_end]
38
+ private_start = line_numbers[:private_start]
39
+
14
40
  Module.new do
15
41
  method_nodes.each do |method_node|
16
- name = method_node.name
17
- line = Parser.line_number(node: method_node)
18
- file = FileProxy.new(path: file_path, line:, scope: "#{klass}##{method_node.name}")
19
- params = Redefiner.params_with_type_expressions(method_node:, file:)
20
- return_proxy = ProxyFactory.return_proxy(method_node:, file:)
42
+ method_start = method_node.start_line
43
+ next unless method_start > class_start && method_node.end_line <= class_end
21
44
 
22
- klass.low_methods[name] = MethodProxy.new(name:, params:, return_proxy:)
45
+ name = method_node.name
23
46
 
24
47
  define_method(name) do |*args, **kwargs|
25
- klass.low_methods[name].params.each do |param_proxy|
48
+ method_proxy = instance_of?(Class) ? low_methods[name] : self.class.low_methods[name] || Object.low_methods[name]
49
+
50
+ method_proxy.params.each do |param_proxy|
26
51
  # Get argument value or default value.
27
52
  value = param_proxy.position ? args[param_proxy.position] : kwargs[param_proxy.name]
28
53
  if value.nil? && param_proxy.type_expression.default_value != :LOW_TYPE_UNDEFINED
29
54
  value = param_proxy.type_expression.default_value
30
55
  end
31
- # Validate argument type.
56
+
32
57
  param_proxy.type_expression.validate!(value:, proxy: param_proxy)
33
- # Handle value(type) special case.
34
58
  value = value.value if value.is_a?(ValueExpression)
35
- # Redefine argument value.
36
59
  param_proxy.position ? args[param_proxy.position] = value : kwargs[param_proxy.name] = value
37
60
  end
38
61
 
39
- if return_proxy
62
+ if (return_proxy = method_proxy.return_proxy)
40
63
  return_value = super(*args, **kwargs)
41
- return_proxy.type_expression.validate!(value: return_value, proxy: klass.low_methods[name].return_proxy)
64
+ return_proxy.type_expression.validate!(value: return_value, proxy: return_proxy)
42
65
  return return_value
43
66
  end
44
67
 
45
68
  super(*args, **kwargs)
46
69
  end
47
70
 
48
- private name if private_start_line && method_node.start_line > private_start_line
71
+ private name if private_start && method_start > private_start
49
72
  end
50
73
  end
51
74
  end
52
75
 
53
- def params_with_type_expressions(method_node:, file:)
76
+ def param_proxies(method_node:, file:)
54
77
  return [] if method_node.parameters.nil?
55
78
 
56
79
  params = method_node.parameters.slice
57
- # Not a security risk because the code comes from a trusted source; the file that did the include. Does the file trust itself?
58
- proxy_method = eval("-> (#{params}) {}", binding, __FILE__, __LINE__) # rubocop:disable Security/Eval
80
+ proxy_method = proxy_method(method_node:)
59
81
  required_args, required_kwargs = required_args(proxy_method:)
60
82
 
61
83
  # Not a security risk because the code comes from a trusted source; the file that did the include. Does the file trust itself?
@@ -87,7 +109,11 @@ module LowType
87
109
  raise ArgumentError, "Incorrect param syntax: #{e.message}"
88
110
  end
89
111
 
90
- private
112
+ def proxy_method(method_node:)
113
+ params = method_node.parameters.slice
114
+ # Not a security risk because the code comes from a trusted source; the file that did the include. Does the file trust itself?
115
+ eval("-> (#{params}) {}", binding, __FILE__, __LINE__) # rubocop:disable Security/Eval
116
+ end
91
117
 
92
118
  def required_args(proxy_method:)
93
119
  required_args = []
@@ -1,42 +1,26 @@
1
- module LowType
2
- # Scoped to the class that includes LowType module.
3
- class Array < ::Array
4
- def self.[](*types)
5
- return LowType::TypeExpression.new(type: [*types]) if types.all? { |type| LowType.type?(type) }
6
- super
7
- end
1
+ # frozen_string_literal: true
8
2
 
9
- def self.===(other)
10
- return true if other == ::Array
11
- super
12
- end
13
-
14
- def ==(other)
15
- return true if other.class == ::Array
16
- super
17
- end
18
- end
3
+ module LowType
4
+ module Syntax
5
+ refine Array.singleton_class do
6
+ def [](*types)
7
+ return LowType::TypeExpression.new(type: [*types]) if types.all? { |type| LowType.type?(type) }
19
8
 
20
- # Scoped to the class that includes LowType module.
21
- class Hash < ::Hash
22
- def self.[](type)
23
- return LowType::TypeExpression.new(type:) if LowType.type?(type)
24
- super
9
+ super
10
+ end
25
11
  end
26
12
 
27
- def self.===(other)
28
- return true if other == ::Hash
29
- super
30
- end
13
+ refine Hash.singleton_class do
14
+ def [](type)
15
+ return LowType::TypeExpression.new(type:) if LowType.type?(type)
31
16
 
32
- def ==(other)
33
- return true if other.class == ::Hash
34
- super
17
+ super
18
+ end
35
19
  end
36
20
  end
37
21
  end
38
22
 
39
- # Scoped to the top-level for method type expressions to work. Could we bind/unbind? Yes. Should we? Probably not.
23
+ # Refine doesn't support inheritence.
40
24
  class Object
41
25
  # For "Type | [type_expression/type/value]" situations, redirecting to or generating a type expression from types.
42
26
  # "|" is not defined on Object class and this is the most compute-efficient way to achieve our goal (world peace).
@@ -57,9 +57,9 @@ module LowType
57
57
 
58
58
  def valid_types
59
59
  types = @types.map do |type|
60
- # Remove 'LowType::' namespaces in subtypes.
60
+ # Remove 'LowType::' namespace in subtypes.
61
61
  if type.is_a?(::Array)
62
- '[' + type.map { |subtype| subtype.to_s.delete_prefix('LowType::') }.join(', ') + ']'
62
+ "[#{type.map { |subtype| subtype.to_s.delete_prefix('LowType::') }.join(', ')}]"
63
63
  else
64
64
  type.inspect.to_s.delete_prefix('LowType::')
65
65
  end
@@ -5,4 +5,5 @@ module LowType
5
5
  class LocalTypeError < TypeError; end
6
6
  class ReturnTypeError < TypeError; end
7
7
  class AllowedTypeError < TypeError; end
8
+ class ConfigError < TypeError; end
8
9
  end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LowType
4
- VERSION = '1.0.0'
4
+ VERSION = '1.0.2'
5
5
  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.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
@@ -19,23 +19,23 @@ extra_rdoc_files: []
19
19
  files:
20
20
  - lib/adapters/adapter_loader.rb
21
21
  - lib/adapters/sinatra_adapter.rb
22
- - lib/basic_types.rb
23
- - lib/error_types.rb
24
22
  - lib/factories/proxy_factory.rb
23
+ - lib/file_parser.rb
25
24
  - lib/instance_types.rb
26
25
  - lib/interfaces/adapter_interface.rb
27
26
  - lib/interfaces/error_interface.rb
28
27
  - lib/local_types.rb
29
28
  - lib/low_type.rb
30
- - lib/parser.rb
31
29
  - lib/proxies/file_proxy.rb
32
30
  - lib/proxies/local_proxy.rb
33
31
  - lib/proxies/method_proxy.rb
34
32
  - lib/proxies/param_proxy.rb
35
33
  - lib/proxies/return_proxy.rb
36
34
  - lib/redefiner.rb
37
- - lib/subclasses.rb
35
+ - lib/syntax.rb
38
36
  - lib/type_expression.rb
37
+ - lib/types/complex_types.rb
38
+ - lib/types/error_types.rb
39
39
  - lib/value_expression.rb
40
40
  - lib/version.rb
41
41
  homepage: https://codeberg.org/low_ruby/low_type
File without changes