lowkey 0.2.1 → 0.3.1

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: 3f105e73eb19ec3b29ba8a8e2452071c1bf9f693fdd5dc5449374bf31315566b
4
- data.tar.gz: 95680c064c4020c42a473cef33e83d9277177ab22eae60ef8180cd79928fa5db
3
+ metadata.gz: b43e841e7df88bd529e4940fbc84e381896e105c5885824de12e8fdaa59e0b1a
4
+ data.tar.gz: 11feefde6d25fe3fadd3d70c38b9b5e5d4867bcade3e83d38eb5aa143eb8f790
5
5
  SHA512:
6
- metadata.gz: 6427844a8671c39e1a6e3ff7298b74d89b6b128d1243c745e65a568498efe72bfc70f5fbe2ee277e5b4b632e18056bf6301f85418fe7dd201dbbd84d5258b9d5
7
- data.tar.gz: fbb67e2e9a8d69848658b6bf51d4afaa1d6c96a46b630b85b74309e20791c0e0745680090d4cb56dd25a113f1e8957db159f2eb3704f29968a5093b08c8688a0
6
+ metadata.gz: 4c9631e357f115bcad8e12b5447b4714e821bb109e61e2c566ef1709d9efa3cca64d6657508d8092d466d37d4f3b5755446a56ed557f3c5943c520d1401c0363
7
+ data.tar.gz: 8c8d05eeb6ea1e3111ae28feeabc1e4558ac362f020f1a4435835db2e2810aad05b23798af28911fd3f451cef12b7773b6f2f1e6211306d8643c6c081df718b6
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'sinatra_adapter'
4
+ require_relative '../proxies/class_proxy'
5
+
6
+ module Lowkey
7
+ class AdapterLoader
8
+ class << self
9
+ def load(file_proxy:)
10
+ class_proxies = file_proxy.definitions.values.filter { |definition| definition.instance_of?(ClassProxy) }
11
+ class_proxies.each do |class_proxy|
12
+ SinatraAdapter.new(class_proxy:).load
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interfaces/adapter'
4
+
5
+ module Lowkey
6
+ class SinatraAdapter < Adapter
7
+ def initialize(class_proxy:)
8
+ @class_proxy = class_proxy
9
+ @file_path = class_proxy.file_path
10
+ end
11
+
12
+ def load # rubocop:disable Metrics/AbcSize
13
+ # Type check return values.
14
+ @class_proxy.method_calls.each do |method_call_node|
15
+ next unless %i[get post patch put delete options].include?(method_call_node.name)
16
+
17
+ arguments_node = method_call_node.compact_child_nodes.first
18
+ next unless arguments_node.is_a?(Prism::ArgumentsNode)
19
+
20
+ pattern = arguments_node.arguments.first.content
21
+ route = "#{method_call_node.name.upcase} #{pattern}"
22
+ name = route
23
+ scope = route
24
+ start_line = method_call_node.start_line
25
+
26
+ next unless (return_proxy = ProxyFactory.return_proxy(method_node: method_call_node, name:, file_path:, scope:))
27
+
28
+ param_proxies = [ParamProxy.new(file_path:, start_line:, scope:, name:, type: :pos_req, position: 0)]
29
+
30
+ @class_proxy.keyed_methods[route] = MethodProxy.new(file_path:, start_line:, scope:, name:, param_proxies:, return_proxy:)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :file_path
37
+ end
38
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../proxies/method_proxy'
4
+ require_relative '../proxies/param_proxy'
5
+ require_relative '../proxies/return_proxy'
6
+
7
+ module Lowkey
8
+ class ProxyFactory
9
+ class << self
10
+ def param_proxies(parameters_node:, file_path:, scope:)
11
+ return [] if parameters_node.nil?
12
+
13
+ param_types = {
14
+ Prism::RequiredParameterNode => :pos_req,
15
+ Prism::OptionalParameterNode => :pos_opt,
16
+ Prism::RequiredKeywordParameterNode => :key_req,
17
+ Prism::OptionalKeywordParameterNode => :key_opt
18
+ }
19
+
20
+ params = [*parameters_node.requireds, *parameters_node.optionals, *parameters_node.keywords]
21
+ params.map.with_index do |param, position|
22
+ type = param_types[param.class]
23
+ name = param.name
24
+ scope = name
25
+ start_line = param.start_line
26
+ value = param.respond_to?(:value) ? param.value.slice : ':LOWKEY_UNDEFINED'
27
+
28
+ ParamProxy.new(file_path:, start_line:, scope:, name:, type:, position:, value:)
29
+ end
30
+ end
31
+
32
+ def return_proxy(method_node:, name:, file_path:, scope:)
33
+ return_node = find_return_node(method_node:)
34
+ return nil if return_node.nil?
35
+
36
+ start_line = return_node.start_line
37
+ value = return_node.body.slice
38
+
39
+ ReturnProxy.new(name:, file_path:, start_line:, scope:, value:)
40
+ end
41
+
42
+ private
43
+
44
+ # A return type is an unassigned lambda defined immediately after a method's parameters/block.
45
+ def find_return_node(method_node:)
46
+ # Method statements.
47
+ statements_node = method_node.compact_child_nodes.find { |node| node.is_a?(Prism::StatementsNode) }
48
+
49
+ # Block statements.
50
+ if statements_node.nil?
51
+ block_node = method_node.compact_child_nodes.find { |node| node.is_a?(Prism::BlockNode) }
52
+ statements_node = block_node.compact_child_nodes.find { |node| node.is_a?(Prism::StatementsNode) } if block_node
53
+ end
54
+
55
+ return nil if statements_node.nil? # Sometimes developers define methods without code inside them.
56
+
57
+ node = statements_node.body.first
58
+ return node if node.is_a?(Prism::LambdaNode)
59
+
60
+ nil
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lowkey
4
+ class Adapter
5
+ def load
6
+ raise NotImplementedError
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lowkey
4
+ class Proxy
5
+ attr_reader :file_path, :start_line, :scope
6
+
7
+ def initialize(file_path:, start_line:, scope:)
8
+ @file_path = file_path
9
+ @start_line = start_line
10
+ @scope = scope
11
+ end
12
+ end
13
+ end
data/lib/lowkey.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'prism'
4
4
 
