objc-dependency-tree-generator 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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