objc-dependency-tree-generator 0.0.8 → 0.1.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/bin/objc_dependency_tree_generator +2 -2
- data/lib/dependency_tree.rb +119 -0
- data/lib/helpers/objc_dependency_tree_generator_helper.rb +58 -0
- data/lib/helpers/swift_primitives.rb +282 -0
- data/lib/{objc_dependencies_generator.rb → objc/objc_dependencies_generator.rb} +0 -0
- data/lib/objc_dependency_tree_generator.rb +106 -89
- data/lib/sourcekitten/sourcekitten_dependencies_generator.rb +229 -0
- data/lib/swift-ast-dump/swift_ast_dependencies_generator.rb +216 -0
- data/lib/swift-ast-dump/swift_ast_parser.rb +217 -0
- data/lib/{swift_dependencies_generator.rb → swift/swift_dependencies_generator.rb} +0 -0
- data/lib/tree_serializer.rb +64 -0
- metadata +14 -8
- data/lib/objc_dependency_tree_generator_helper.rb +0 -67
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'helpers/objc_dependency_tree_generator_helper'
|
2
|
+
require 'swift-ast-dump/swift_ast_parser'
|
3
|
+
|
4
|
+
class SwiftAstDependenciesGenerator
|
5
|
+
|
6
|
+
def initialize(ast_file, dump_parsed_tree, verbose = false)
|
7
|
+
@ast_file = ast_file
|
8
|
+
@verbose = verbose
|
9
|
+
@dump_parsed_tree = dump_parsed_tree
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [DependencyTree]
|
13
|
+
def generate_dependencies
|
14
|
+
|
15
|
+
@tree = DependencyTree.new
|
16
|
+
@context = []
|
17
|
+
@generics_context = []
|
18
|
+
|
19
|
+
@ast_tree = SwiftAST::Parser.new().parse_build_log_output(File.read(@ast_file))
|
20
|
+
@ast_tree.dump if @dump_parsed_tree
|
21
|
+
scan_source_files
|
22
|
+
|
23
|
+
@tree
|
24
|
+
end
|
25
|
+
|
26
|
+
def scan_source_files
|
27
|
+
source_files = @ast_tree.find_nodes("source_file")
|
28
|
+
return scan_source_files_classes(@ast_tree) if source_files.empty?
|
29
|
+
|
30
|
+
source_files.each { |source_file|
|
31
|
+
scan_source_files_classes(source_file)
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def scan_source_files_classes(root)
|
36
|
+
classes = root.find_nodes("class_decl")
|
37
|
+
classes.each { |node|
|
38
|
+
next unless classname = node.parameters.first
|
39
|
+
@tree.register(classname, DependencyItemType::CLASS)
|
40
|
+
}
|
41
|
+
|
42
|
+
protocols = @ast_tree.find_nodes("protocol")
|
43
|
+
protocols.each { |node|
|
44
|
+
next unless protoname = node.parameters.first
|
45
|
+
@tree.register(protoname, DependencyItemType::PROTOCOL)
|
46
|
+
}
|
47
|
+
|
48
|
+
structs = @ast_tree.find_nodes("struct_decl")
|
49
|
+
structs.each { |node|
|
50
|
+
next unless struct_name = node.parameters.first
|
51
|
+
@tree.register(struct_name, DependencyItemType::STRUCTURE)
|
52
|
+
}
|
53
|
+
|
54
|
+
classes.each { |node|
|
55
|
+
next unless classname = node.parameters.first
|
56
|
+
generic_names = register_generic_parameters(node, classname)
|
57
|
+
@generics_context << generic_names
|
58
|
+
register_typealiases(node, classname)
|
59
|
+
|
60
|
+
register_inheritance(node, classname)
|
61
|
+
register_variables(node, classname)
|
62
|
+
register_calls(node, classname)
|
63
|
+
register_function_parameters(node, classname)
|
64
|
+
|
65
|
+
@generics_context.pop
|
66
|
+
|
67
|
+
}
|
68
|
+
|
69
|
+
protocols.each { |node|
|
70
|
+
return unless proto_name = node.parameters.first
|
71
|
+
generic_names = register_generic_parameters(node, proto_name)
|
72
|
+
@generics_context << generic_names
|
73
|
+
|
74
|
+
register_inheritance(node, proto_name)
|
75
|
+
register_function_parameters(node, proto_name)
|
76
|
+
@generics_context.pop
|
77
|
+
|
78
|
+
}
|
79
|
+
|
80
|
+
structs.each { |node|
|
81
|
+
next unless classname = node.parameters.first
|
82
|
+
generic_names = register_generic_parameters(node, classname)
|
83
|
+
@generics_context << generic_names
|
84
|
+
register_typealiases(node, classname)
|
85
|
+
|
86
|
+
register_inheritance(node, classname)
|
87
|
+
register_variables(node, classname)
|
88
|
+
register_calls(node, classname)
|
89
|
+
register_function_parameters(node, classname)
|
90
|
+
|
91
|
+
@generics_context.pop
|
92
|
+
}
|
93
|
+
|
94
|
+
extensions = @ast_tree.find_nodes("extension_decl")
|
95
|
+
extensions.each { |node|
|
96
|
+
return unless extension_name = node.parameters.first
|
97
|
+
register_inheritance(node, extension_name)
|
98
|
+
}
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
def register_inheritance(node, name)
|
103
|
+
inheritance = node.parameters.drop_while { |el| el != "inherits:" }
|
104
|
+
inheritance = inheritance.drop(1)
|
105
|
+
inheritance.each { |inh|
|
106
|
+
inh_name = inh.chomp(",")
|
107
|
+
add_tree_dependency(name, inh_name, DependencyLinkType::INHERITANCE)
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
def register_typealiases(node, name)
|
112
|
+
node.on_node("typealias") { |typealias|
|
113
|
+
typealias.parameters.select { |el| el.start_with?("type=") }.each { |type_decl|
|
114
|
+
type_name = type_decl.sub("type=", '')[1..-2].chomp("?")
|
115
|
+
add_tree_dependency(name, type_name, DependencyLinkType::PARAMETER)
|
116
|
+
}
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
def register_generic_parameters(node, name)
|
121
|
+
return [] unless generic = node.parameters[1] # Second parameter
|
122
|
+
return [] unless generic[0] + generic[-1] == "<>"
|
123
|
+
|
124
|
+
# REmove brackets
|
125
|
+
generic = generic[1..-2]
|
126
|
+
|
127
|
+
generic_decls = []
|
128
|
+
generic.split(",").each { |decl|
|
129
|
+
parts = decl.split(":")
|
130
|
+
leftPart = parts[0]
|
131
|
+
rightPart = parts[1]
|
132
|
+
|
133
|
+
next unless leftPart
|
134
|
+
|
135
|
+
generic_name = leftPart.strip || leftPart
|
136
|
+
generic_decls << generic_name
|
137
|
+
|
138
|
+
next unless rightPart
|
139
|
+
|
140
|
+
|
141
|
+
rightPart.split("&").each { |protocol_or_class|
|
142
|
+
proto_name = protocol_or_class.strip || protocol_or_class
|
143
|
+
add_tree_dependency(name, proto_name, DependencyLinkType::INHERITANCE)
|
144
|
+
}
|
145
|
+
}
|
146
|
+
|
147
|
+
generic_decls
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
def register_variables(node, name)
|
152
|
+
node.on_node("var_decl") { |variable|
|
153
|
+
next unless type_decl = variable.parameters.find { |el| el.start_with?("type=") }
|
154
|
+
type_name = type_decl.sub("type=", '')[1..-2].chomp("?")
|
155
|
+
add_tree_dependency(name, type_name, DependencyLinkType::IVAR)
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
def register_calls(node, name)
|
161
|
+
node.on_node("call_expr") { |variable|
|
162
|
+
next unless type_decl = variable.parameters.find { |el| el.start_with?("type=") }
|
163
|
+
type_name = type_decl.sub("type=", '')[1..-2].chomp("?")
|
164
|
+
add_tree_dependency(name, type_name, DependencyLinkType::CALL)
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
def register_function_parameters(node, name)
|
169
|
+
node.on_node("func_decl") { |func_decl|
|
170
|
+
|
171
|
+
generic_names = register_generic_parameters(func_decl, name)
|
172
|
+
@generics_context << generic_names
|
173
|
+
|
174
|
+
func_decl.on_node("parameter_list") { |param_list|
|
175
|
+
param_list.on_node("parameter") { |parameter|
|
176
|
+
next unless type_decl = parameter.parameters.find { |el| el.start_with?("type=") }
|
177
|
+
type_name = type_decl.sub("type=", '')[1..-2].chomp("?")
|
178
|
+
add_tree_dependency(name, type_name, DependencyLinkType::PARAMETER)
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
func_decl.on_node("result") { |result_decl|
|
183
|
+
result_decl.on_node("type_ident") { |type_id|
|
184
|
+
type_id.on_node("component") { |comp|
|
185
|
+
next unless type_decl = comp.parameters.find { |el| el.start_with?("id=") }
|
186
|
+
type_name = type_decl.sub("id=", '')[1..-2].chomp("?")
|
187
|
+
add_tree_dependency(name, type_name, DependencyLinkType::PARAMETER)
|
188
|
+
}
|
189
|
+
}
|
190
|
+
}
|
191
|
+
@generics_context.pop
|
192
|
+
}
|
193
|
+
end
|
194
|
+
|
195
|
+
def normalized_name(the_name)
|
196
|
+
the_name.sub("inout ", "")[/(\w|\d|\.)+/]
|
197
|
+
end
|
198
|
+
|
199
|
+
def add_tree_dependency(from_name, to_name, type)
|
200
|
+
# skip names from generics
|
201
|
+
# we also will need to skip generics_from_functions
|
202
|
+
skip_names = (@generics_context || []).flatten
|
203
|
+
|
204
|
+
from = normalized_name(from_name)
|
205
|
+
return if skip_names.include? from
|
206
|
+
|
207
|
+
to = normalized_name(to_name)
|
208
|
+
return if skip_names.include? to
|
209
|
+
|
210
|
+
return unless to
|
211
|
+
return unless from
|
212
|
+
@tree.add(from, to, type)
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
module SwiftAST
|
2
|
+
class Parser
|
3
|
+
def parse(string)
|
4
|
+
@scanner = StringScanner.new(string)
|
5
|
+
node = scan_children.first
|
6
|
+
node
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def parse_build_log_output(string)
|
11
|
+
@scanner = StringScanner.new(string)
|
12
|
+
return unless @scanner.scan_until(/^\(source_file/)
|
13
|
+
|
14
|
+
@scanner.pos = @scanner.pos - 12
|
15
|
+
children = scan_children
|
16
|
+
|
17
|
+
return if children.empty?
|
18
|
+
Node.new("ast", [], children)
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def scan_parameters
|
23
|
+
parameters = []
|
24
|
+
|
25
|
+
while true
|
26
|
+
@scanner.skip(/\s*/)
|
27
|
+
parameter = scan_parameter?
|
28
|
+
break unless parameter
|
29
|
+
parameters << parameter
|
30
|
+
end
|
31
|
+
|
32
|
+
parameters
|
33
|
+
end
|
34
|
+
|
35
|
+
def scan_parameters_from_types
|
36
|
+
first_param = @scanner.scan(/\d+:/)
|
37
|
+
return [] unless first_param
|
38
|
+
return [first_param] + scan_parameters
|
39
|
+
end
|
40
|
+
|
41
|
+
def scan_children(level = 0)
|
42
|
+
children = []
|
43
|
+
while true
|
44
|
+
return children unless whitespaces = whitespaces_at(level)
|
45
|
+
node_name = scan_name?
|
46
|
+
return children if node_name == "source_file" && level != 0 && unscan(node_name + whitespaces)
|
47
|
+
node_parameters = scan_parameters
|
48
|
+
|
49
|
+
node_children = scan_children(level + 1)
|
50
|
+
|
51
|
+
while next_params = scan_parameters_from_types # these are stupid params alike
|
52
|
+
break if next_params.empty?
|
53
|
+
node_parameters += next_params
|
54
|
+
node_children += scan_children(level + 1)
|
55
|
+
end
|
56
|
+
node = Node.new(node_name, node_parameters, node_children)
|
57
|
+
|
58
|
+
children << node
|
59
|
+
@scanner.scan(/(\s|\\|\n|\r|\t)*\)/)
|
60
|
+
end
|
61
|
+
children
|
62
|
+
end
|
63
|
+
|
64
|
+
def whitespaces_at(level = 0)
|
65
|
+
whitespaces = @scanner.scan(/(\s|\\|\n|\r|\t)*\(/)
|
66
|
+
if level == 0 && whitespaces.nil?
|
67
|
+
whitespaces = @scanner.scan(/.*?\(source_file/m)
|
68
|
+
return nil unless whitespaces
|
69
|
+
unscan("source_file")
|
70
|
+
end
|
71
|
+
whitespaces
|
72
|
+
end
|
73
|
+
|
74
|
+
def unscan(string)
|
75
|
+
@scanner.pos = @scanner.pos - string.length
|
76
|
+
end
|
77
|
+
|
78
|
+
def scan_name?
|
79
|
+
el_name = @scanner.scan(/#?[\w:]+/)
|
80
|
+
el_name
|
81
|
+
end
|
82
|
+
|
83
|
+
def scan_parameter?(is_parsing_rvalue = false)
|
84
|
+
#white spaces are skipped
|
85
|
+
|
86
|
+
# scan everything until space or opening sequence like ( < ' ".
|
87
|
+
# Since we can end up with closing bracket - we alos check for )
|
88
|
+
|
89
|
+
prefix = @scanner.scan(/[^\s()'"\[\\]+/) if is_parsing_rvalue
|
90
|
+
prefix = @scanner.scan(/[^\s()<'"\[\\=]+/) unless is_parsing_rvalue
|
91
|
+
|
92
|
+
next_char = @scanner.peek(1)
|
93
|
+
return nil unless next_char
|
94
|
+
should_unwrap_strings = !is_parsing_rvalue && !prefix
|
95
|
+
|
96
|
+
case next_char
|
97
|
+
when " " # next parameter
|
98
|
+
result = prefix
|
99
|
+
when "\\" # next parameter
|
100
|
+
@scanner.scan(/./)
|
101
|
+
result = prefix
|
102
|
+
|
103
|
+
when "\n" # next parameter
|
104
|
+
result = prefix
|
105
|
+
when ")" # closing bracket == end of element
|
106
|
+
result = prefix
|
107
|
+
when "\"" # doube quoted string
|
108
|
+
result = @scanner.scan(/./) + @scanner.scan_until(/"/)
|
109
|
+
result = result[1..-2] if should_unwrap_strings
|
110
|
+
result = (prefix || "") + result
|
111
|
+
when "'" # single quoted string
|
112
|
+
result = @scanner.scan(/./) + @scanner.scan_until(/'/)
|
113
|
+
result = result[1..-2] if should_unwrap_strings
|
114
|
+
result = (prefix || "") + result + (scan_parameter?(is_parsing_rvalue) || "")
|
115
|
+
when "<" # kinda generic
|
116
|
+
result = (prefix || "") + @scanner.scan(/./)
|
117
|
+
#in some cases this can be last char, just because we can end up with a=sdsd.function.<
|
118
|
+
result += @scanner.scan_until(/>/) + (scan_parameter?(is_parsing_rvalue) || "")
|
119
|
+
when "("
|
120
|
+
return nil if !prefix && !is_parsing_rvalue
|
121
|
+
result = (prefix || "") + @scanner.scan_until(/\)/) + (scan_parameter?(is_parsing_rvalue) || "")
|
122
|
+
when "["
|
123
|
+
result = (prefix || "") + scan_range + (scan_parameter?(is_parsing_rvalue) || "")
|
124
|
+
when "="
|
125
|
+
result = prefix + @scanner.scan(/./) + (scan_parameter?(true) || "")
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
# puts "prefix is '#{prefix}' ||#{is_parsing_rvalue} result =#{result}"
|
130
|
+
|
131
|
+
result
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
def scan_range
|
136
|
+
return unless @scanner.peek(1) == "["
|
137
|
+
result = @scanner.scan(/./)
|
138
|
+
|
139
|
+
while true
|
140
|
+
inside = @scanner.scan(/[^\]\[]+/) #everything but [ or ]
|
141
|
+
result += inside || ""
|
142
|
+
next_char = @scanner.peek(1)
|
143
|
+
|
144
|
+
return result + @scanner.scan(/./) if next_char == "]" # we found the end
|
145
|
+
result += scan_range if next_char == "["
|
146
|
+
raise "Unexpected character #{next_char} - [ or ] expected" if next_char != "[" && next_char != "]"
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
def scan_line_and_column
|
152
|
+
@scanner.scan(/:\d+:\d+/)
|
153
|
+
end
|
154
|
+
|
155
|
+
def isalpha(str)
|
156
|
+
!str.match(/[^A-Za-z@_]/)
|
157
|
+
end
|
158
|
+
def isAlphaDigit(str)
|
159
|
+
!str.match(/[^A-Za-z@_0-9]/)
|
160
|
+
end
|
161
|
+
def isalphaOrDot(str)
|
162
|
+
!str.match(/[^A-Za-z@_.,]/)
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
class Node
|
169
|
+
|
170
|
+
def initialize(name, parameters = [], children = [])
|
171
|
+
@name = name
|
172
|
+
@parameters = parameters
|
173
|
+
@children = children
|
174
|
+
end
|
175
|
+
|
176
|
+
def name
|
177
|
+
@name
|
178
|
+
end
|
179
|
+
|
180
|
+
def parameters
|
181
|
+
@parameters
|
182
|
+
end
|
183
|
+
|
184
|
+
def children
|
185
|
+
@children
|
186
|
+
end
|
187
|
+
|
188
|
+
def dump(level = 0)
|
189
|
+
@@line = 0 if level == 0
|
190
|
+
puts "\n" if level == 0
|
191
|
+
puts " " * level + "[#{@@line}][#{@name} #{@parameters}"
|
192
|
+
@@line = @@line + 1
|
193
|
+
@children.each { |child| child.dump(level + 1) }
|
194
|
+
end
|
195
|
+
|
196
|
+
def find_nodes(type)
|
197
|
+
found_nodes = []
|
198
|
+
@children.each { |child|
|
199
|
+
if child.name == type
|
200
|
+
found_nodes << child
|
201
|
+
else
|
202
|
+
found_nodes += child.find_nodes(type)
|
203
|
+
end
|
204
|
+
}
|
205
|
+
found_nodes
|
206
|
+
end
|
207
|
+
|
208
|
+
def on_node(type, &block)
|
209
|
+
@children.each { |child|
|
210
|
+
yield child if child.name == type
|
211
|
+
child.on_node(type, &block)
|
212
|
+
}
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
File without changes
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class TreeSerializer
|
2
|
+
attr_reader :dependency_tree
|
3
|
+
|
4
|
+
# @param [DependencyTree] dependency_tree
|
5
|
+
def initialize(dependency_tree)
|
6
|
+
@dependency_tree = dependency_tree
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
# @return [String]
|
11
|
+
def serialize(output_format)
|
12
|
+
object_to_serialize = {}
|
13
|
+
object_to_serialize[:links] = @dependency_tree.links_with_types
|
14
|
+
object_to_serialize[:links_count] = @dependency_tree.links_count
|
15
|
+
object_to_serialize[:objects] = Hash[
|
16
|
+
@dependency_tree.objects.map do |o|
|
17
|
+
[o, { type: @dependency_tree.type(o) }]
|
18
|
+
end
|
19
|
+
]
|
20
|
+
|
21
|
+
case output_format
|
22
|
+
when 'dot'
|
23
|
+
serialize_to_dot(object_to_serialize)
|
24
|
+
when 'json-pretty'
|
25
|
+
serialize_to_json_pretty(object_to_serialize)
|
26
|
+
when 'json'
|
27
|
+
serialize_to_json(object_to_serialize)
|
28
|
+
when 'json-var'
|
29
|
+
serialize_to_json_var(object_to_serialize)
|
30
|
+
when 'yaml'
|
31
|
+
serialize_to_yaml(object_to_serialize)
|
32
|
+
else
|
33
|
+
raise
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def serialize_to_yaml(object_to_serialize)
|
39
|
+
object_to_serialize.to_yaml
|
40
|
+
end
|
41
|
+
|
42
|
+
def serialize_to_json_var(object_to_serialize)
|
43
|
+
'var dependencies = ' + object_to_serialize.to_json
|
44
|
+
end
|
45
|
+
|
46
|
+
def serialize_to_json(object_to_serialize)
|
47
|
+
object_to_serialize.to_json
|
48
|
+
end
|
49
|
+
|
50
|
+
def serialize_to_json_pretty(object_to_serialize)
|
51
|
+
JSON.pretty_generate(object_to_serialize)
|
52
|
+
end
|
53
|
+
|
54
|
+
def serialize_to_dot(object_to_serialize)
|
55
|
+
indent = "\t"
|
56
|
+
s = "digraph dependencies {\n#{indent}node [fontname=monospace, fontsize=9, shape=box, style=rounded]\n"
|
57
|
+
object_to_serialize[:links].each do |link|
|
58
|
+
s += "#{indent}\"#{link['source']}\" -> \"#{link['dest']}\"\n"
|
59
|
+
end
|
60
|
+
s += "}\n"
|
61
|
+
s
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|