5
+ require_relative 'adapters/adapter_loader'
5
6
  require_relative 'visitors/visitor'
6
7
  require_relative 'proxies/file_proxy'
7
8
 
@@ -23,6 +24,8 @@ module Lowkey
23
24
  visitor = Visitor.new(file_proxy:, parent_map:)
24
25
  root_node.accept(visitor)
25
26
 
27
+ AdapterLoader.load(file_proxy:)
28
+
26
29
  if Lowkey.config.cache
27
30
  map_file_path(file_proxy:)
28
31
  map_definitions(file_proxy:)
@@ -48,6 +51,10 @@ module Lowkey
48
51
 
49
52
  def map_file_path(file_proxy:)
50
53
  keys[file_proxy.path] = file_proxy
54
+
55
+ # Map absolute paths to project root/relative paths.
56
+ project_path = file_proxy.path.delete_prefix(Dir.pwd).delete_prefix('/')
57
+ keys[project_path] = file_proxy if project_path != file_proxy.path
51
58
  end
52
59
 
53
60
  def map_definitions(file_proxy:)
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../queries/query'
4
+
3
5
  module Lowkey
4
6
  class ClassProxy
7
+ include Query
8
+
5
9
  attr_reader :namespace, :start_line, :end_line
6
- attr_writer :method_calls
7
- attr_accessor :private_start_line, :class_methods, :instance_methods
10
+ attr_accessor :private_start_line, :keyed_methods, :class_methods, :instance_methods, :method_calls
8
11
 
9
12
  def initialize(node:, namespace:, file_proxy:)
13
+ @node = node
10
14
  @namespace = namespace
11
15
  @file_proxy = file_proxy
12
16
 
@@ -15,15 +19,15 @@ module Lowkey
15
19
  @end_line = file_proxy.end_line if namespace == 'Object'
16
20
  @private_start_line = nil
17
21
 
22
+ @keyed_methods = {}
18
23
  @class_methods = {}
19
24
  @instance_methods = {}
25
+
20
26
  @method_calls = []
21
27
  end
22
28
 
23
- def method_calls(method_names = nil)
24
- return @method_calls if method_names.nil?
25
-
26
- @method_calls.filter { |method_call| method_names.include?(method_call.name) }
29
+ def [](key)
30
+ key.start_with?('.') ? query(node: @node, namespace: nil, name: key.delete_prefix('.')) : @keyed_methods[key]
27
31
  end
28
32
 
29
33
  def file_path
@@ -31,31 +35,12 @@ module Lowkey
31
35
  end
32
36
 
33
37
  class << self
