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.
@@ -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