lowkey 0.4.3 → 0.6.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/sinatra_adapter.rb +5 -3
- data/lib/factories/proxy_factory.rb +42 -5
- data/lib/factories/source_factory.rb +14 -2
- data/lib/interfaces/proxy.rb +27 -8
- data/lib/interfaces/releasable_node.rb +23 -0
- data/lib/lowkey.rb +35 -7
- data/lib/maps/parent_map.rb +4 -0
- data/lib/models/source.rb +32 -3
- data/lib/proxies/file_proxy.rb +15 -2
- data/lib/proxies/method_call_proxy.rb +110 -0
- data/lib/proxies/method_proxy.rb +52 -5
- data/lib/proxies/module_proxy.rb +91 -8
- data/lib/proxies/param_proxy.rb +37 -2
- data/lib/queries/query.rb +23 -8
- data/lib/version.rb +1 -1
- data/lib/visitors/method_call_visitor.rb +1 -1
- data/lib/visitors/method_def_visitor.rb +7 -4
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1a54c7a7c2d38827bf0eec31f56a83ca054e46b44312ee2ead8a39d0630f00c8
|
|
4
|
+
data.tar.gz: 4e77aca4128ce3cec24cd077c75922aa8a2ef22e6cccb76fefbab0cbfe41f5c0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5a76a77c867d1051d553f8af3309dc389b3d8b37e168cd7152e954625306f2666dd4ab8b3ac78c2a5c2063a4adfa7b738a5f3cadb220c3bd6804fedeaf42dd8d
|
|
7
|
+
data.tar.gz: ee32de9dd6b3fe80c96422744ba8077605babe0ed3d5ffaa9a28a8609fd20873bd404dd04b3e453628b6567e909575c33f9159fa65120a891e319df15372327e
|
|
@@ -12,8 +12,10 @@ module Lowkey
|
|
|
12
12
|
|
|
13
13
|
def load # rubocop:disable Metrics/AbcSize
|
|
14
14
|
# Type check return values.
|
|
15
|
-
@class_proxy.method_calls.each do |
|
|
16
|
-
next unless %i[get post patch put delete options].include?(
|
|
15
|
+
@class_proxy.method_calls.each do |method_call|
|
|
16
|
+
next unless %i[get post patch put delete options].include?(method_call.name)
|
|
17
|
+
|
|
18
|
+
method_call_node = method_call.node
|
|
17
19
|
|
|
18
20
|
arguments_node = method_call_node.compact_child_nodes.first
|
|
19
21
|
next unless arguments_node.is_a?(Prism::ArgumentsNode)
|
|
@@ -23,7 +25,7 @@ module Lowkey
|
|
|
23
25
|
name = route
|
|
24
26
|
source = SourceFactory.method_call_source(method_call_node:, arguments_node:, file_path:, lines:)
|
|
25
27
|
|
|
26
|
-
next unless (return_proxy = ProxyFactory.return_proxy(method_node: method_call_node, name:,
|
|
28
|
+
next unless (return_proxy = ProxyFactory.return_proxy(method_node: method_call_node, name:, file_path:, lines:))
|
|
27
29
|
|
|
28
30
|
param_proxies = [ParamProxy.new(source:, name:, type: :pos_req, position: 0)]
|
|
29
31
|
method_proxy = MethodProxy.new(source:, name:, param_proxies:, return_proxy:)
|
|
@@ -5,6 +5,7 @@ require_relative '../factories/source_factory'
|
|
|
5
5
|
require_relative '../proxies/body_proxy'
|
|
6
6
|
require_relative '../proxies/class_proxy'
|
|
7
7
|
require_relative '../proxies/file_proxy'
|
|
8
|
+
require_relative '../proxies/method_call_proxy'
|
|
8
9
|
require_relative '../proxies/method_proxy'
|
|
9
10
|
require_relative '../proxies/module_proxy'
|
|
10
11
|
require_relative '../proxies/param_proxy'
|
|
@@ -37,9 +38,15 @@ module Lowkey
|
|
|
37
38
|
|
|
38
39
|
param_proxies = param_proxies(parameters_node: method_node.parameters, file_path: file_proxy.file_path, source: method_source)
|
|
39
40
|
body_proxy = BodyProxy.new(name:, source: body_source)
|
|
40
|
-
return_proxy = return_proxy(name:, method_node:,
|
|
41
|
+
return_proxy = return_proxy(name:, method_node:, file_path: file_proxy.file_path, lines: file_proxy.lines)
|
|
42
|
+
signature_end_line = signature_end_node(method_node:, return_proxy:)&.end_line || method_node.start_line
|
|
41
43
|
|
|
42
|
-
MethodProxy.new(name:, source: method_source, param_proxies:, body_proxy:, return_proxy
|
|
44
|
+
MethodProxy.new(name:, source: method_source, param_proxies:, body_proxy:, return_proxy:, signature_end_line:,
|
|
45
|
+
receiver: method_node.receiver&.slice, node: method_node)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def method_call_proxy(call_node:, file_path:, lines:)
|
|
49
|
+
MethodCallProxy.new(name: call_node.name, node: call_node, file_path:, lines:)
|
|
43
50
|
end
|
|
44
51
|
|
|
45
52
|
def param_proxies(parameters_node:, source:, file_path:)
|
|
@@ -57,23 +64,53 @@ module Lowkey
|
|
|
57
64
|
name = param.name
|
|
58
65
|
source = SourceFactory.param_source(param_node: param, file_path:, lines: source.lines)
|
|
59
66
|
type = param_types[param.class]
|
|
60
|
-
value = param.respond_to?(:value) ? param.value.slice :
|
|
67
|
+
value = param.respond_to?(:value) ? param.value.slice : :LOWKEY_UNDEFINED
|
|
61
68
|
|
|
62
|
-
ParamProxy.new(name:, source:, type:, position:, value:)
|
|
69
|
+
ParamProxy.new(name:, source:, type:, position:, value:, default: param_default(param:))
|
|
63
70
|
end
|
|
64
71
|
end
|
|
65
72
|
|
|
66
|
-
def return_proxy(method_node:, name:,
|
|
73
|
+
def return_proxy(method_node:, name:, file_path:, lines:)
|
|
67
74
|
return_node = find_return_node(method_node:)
|
|
68
75
|
return nil if return_node.nil?
|
|
69
76
|
|
|
70
77
|
value = return_node.body.slice
|
|
78
|
+
source = SourceFactory.return_source(return_node:, scope: name, file_path:, lines:)
|
|
71
79
|
|
|
72
80
|
ReturnProxy.new(name:, source:, value:)
|
|
73
81
|
end
|
|
74
82
|
|
|
83
|
+
# The default value of a param or argument is the last value in a chain of "|" operators: "Type | Type | 'default'".
|
|
84
|
+
# LowType's "value(Type)" wraps a type to be used as a default value.
|
|
85
|
+
def default_source(node:)
|
|
86
|
+
node = node.arguments.arguments.first if union?(node:)
|
|
87
|
+
node = node.arguments.arguments.first if value_call?(node:)
|
|
88
|
+
|
|
89
|
+
node.slice
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def union?(node:)
|
|
93
|
+
node.is_a?(Prism::CallNode) && node.name == :| && node.arguments&.arguments&.one?
|
|
94
|
+
end
|
|
95
|
+
|
|
75
96
|
private
|
|
76
97
|
|
|
98
|
+
def value_call?(node:)
|
|
99
|
+
node.is_a?(Prism::CallNode) && node.receiver.nil? && %i[value low_value].include?(node.name) &&
|
|
100
|
+
node.arguments&.arguments&.one?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# The last node of a method's signature: "def name(params) -> { ReturnType }".
|
|
104
|
+
def signature_end_node(method_node:, return_proxy:)
|
|
105
|
+
return find_return_node(method_node:) if return_proxy
|
|
106
|
+
|
|
107
|
+
method_node.rparen_loc || method_node.parameters || method_node.name_loc
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def param_default(param:)
|
|
111
|
+
param.respond_to?(:value) ? default_source(node: param.value) : nil
|
|
112
|
+
end
|
|
113
|
+
|
|
77
114
|
# A return type is an unassigned lambda defined immediately after a method's parameters/block.
|
|
78
115
|
def find_return_node(method_node:)
|
|
79
116
|
# Method statements.
|
|
@@ -34,7 +34,7 @@ module Lowkey
|
|
|
34
34
|
# TODO: Brittle conditional. What we really want to identify is if the method/rest of file was able to be parsed into an AST.
|
|
35
35
|
end_line = end_line_from_indent(method_node:, lines:) if end_line >= lines.count
|
|
36
36
|
|
|
37
|
-
Source.new(file_path:, scope:, lines:, start_line:, end_line:)
|
|
37
|
+
Source.new(file_path:, scope:, lines:, start_line:, end_line:, location: method_node.location)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def param_source(param_node:, file_path:, lines:)
|
|
@@ -42,7 +42,14 @@ module Lowkey
|
|
|
42
42
|
start_line = param_node.start_line
|
|
43
43
|
end_line = param_node.end_line
|
|
44
44
|
|
|
45
|
-
Source.new(file_path:, scope:, lines:, start_line:, end_line:)
|
|
45
|
+
Source.new(file_path:, scope:, lines:, start_line:, end_line:, location: param_node.location)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def return_source(return_node:, scope:, file_path:, lines:)
|
|
49
|
+
start_line = return_node.start_line
|
|
50
|
+
end_line = return_node.end_line
|
|
51
|
+
|
|
52
|
+
Source.new(file_path:, scope:, lines:, start_line:, end_line:, location: return_node.location)
|
|
46
53
|
end
|
|
47
54
|
|
|
48
55
|
def body_source(method_source:)
|
|
@@ -53,6 +60,11 @@ module Lowkey
|
|
|
53
60
|
Source.new(file_path: method_source.file_path, scope: method_source.scope, lines: method_source.lines, start_line:, end_line:)
|
|
54
61
|
end
|
|
55
62
|
|
|
63
|
+
def call_source(call_node:, file_path:, lines:)
|
|
64
|
+
Source.new(file_path:, scope: call_node.name, lines:, start_line: call_node.start_line, end_line: call_node.end_line,
|
|
65
|
+
location: call_node.location)
|
|
66
|
+
end
|
|
67
|
+
|
|
56
68
|
def method_call_source(method_call_node:, arguments_node:, file_path:, lines:)
|
|
57
69
|
pattern = arguments_node.arguments.first.content
|
|
58
70
|
scope = "#{method_call_node.name.upcase} #{pattern}"
|
data/lib/interfaces/proxy.rb
CHANGED
|
@@ -1,21 +1,40 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'forwardable'
|
|
4
|
-
|
|
5
3
|
module Lowkey
|
|
6
4
|
# Proxy and Source abstract away nodes and avoid referencing the node on the proxy itself. Will this change?
|
|
7
5
|
# The goal is to make an API that's indiependent of the AST, that can manipulate source code line by line.
|
|
6
|
+
#
|
|
7
|
+
# Methods delegate to the source by hand, as Forwardable allocates an array of arguments on every call.
|
|
8
|
+
# Subclasses can build their source on first access by overriding "source".
|
|
8
9
|
class Proxy
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
attr_reader :name
|
|
12
|
-
|
|
13
|
-
def_delegators :@source, :file_path, :lines, :lines=, :start_line, :end_line, :scope
|
|
14
|
-
def_delegators :@source, :wrap, :export
|
|
10
|
+
attr_reader :name, :source
|
|
15
11
|
|
|
16
12
|
def initialize(name:, source:)
|
|
17
13
|
@name = name
|
|
18
14
|
@source = source
|
|
19
15
|
end
|
|
16
|
+
|
|
17
|
+
def file_path = source.file_path
|
|
18
|
+
def scope = source.scope
|
|
19
|
+
|
|
20
|
+
def lines = source.lines
|
|
21
|
+
|
|
22
|
+
def lines=(new_lines)
|
|
23
|
+
source.lines = new_lines
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def start_line = source.start_line
|
|
27
|
+
def end_line = source.end_line
|
|
28
|
+
def start_index = source.start_index
|
|
29
|
+
def end_index = source.end_index
|
|
30
|
+
|
|
31
|
+
def start_offset = source.start_offset
|
|
32
|
+
def end_offset = source.end_offset
|
|
33
|
+
def start_column = source.start_column
|
|
34
|
+
def end_column = source.end_column
|
|
35
|
+
def line_offset = source.line_offset
|
|
36
|
+
|
|
37
|
+
def wrap(prefix:, suffix:) = source.wrap(prefix:, suffix:)
|
|
38
|
+
def export = source.export
|
|
20
39
|
end
|
|
21
40
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lowkey
|
|
4
|
+
class ReleasedNodeError < StandardError
|
|
5
|
+
def initialize(message = 'Prism nodes are released once a file loads. Set "Lowkey.config.release_nodes = false" to keep them.')
|
|
6
|
+
super
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# A proxy whose Prism node is released once its file loads, as nodes use most of a cached file's memory.
|
|
11
|
+
module ReleasableNode
|
|
12
|
+
def node
|
|
13
|
+
raise ReleasedNodeError if @node_released
|
|
14
|
+
|
|
15
|
+
@node
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def release_node
|
|
19
|
+
@node = nil
|
|
20
|
+
@node_released = true
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/lowkey.rb
CHANGED
|
@@ -9,17 +9,22 @@ require_relative 'visitors/visitor'
|
|
|
9
9
|
require_relative 'proxies/file_proxy'
|
|
10
10
|
|
|
11
11
|
module Lowkey
|
|
12
|
+
NAMESPACE_PATTERN = /\A[A-Z]\w*(::[A-Z]\w*)*\z/
|
|
13
|
+
|
|
12
14
|
class << self
|
|
13
15
|
def keys
|
|
14
16
|
@keys ||= {}
|
|
15
17
|
end
|
|
16
18
|
|
|
19
|
+
# A namespace returns its file proxies. A file path returns its file proxy, loading the file on first access.
|
|
17
20
|
def [](key)
|
|
18
|
-
keys[key]
|
|
21
|
+
return keys[key] if keys.key?(key) || !file_path?(key)
|
|
22
|
+
|
|
23
|
+
load(key)
|
|
19
24
|
end
|
|
20
25
|
|
|
21
|
-
def load(file_path, cache: true)
|
|
22
|
-
root_node = Prism.
|
|
26
|
+
def load(file_path, cache: true, release_nodes: config.release_nodes)
|
|
27
|
+
root_node = Prism.parse(File.read(file_path), filepath: file_path).value
|
|
23
28
|
file_proxy = ProxyFactory.file_proxy(root_node:, file_path:)
|
|
24
29
|
|
|
25
30
|
parent_map = ParentMap.new(root_node:)
|
|
@@ -33,16 +38,35 @@ module Lowkey
|
|
|
33
38
|
map_definitions(file_proxy:)
|
|
34
39
|
end
|
|
35
40
|
|
|
41
|
+
file_proxy.release_nodes if release_nodes
|
|
36
42
|
file_proxy
|
|
37
43
|
end
|
|
38
44
|
|
|
39
|
-
def
|
|
40
|
-
|
|
45
|
+
def kept
|
|
46
|
+
@kept ||= Set.new
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Keeps a file proxy in the cache when it's cleared, for gems that look it up after the app has loaded.
|
|
50
|
+
def keep(file_proxy)
|
|
51
|
+
kept << file_proxy
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Clears cached file proxies to save memory, such as in a boot file once an app has loaded.
|
|
55
|
+
# Kept file proxies stay along with their namespaces, unless "keep" is false.
|
|
56
|
+
def clear(keep: true)
|
|
57
|
+
kept.clear unless keep
|
|
58
|
+
|
|
59
|
+
keys.select! do |_key, value|
|
|
60
|
+
next kept.include?(value) unless value.is_a?(Array)
|
|
61
|
+
|
|
62
|
+
value.select! { |file_proxy| kept.include?(file_proxy) }
|
|
63
|
+
!value.empty?
|
|
64
|
+
end
|
|
41
65
|
end
|
|
42
66
|
|
|
43
67
|
def config
|
|
44
|
-
config = Struct.new(:cache)
|
|
45
|
-
@config ||= config.new(true)
|
|
68
|
+
config = Struct.new(:cache, :release_nodes)
|
|
69
|
+
@config ||= config.new(true, true)
|
|
46
70
|
end
|
|
47
71
|
|
|
48
72
|
def configure
|
|
@@ -51,6 +75,10 @@ module Lowkey
|
|
|
51
75
|
|
|
52
76
|
private
|
|
53
77
|
|
|
78
|
+
def file_path?(key)
|
|
79
|
+
key.is_a?(String) && !key.match?(NAMESPACE_PATTERN) && File.file?(key)
|
|
80
|
+
end
|
|
81
|
+
|
|
54
82
|
def map_file_path(file_proxy:)
|
|
55
83
|
keys[file_proxy.file_path] = file_proxy
|
|
56
84
|
|
data/lib/maps/parent_map.rb
CHANGED
data/lib/models/source.rb
CHANGED
|
@@ -4,16 +4,22 @@ module Lowkey
|
|
|
4
4
|
class WriteError < StandardError; end
|
|
5
5
|
|
|
6
6
|
class Source
|
|
7
|
-
attr_reader :file_path, :scope, :lines
|
|
7
|
+
attr_reader :file_path, :scope, :lines, :start_offset, :end_offset, :start_column, :end_column
|
|
8
8
|
attr_accessor :start_line, :end_line
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
# Offsets are byte offsets of the node in the original file. Columns are byte columns.
|
|
11
|
+
def initialize(file_path:, scope:, lines:, start_line:, end_line: nil, location: nil) # rubocop:disable Metrics/ParameterLists
|
|
11
12
|
@file_path = file_path
|
|
12
13
|
@scope = scope
|
|
13
14
|
|
|
14
15
|
@start_line = start_line
|
|
15
16
|
@end_line = end_line
|
|
16
17
|
@lines = lines
|
|
18
|
+
|
|
19
|
+
@start_offset = location&.start_offset
|
|
20
|
+
@end_offset = location&.end_offset
|
|
21
|
+
@start_column = location&.start_column
|
|
22
|
+
@end_column = location&.end_column
|
|
17
23
|
end
|
|
18
24
|
|
|
19
25
|
def lines=(new_lines)
|
|
@@ -38,7 +44,25 @@ module Lowkey
|
|
|
38
44
|
lines[start_index..end_index].join
|
|
39
45
|
end
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
# Applies edits of [start_offset, end_offset, text] to a range of lines, keeping the amount of lines the same so that
|
|
48
|
+
# other sources sharing the lines stay valid. Offsets are byte offsets from the start of the range's first line.
|
|
49
|
+
def rewrite_lines(range:, edits:)
|
|
50
|
+
new_lines = apply_edits(text: lines[range].join.b, edits:).force_encoding(encoding).lines
|
|
51
|
+
raise WriteError, "More new lines than old lines, won't fit" if new_lines.count > range.count
|
|
52
|
+
|
|
53
|
+
lines[range] = new_lines + Array.new(range.count - new_lines.count, '')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def encoding
|
|
57
|
+
lines.first&.encoding || Encoding::UTF_8
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Edits are applied from last to first so that their offsets stay valid.
|
|
61
|
+
def apply_edits(text:, edits:)
|
|
62
|
+
edits.sort_by { |start_offset, _end_offset, _text| -start_offset }.reduce(text) do |new_text, (start_offset, end_offset, edit)|
|
|
63
|
+
new_text.byteslice(0, start_offset) + edit.b + new_text.byteslice(end_offset..)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
42
66
|
|
|
43
67
|
def start_index
|
|
44
68
|
start_line - 1
|
|
@@ -47,5 +71,10 @@ module Lowkey
|
|
|
47
71
|
def end_index
|
|
48
72
|
end_line - 1
|
|
49
73
|
end
|
|
74
|
+
|
|
75
|
+
# The byte offset of the start of the first line.
|
|
76
|
+
def line_offset
|
|
77
|
+
start_offset - start_column
|
|
78
|
+
end
|
|
50
79
|
end
|
|
51
80
|
end
|
data/lib/proxies/file_proxy.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../interfaces/proxy'
|
|
4
|
+
require_relative '../interfaces/releasable_node'
|
|
4
5
|
require_relative '../proxies/class_proxy'
|
|
5
6
|
require_relative '../queries/query'
|
|
6
7
|
|
|
@@ -8,7 +9,6 @@ module Lowkey
|
|
|
8
9
|
class FileProxy < Proxy
|
|
9
10
|
include Query
|
|
10
11
|
|
|
11
|
-
attr_reader :root_node
|
|
12
12
|
attr_accessor :definitions, :dependencies
|
|
13
13
|
|
|
14
14
|
def initialize(root_node:, source:)
|
|
@@ -22,7 +22,20 @@ module Lowkey
|
|
|
22
22
|
|
|
23
23
|
def [](keypath)
|
|
24
24
|
namespace, *file_path = keypath.split('.')
|
|
25
|
-
file_path.empty? ? @definitions[namespace] : query(node:
|
|
25
|
+
file_path.empty? ? @definitions[namespace] : query(node: root_node, namespace:, name: file_path.join)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def root_node
|
|
29
|
+
raise ReleasedNodeError if @nodes_released
|
|
30
|
+
|
|
31
|
+
@root_node
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Prism's syntax tree uses most of a cached file proxy's memory, so it's released once the file is loaded.
|
|
35
|
+
def release_nodes
|
|
36
|
+
@root_node = nil
|
|
37
|
+
@nodes_released = true
|
|
38
|
+
@definitions.each_value(&:release_nodes)
|
|
26
39
|
end
|
|
27
40
|
|
|
28
41
|
def []=(keypath, value)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../factories/source_factory'
|
|
4
|
+
require_relative '../interfaces/proxy'
|
|
5
|
+
require_relative '../interfaces/releasable_node'
|
|
6
|
+
|
|
7
|
+
module Lowkey
|
|
8
|
+
# A method call like "include Module", "attr_reader :name" or "type(String | 'default')".
|
|
9
|
+
# Details, including the source, are read from the node on first access as consumers often only need a few method calls.
|
|
10
|
+
class MethodCallProxy < Proxy
|
|
11
|
+
include ReleasableNode
|
|
12
|
+
|
|
13
|
+
def initialize(name:, node:, file_path:, lines:)
|
|
14
|
+
super(name:, source: nil)
|
|
15
|
+
|
|
16
|
+
@node = node
|
|
17
|
+
@file_path = file_path
|
|
18
|
+
@lines = lines
|
|
19
|
+
@rewritten = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# The file path and lines don't need the node, so they're still available once nodes are released.
|
|
23
|
+
attr_reader :file_path, :lines
|
|
24
|
+
|
|
25
|
+
def source
|
|
26
|
+
@source ||= SourceFactory.call_source(call_node: node, file_path: @file_path, lines: @lines)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The source of the receiver in "receiver.method", such as "self".
|
|
30
|
+
def receiver
|
|
31
|
+
return @receiver if defined?(@receiver)
|
|
32
|
+
|
|
33
|
+
@receiver = node.receiver&.slice
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def receiver?
|
|
37
|
+
!receiver.nil?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The source of each argument.
|
|
41
|
+
def arguments
|
|
42
|
+
@arguments ||= argument_nodes.map(&:slice)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# The keys of a hash argument like "method key: value" when every key is a symbol, otherwise nil.
|
|
46
|
+
def keywords
|
|
47
|
+
return @keywords if defined?(@keywords)
|
|
48
|
+
|
|
49
|
+
hash = argument_nodes.last
|
|
50
|
+
symbol_keys = (hash.is_a?(Prism::KeywordHashNode) || hash.is_a?(Prism::HashNode)) &&
|
|
51
|
+
hash.elements.all? { |element| element.is_a?(Prism::AssocNode) && element.key.is_a?(Prism::SymbolNode) }
|
|
52
|
+
|
|
53
|
+
@keywords = symbol_keys ? hash.elements.map { |element| element.key.unescaped.to_sym } : nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The source of the default value when the argument is "Type | 'default'".
|
|
57
|
+
def default
|
|
58
|
+
return @default if defined?(@default)
|
|
59
|
+
|
|
60
|
+
union = argument_nodes.one? && ProxyFactory.union?(node: argument_nodes.first)
|
|
61
|
+
@default = union ? ProxyFactory.default_source(node: argument_nodes.first) : nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Replaces the method call with text, once. Calls on the same line must be rewritten from last to first.
|
|
65
|
+
def rewrite(text)
|
|
66
|
+
return if @rewritten
|
|
67
|
+
|
|
68
|
+
source.rewrite_lines(range: start_index..end_index, edits: [[start_offset - line_offset, end_offset - line_offset, text]])
|
|
69
|
+
@rewritten = true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Removes the method call, along with its lines when it's the only code on them, and any blank line left orphaned.
|
|
73
|
+
def remove
|
|
74
|
+
return if @rewritten
|
|
75
|
+
return rewrite('') unless own_lines?
|
|
76
|
+
|
|
77
|
+
(start_index..end_index).each { |index| lines[index] = '' }
|
|
78
|
+
remove_orphaned_blank_line
|
|
79
|
+
@rewritten = true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def argument_nodes
|
|
85
|
+
node.arguments&.arguments || []
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def own_lines?
|
|
89
|
+
lines[start_index].b.byteslice(0, start_column).strip.empty? && lines[end_index].b.byteslice(end_column..).strip.empty?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# A blank line after a removed line is orphaned when it follows another blank line or opens a class/module.
|
|
93
|
+
def remove_orphaned_blank_line
|
|
94
|
+
next_index = ((end_index + 1)...lines.count).find { |index| lines[index] != '' }
|
|
95
|
+
return unless next_index && lines[next_index].strip.empty?
|
|
96
|
+
|
|
97
|
+
lines[next_index] = '' if orphaning_line?(line: previous_line(index: start_index))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def orphaning_line?(line:)
|
|
101
|
+
line.nil? || line.strip.empty? || line.match?(/\A\s*(?:class|module)\b/)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Skips removed lines, which are empty strings rather than blank lines.
|
|
105
|
+
def previous_line(index:)
|
|
106
|
+
previous_index = (0...index).reverse_each.find { |line_index| lines[line_index] != '' }
|
|
107
|
+
previous_index ? lines[previous_index] : nil
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
data/lib/proxies/method_proxy.rb
CHANGED
|
@@ -1,36 +1,65 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../interfaces/proxy'
|
|
4
|
+
require_relative '../interfaces/releasable_node'
|
|
4
5
|
require_relative '../queries/query'
|
|
5
6
|
|
|
6
7
|
module Lowkey
|
|
7
8
|
class MethodProxy < Proxy
|
|
8
9
|
include Query
|
|
10
|
+
include ReleasableNode
|
|
9
11
|
|
|
10
|
-
attr_reader :params, :body, :return_proxy
|
|
12
|
+
attr_reader :params, :body, :return_proxy, :signature_end_line, :receiver
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
# @param receiver - The source of the receiver in "def receiver.method", such as "self".
|
|
15
|
+
# @param node - The method's node, which is released once its file loads.
|
|
16
|
+
def initialize(name:, source:, param_proxies: [], body_proxy: nil, return_proxy: nil, signature_end_line: nil, receiver: nil, # rubocop:disable Metrics/ParameterLists
|
|
17
|
+
node: nil)
|
|
13
18
|
super(name:, source:)
|
|
14
19
|
|
|
20
|
+
@node = node
|
|
21
|
+
@receiver = receiver
|
|
15
22
|
@params = param_proxies
|
|
16
23
|
@body = body_proxy
|
|
17
24
|
@return_proxy = return_proxy
|
|
25
|
+
@signature_end_line = signature_end_line || source.start_line
|
|
26
|
+
@rewritten = false
|
|
18
27
|
|
|
19
28
|
@named_params = name_params
|
|
20
29
|
@tagged_params = tag_params
|
|
21
30
|
end
|
|
22
31
|
|
|
32
|
+
# Methods built from method calls, like Sinatra routes, have no method node to query.
|
|
23
33
|
def [](key)
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
return @named_params[key] unless key.start_with?('.')
|
|
35
|
+
|
|
36
|
+
query(node:, namespace: nil, name: key.delete_prefix('.')) if node
|
|
26
37
|
end
|
|
27
38
|
|
|
28
39
|
def tagged_params(tag)
|
|
29
40
|
@tagged_params[tag] || []
|
|
30
41
|
end
|
|
31
42
|
|
|
43
|
+
# Removes types from the signature, leaving the rest of the source as is. The amount of lines stays the same.
|
|
44
|
+
def rewrite_signature
|
|
45
|
+
return if @rewritten
|
|
46
|
+
|
|
47
|
+
signature_range = start_index..(signature_end_line - 1)
|
|
48
|
+
|
|
49
|
+
edits = param_edits
|
|
50
|
+
edits << return_type_edit(signature: lines[signature_range].join.b) if @return_proxy&.expression
|
|
51
|
+
|
|
52
|
+
@source.rewrite_lines(range: signature_range, edits:)
|
|
53
|
+
@rewritten = true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Whether the method is defined on its receiver, such as "def self.method", rather than on the current class.
|
|
57
|
+
def receiver?
|
|
58
|
+
!@receiver.nil?
|
|
59
|
+
end
|
|
60
|
+
|
|
32
61
|
def expressions?
|
|
33
|
-
@params.any?(&:expression) ||
|
|
62
|
+
@params.any?(&:expression) || !@return_proxy&.expression.nil?
|
|
34
63
|
end
|
|
35
64
|
|
|
36
65
|
def params_with_expressions
|
|
@@ -39,6 +68,24 @@ module Lowkey
|
|
|
39
68
|
|
|
40
69
|
private
|
|
41
70
|
|
|
71
|
+
def param_edits
|
|
72
|
+
params_with_expressions.map do |param|
|
|
73
|
+
[param.start_offset - line_offset, param.end_offset - line_offset, param.export(typed: false)]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Also removes the whitespace before the return type, or the whole line when the return type is on its own line.
|
|
78
|
+
def return_type_edit(signature:)
|
|
79
|
+
start_offset = @return_proxy.start_offset - line_offset
|
|
80
|
+
end_offset = @return_proxy.end_offset - line_offset
|
|
81
|
+
line_start_offset = start_offset - @return_proxy.start_column
|
|
82
|
+
before = signature.byteslice(line_start_offset, @return_proxy.start_column)
|
|
83
|
+
|
|
84
|
+
return [line_start_offset, end_offset + 1, ''] if before.strip.empty? && signature.byteslice(end_offset) == "\n"
|
|
85
|
+
|
|
86
|
+
[start_offset - before[/[ \t]*\z/].bytesize, end_offset, '']
|
|
87
|
+
end
|
|
88
|
+
|
|
42
89
|
def name_params
|
|
43
90
|
@params.each_with_object({}) do |param, named_params|
|
|
44
91
|
named_params[param.name] = param if %i[pos_req pos_opt key_req key_opt].include?(param.type)
|
data/lib/proxies/module_proxy.rb
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../interfaces/proxy'
|
|
4
|
+
require_relative '../interfaces/releasable_node'
|
|
4
5
|
require_relative '../queries/query'
|
|
5
6
|
|
|
6
7
|
module Lowkey
|
|
7
8
|
class ModuleProxy < Proxy
|
|
8
9
|
include Query
|
|
10
|
+
include ReleasableNode
|
|
9
11
|
|
|
10
12
|
attr_reader :namespace
|
|
11
|
-
attr_accessor :private_start_line, :keyed_methods, :class_methods
|
|
13
|
+
attr_accessor :private_start_line, :keyed_methods, :class_methods
|
|
12
14
|
|
|
13
15
|
def initialize(node:, name:, namespace:, source:)
|
|
14
16
|
super(name:, source:)
|
|
@@ -21,24 +23,105 @@ module Lowkey
|
|
|
21
23
|
@keyed_methods = {}
|
|
22
24
|
@class_methods = {}
|
|
23
25
|
|
|
24
|
-
@
|
|
26
|
+
@method_call_nodes = []
|
|
27
|
+
@method_calls = nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Method call proxies are lazy loaded and may not always be needed.
|
|
31
|
+
def method_calls
|
|
32
|
+
@method_calls ||= method_call_nodes.map { |call_node| method_call_proxy(call_node:) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def add_method_call(node:)
|
|
36
|
+
@method_call_nodes << node
|
|
37
|
+
@method_calls << method_call_proxy(call_node: node) if @method_calls
|
|
25
38
|
end
|
|
26
39
|
|
|
27
40
|
def [](key)
|
|
28
|
-
key.start_with?('.') ? query(node
|
|
41
|
+
key.start_with?('.') ? query(node:, namespace: nil, name: key.delete_prefix('.')) : @keyed_methods[key]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Also releases the nodes of its methods and method calls.
|
|
45
|
+
def release_nodes
|
|
46
|
+
release_node
|
|
47
|
+
@method_call_nodes = nil
|
|
48
|
+
@method_calls&.each(&:release_node)
|
|
49
|
+
method_proxies.each(&:release_node)
|
|
29
50
|
end
|
|
30
51
|
|
|
31
52
|
class << self
|
|
53
|
+
# "def self.method", "def ClassName.method" or a method within "class << self"/"class << ClassName".
|
|
32
54
|
def class_method?(method_node:, parent_map:)
|
|
33
|
-
return
|
|
34
|
-
return
|
|
55
|
+
return class_receiver?(receiver: method_node.receiver, node: method_node, parent_map:) if method_node.receiver
|
|
56
|
+
return false unless (singleton_class_node = singleton_class_node(method_node:, parent_map:))
|
|
35
57
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
end
|
|
58
|
+
class_receiver?(receiver: singleton_class_node.expression, node: singleton_class_node, parent_map:)
|
|
59
|
+
end
|
|
39
60
|
|
|
61
|
+
# A method defined on an object, like "def object.method" or a method within "class << object".
|
|
62
|
+
def singleton_method?(method_node:, parent_map:)
|
|
63
|
+
!method_node.receiver.nil? || !singleton_class_node(method_node:, parent_map:).nil?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Any other receiver, like "def object.method", defines a method on that object rather than the class.
|
|
67
|
+
def class_receiver?(receiver:, node:, parent_map:)
|
|
68
|
+
receiver = unwrap(node: receiver)
|
|
69
|
+
return true if receiver.is_a?(::Prism::SelfNode)
|
|
70
|
+
return false unless receiver.is_a?(::Prism::ConstantReadNode) || receiver.is_a?(::Prism::ConstantPathNode)
|
|
71
|
+
|
|
72
|
+
# The receiver can be the class name or any part of its namespace, like "Name" or "Namespace::Name".
|
|
73
|
+
"::#{namespace(node:, parent_map:)}".end_with?("::#{receiver.full_name}")
|
|
74
|
+
rescue ::Prism::ConstantPathNode::DynamicPartsInConstantPathError
|
|
40
75
|
false
|
|
41
76
|
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def singleton_class_node(method_node:, parent_map:)
|
|
81
|
+
node = parent_map[method_node]
|
|
82
|
+
node = parent_map[node] until node.nil? || module_node?(node) || node.is_a?(::Prism::SingletonClassNode)
|
|
83
|
+
|
|
84
|
+
node.is_a?(::Prism::SingletonClassNode) ? node : nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# "def (Namespace::Name).method" wraps the receiver in parentheses.
|
|
88
|
+
def unwrap(node:)
|
|
89
|
+
return node unless node.is_a?(::Prism::ParenthesesNode)
|
|
90
|
+
|
|
91
|
+
body = node.body
|
|
92
|
+
body.is_a?(::Prism::StatementsNode) && body.body.one? ? body.body.first : body
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def namespace(node:, parent_map:)
|
|
96
|
+
names = []
|
|
97
|
+
|
|
98
|
+
while (node = parent_map[node])
|
|
99
|
+
names.unshift(node.constant_path.full_name) if module_node?(node)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
names.join('::')
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def module_node?(node)
|
|
106
|
+
node.is_a?(::Prism::ClassNode) || node.is_a?(::Prism::ModuleNode)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
# Method proxies with the same name share a key in "keyed_methods", so instance and class methods are included.
|
|
113
|
+
def method_proxies
|
|
114
|
+
[*@keyed_methods.values, *@class_methods.values, *(instance_methods.values if respond_to?(:instance_methods))].uniq
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def method_call_nodes
|
|
118
|
+
raise ReleasedNodeError if @method_call_nodes.nil?
|
|
119
|
+
|
|
120
|
+
@method_call_nodes
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def method_call_proxy(call_node:)
|
|
124
|
+
ProxyFactory.method_call_proxy(call_node:, file_path:, lines:)
|
|
42
125
|
end
|
|
43
126
|
end
|
|
44
127
|
end
|
data/lib/proxies/param_proxy.rb
CHANGED
|
@@ -5,14 +5,17 @@ require_relative '../interfaces/proxy'
|
|
|
5
5
|
module Lowkey
|
|
6
6
|
class ParamProxy < Proxy
|
|
7
7
|
attr_reader :type, :position, :value
|
|
8
|
-
attr_accessor :expression
|
|
8
|
+
attr_accessor :default, :expression
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
# @param value - The source of the param's value, such as "String | 'default'".
|
|
11
|
+
# @param default - The source of the default value within the value, such as "'default'". Can be overridden.
|
|
12
|
+
def initialize(name:, source:, type:, position: nil, value: :LOWKEY_UNDEFINED, default: nil, expression: nil) # rubocop:disable Metrics/ParameterLists
|
|
11
13
|
super(name:, source:)
|
|
12
14
|
|
|
13
15
|
@type = type
|
|
14
16
|
@position = position
|
|
15
17
|
@value = value
|
|
18
|
+
@default = default
|
|
16
19
|
|
|
17
20
|
@expression = expression
|
|
18
21
|
end
|
|
@@ -20,5 +23,37 @@ module Lowkey
|
|
|
20
23
|
def required?
|
|
21
24
|
@value == :LOWKEY_UNDEFINED
|
|
22
25
|
end
|
|
26
|
+
|
|
27
|
+
def export(typed: true)
|
|
28
|
+
return @source.export if typed
|
|
29
|
+
return untyped_keyword_param if %i[key_req key_opt].include?(@type)
|
|
30
|
+
|
|
31
|
+
untyped_positional_param
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def untyped_keyword_param
|
|
37
|
+
default = resolved_default
|
|
38
|
+
default.nil? ? "#{@name}:" : "#{@name}: #{default}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def untyped_positional_param
|
|
42
|
+
default = resolved_default
|
|
43
|
+
default.nil? ? @name.to_s : "#{@name} = #{default}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Uses the source of the default value rather than the evaluated default value, to preserve the developer's code.
|
|
47
|
+
def resolved_default
|
|
48
|
+
if @expression
|
|
49
|
+
return nil if @expression.required?
|
|
50
|
+
|
|
51
|
+
return @default
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
return nil if @value == :LOWKEY_UNDEFINED
|
|
55
|
+
|
|
56
|
+
@value
|
|
57
|
+
end
|
|
23
58
|
end
|
|
24
59
|
end
|
data/lib/queries/query.rb
CHANGED
|
@@ -11,18 +11,33 @@ module Lowkey
|
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def namespace(node:, parent_map
|
|
14
|
+
def namespace(node:, parent_map:)
|
|
15
|
+
path = namespace_path(node:, parent_map:)
|
|
16
|
+
return nil if path.nil?
|
|
17
|
+
|
|
18
|
+
path.empty? ? 'Object' : path
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# Each node caches the namespace above it, so our first ancestor will have the full namespace.
|
|
24
|
+
def namespace_path(node:, parent_map:)
|
|
25
|
+
namespaces = parent_map.namespaces
|
|
26
|
+
return namespaces[node] if namespaces.key?(node)
|
|
27
|
+
|
|
28
|
+
namespaces[node] = resolve_namespace_path(node:, parent_map:)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def resolve_namespace_path(node:, parent_map:)
|
|
15
32
|
# When inside RBX, Prism thinks we're defining a class because of "<".
|
|
16
33
|
return nil if node.respond_to?(:constant_path) && node.constant_path.is_a?(Prism::MissingNode)
|
|
34
|
+
return '' if (parent = parent_map[node]).nil?
|
|
17
35
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return namespace.reverse.join('::')
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
namespace << node.constant_path.name.to_s if node.respond_to?(:constant_path)
|
|
36
|
+
path = namespace_path(node: parent, parent_map:)
|
|
37
|
+
return path if path.nil? || !node.respond_to?(:constant_path)
|
|
24
38
|
|
|
25
|
-
|
|
39
|
+
name = node.constant_path.name.to_s
|
|
40
|
+
(path.empty? ? name : "#{path}::#{name}").freeze
|
|
26
41
|
end
|
|
27
42
|
end
|
|
28
43
|
end
|
data/lib/version.rb
CHANGED
|
@@ -15,7 +15,7 @@ module Lowkey
|
|
|
15
15
|
namespace = namespace(node:, parent_map:) || return
|
|
16
16
|
module_proxy = file_proxy[namespace]
|
|
17
17
|
|
|
18
|
-
module_proxy.
|
|
18
|
+
module_proxy.add_method_call(node:)
|
|
19
19
|
upsert_dependency(node:, namespace:)
|
|
20
20
|
|
|
21
21
|
return unless node.name == :private && node.respond_to?(:start_line) && module_proxy.start_line && module_proxy.end_line
|
|
@@ -20,17 +20,20 @@ module Lowkey
|
|
|
20
20
|
method_proxy = ProxyFactory.method_proxy(method_node:, file_proxy:)
|
|
21
21
|
|
|
22
22
|
module_proxy.keyed_methods[method_node.name] = method_proxy
|
|
23
|
+
tag_method(method_node:, method_proxy:, module_proxy:)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
# TODO: Implemented as tagged methods similar to tagged params.
|
|
29
|
+
def tag_method(method_node:, method_proxy:, module_proxy:)
|
|
25
30
|
if ModuleProxy.class_method?(method_node:, parent_map:)
|
|
26
31
|
module_proxy.class_methods[method_node.name] = method_proxy
|
|
27
|
-
elsif module_proxy.class <= ClassProxy
|
|
32
|
+
elsif module_proxy.class <= ClassProxy && !ModuleProxy.singleton_method?(method_node:, parent_map:)
|
|
28
33
|
module_proxy.instance_methods[method_node.name] = method_proxy
|
|
29
34
|
end
|
|
30
35
|
end
|
|
31
36
|
|
|
32
|
-
private
|
|
33
|
-
|
|
34
37
|
attr_reader :file_proxy, :parent_map
|
|
35
38
|
end
|
|
36
39
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lowkey
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-10-17 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: prism
|
|
@@ -37,12 +37,14 @@ files:
|
|
|
37
37
|
- lib/factories/source_factory.rb
|
|
38
38
|
- lib/interfaces/adapter.rb
|
|
39
39
|
- lib/interfaces/proxy.rb
|
|
40
|
+
- lib/interfaces/releasable_node.rb
|
|
40
41
|
- lib/lowkey.rb
|
|
41
42
|
- lib/maps/parent_map.rb
|
|
42
43
|
- lib/models/source.rb
|
|
43
44
|
- lib/proxies/body_proxy.rb
|
|
44
45
|
- lib/proxies/class_proxy.rb
|
|
45
46
|
- lib/proxies/file_proxy.rb
|
|
47
|
+
- lib/proxies/method_call_proxy.rb
|
|
46
48
|
- lib/proxies/method_proxy.rb
|
|
47
49
|
- lib/proxies/module_proxy.rb
|
|
48
50
|
- lib/proxies/param_proxy.rb
|
|
@@ -73,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
73
75
|
- !ruby/object:Gem::Version
|
|
74
76
|
version: '0'
|
|
75
77
|
requirements: []
|
|
76
|
-
rubygems_version:
|
|
78
|
+
rubygems_version: 3.6.2
|
|
77
79
|
specification_version: 4
|
|
78
80
|
summary: An API for accessing PRISM metadata easily and efficiently
|
|
79
81
|
test_files: []
|