34
- # Only a lambda defined immediately after a method's parameters/block is considered a return type expression.
35
- def return_type(method_node:)
36
- # Method statements.
37
- statements_node = method_node.compact_child_nodes.find { |node| node.is_a?(Prism::StatementsNode) }
38
-
39
- # Block statements.
40
- if statements_node.nil?
41
- block_node = method_node.compact_child_nodes.find { |node| node.is_a?(Prism::BlockNode) }
42
- statements_node = block_node.compact_child_nodes.find { |node| node.is_a?(Prism::StatementsNode) } if block_node
43
- end
44
-
45
- return nil if statements_node.nil? # Sometimes developers define methods without code inside them.
46
-
47
- node = statements_node.body.first
48
- return node if node.is_a?(Prism::LambdaNode)
49
-
50
- nil
51
- end
52
-
53
- def class_method?(node:, parent_map:)
54
- return true if node.is_a?(::Prism::DefNode) && node.receiver.instance_of?(Prism::SelfNode) # self.method_name
55
- return true if node.is_a?(::Prism::SingletonClassNode) # class << self
38
+ def class_method?(method_node:, parent_map:)
39
+ return true if method_node.is_a?(::Prism::DefNode) && method_node.receiver.instance_of?(Prism::SelfNode) # self.method_name
40
+ return true if method_node.is_a?(::Prism::SingletonClassNode) # class << self
56
41
 
57
- if (parent_node = parent_map[node])
58
- return class_method?(node: parent_node, parent_map:)
42
+ if (parent_node = parent_map[method_node])
43
+ return class_method?(method_node: parent_node, parent_map:)
59
44
  end
60
45
 
61
46
  false
@@ -2,14 +2,19 @@
2
2
 
3
3
  require_relative '../maps/parent_map'
4
4
  require_relative '../proxies/class_proxy'
5
+ require_relative '../queries/query'
5
6
 
6
7
  module Lowkey
7
8
  class FileProxy
9
+ include Query
10
+
8
11
  attr_reader :path, :start_line, :end_line
9
12
  attr_accessor :definitions, :dependencies
10
13
 
11
14
  def initialize(path:, root_node:)
12
15
  @path = path
16
+ @root_node = root_node
17
+
13
18
  @start_line = 0
14
19
  @end_line = root_node.respond_to?(:end_line) ? root_node.end_line : nil
15
20
 
@@ -17,6 +22,15 @@ module Lowkey
17
22
  @dependencies = []
18
23
  end
19
24
 
25
+ def [](keypath)
26
+ namespace, *path = keypath.split('.')
27
+ path.empty? ? @definitions[namespace] : query(node: @root_node, namespace:, name: path.join)
28
+ end
29
+
30
+ def []=(keypath, value)
31
+ # TODO: Slice the lines in a file and replace with the output of the class proxy.
32
+ end
33
+
20
34
  def upsert_class_proxy(node:, parent_map:)
21
35
  namespace = namespace(node:, parent_map:).reverse.join('::')
22
36
  namespace = 'Object' if namespace.empty?
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interfaces/proxy'
4
+ require_relative '../queries/query'
5
+
6
+ module Lowkey
7
+ class MethodProxy < Proxy
8
+ include Query
9
+
10
+ attr_reader :file_path, :start_line, :scope, :name, :params, :return_proxy
11
+
12
+ # TODO: Refactor file path, start line and scope into "scope" model.
13
+ def initialize(file_path:, start_line:, scope:, name:, param_proxies: [], return_proxy: nil) # rubocop:disable Metrics/ParameterLists
14
+ super(file_path:, start_line:, scope:)
15
+
16
+ @name = name
17
+ @params = param_proxies
18
+ @named_params = name_params
19
+ @tagged_params = tag_params
20
+ @return_proxy = return_proxy
21
+ end
22
+
23
+ def [](key)
24
+ # TODO: Initialize method proxy with method node and support query code path.
25
+ key.start_with?('.') ? query(node: @node, namespace: nil, name: key.delete_prefix('.')) : @named_params[key]
26
+ end
27
+
28
+ def tagged_params(tag)
29
+ @tagged_params[tag] || []
30
+ end
31
+
32
+ def expressions?
33
+ @params.any?(&:expression) || @return_proxy
34
+ end
35
+
36
+ def params_with_expressions
37
+ @params_with_expressions ||= @params.filter(&:expression)
38
+ end
39
+
40
+ private
41
+
42
+ def name_params
43
+ @params.each_with_object({}) do |param, named_params|
44
+ named_params[param.name] = param if %i[pos_req pos_opt key_req key_opt].include?(param.type)
45
+ end
46
+ end
47
+
48
+ def tag_params
49
+ tags = { required: [], optional: [], positional: [], keyword: [], value: [] }
50
+
51
+ tag_from_types = {
52
+ required: %i[pos_req key_req],
53
+ optional: %i[pos_opt key_opt],
54
+ positional: %i[pos_req pos_opt],
55
+ keyword: %i[key_req key_opt]
56
+ }
57
+
58
+ tag_from_types.each do |tag, types|
59
+ @params.each do |param|
60
+ tags[tag] << param if types.include?(param.type)
61
+ tags[:value] << param if param.value != :LOWKEY_UNDEFINED
62
+ end
63
+ end
64
+
65
+ tags
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interfaces/proxy'
4
+
5
+ module Lowkey
6
+ class ParamProxy < Proxy
7
+ attr_reader :name, :type, :position, :value
8
+ attr_accessor :expression
9
+
10
+ # TODO: Refactor file path, start line and scope into "scope" model.
11
+ def initialize(file_path:, start_line:, scope:, name:, type:, value: :LOWKEY_UNDEFINED, position: nil, expression: nil) # rubocop:disable Metrics/ParameterLists
12
+ super(file_path:, start_line:, scope:)
13
+
14
+ @name = name
15
+ @type = type
16
+ @position = position
17
+ @value = value
18
+ @expression = expression
19
+ end
20
+
21
+ def required?
22
+ @value == :LOWKEY_UNDEFINED
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interfaces/proxy'
4
+
5
+ module Lowkey
6
+ class ReturnProxy < Proxy
7
+ attr_reader :name, :value
8
+ attr_accessor :expression
9
+
10
+ def initialize(file_path:, start_line:, scope:, name:, value: :LOWKEY_UNDEFINED, expression: nil) # rubocop:disable Metrics/ParameterLists
11
+ super(file_path:, start_line:, scope:)
12
+
13
+ @name = name
14
+ @value = value
15
+ @expression = expression
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lowkey
4
+ module Query
5
+ # TODO: Make name a keypath.
6
+ def query(node:, namespace:, name:) # rubocop:disable Lint/UnusedMethodArgument
7
+ node.breadth_first_search do |n|
8
+ n.respond_to?(:name) && n.name == name.to_sym
9
+ end
10
+ end
11
+ end
12
+ end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lowkey
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.1'
5
5
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../factories/proxy_factory'
4
+
3
5
  module Lowkey
4
6
  class MethodDefVisitor
5
7
  attr_reader :parent_map
@@ -9,14 +11,29 @@ module Lowkey
9
11
  @parent_map = parent_map
10
12
  end
11
13
 
12
- def visit(node)
13
- class_proxy = @file_proxy.upsert_class_proxy(node:, parent_map:)
14
+ def visit(method_node) # rubocop:disable Metrics/AbcSize
15
+ class_proxy = @file_proxy.upsert_class_proxy(node: method_node, parent_map:)
16
+ name = method_node.name
17
+ scope = name
18
+
19
+ param_proxies = ProxyFactory.param_proxies(parameters_node: method_node.parameters, file_path:, scope:)
20
+ return_proxy = ProxyFactory.return_proxy(name:, method_node:, file_path:, scope:)
21
+ method_proxy = MethodProxy.new(file_path:, start_line: method_node.start_line, scope:, name:, param_proxies:, return_proxy:)
22
+
23
+ class_proxy.keyed_methods[method_node.name] = method_proxy
14
24
 
15
- if ClassProxy.class_method?(node:, parent_map:)
16
- class_proxy.class_methods[node.name] = node
25
+ # TODO: Implemented as sorted methods similar to sorted params.
26
+ if ClassProxy.class_method?(method_node:, parent_map:)
27
+ class_proxy.class_methods[method_node.name] = method_proxy
17
28
  else
18
- class_proxy.instance_methods[node.name] = node
29
+ class_proxy.instance_methods[method_node.name] = method_proxy
19
30
  end
20
31
  end
32
+
33
+ private
34
+
35
+ def file_path
36
+ @file_proxy.path
37
+ end
21
38
  end
22
39
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lowkey
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
@@ -31,10 +31,19 @@ executables: []
31
31
  extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
+ - lib/adapters/adapter_loader.rb
35
+ - lib/adapters/sinatra_adapter.rb
36
+ - lib/factories/proxy_factory.rb
37
+ - lib/interfaces/adapter.rb
38
+ - lib/interfaces/proxy.rb
34
39
  - lib/lowkey.rb
35
40
  - lib/maps/parent_map.rb
36
41
  - lib/proxies/class_proxy.rb
37
42
  - lib/proxies/file_proxy.rb
43
+ - lib/proxies/method_proxy.rb
44
+ - lib/proxies/param_proxy.rb
45
+ - lib/proxies/return_proxy.rb
46
+ - lib/queries/query.rb
38
47
  - lib/version.rb
39
48
  - lib/visitors/method_call_visitor.rb
40
49
  - lib/visitors/method_class_visitor.rb