-
Notifications
You must be signed in to change notification settings - Fork 409
/
symbol.rb
233 lines (198 loc) · 7.38 KB
/
symbol.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# frozen_string_literal: true
# rubocop:disable Metrics/ClassLength
module Jazzy
module SymbolGraph
# A Symbol is a tidied-up SymbolGraph JSON object
class Symbol
attr_accessor :usr
attr_accessor :path_components
attr_accessor :declaration
attr_accessor :kind
attr_accessor :acl
attr_accessor :spi
attr_accessor :location # can be nil, keys :filename :line :character
attr_accessor :constraints # array, can be empty
attr_accessor :doc_comments # can be nil
attr_accessor :attributes # array, can be empty
attr_accessor :generic_type_params # set, can be empty
attr_accessor :parameter_names # array, can be nil
def name
path_components[-1] || '??'
end
def initialize(hash)
self.usr = hash[:identifier][:precise]
self.path_components = hash[:pathComponents]
raw_decl = hash[:declarationFragments].map { |f| f[:spelling] }.join
init_kind(hash[:kind][:identifier])
init_declaration(raw_decl)
if func_signature = hash[:functionSignature]
init_func_signature(func_signature)
end
init_acl(hash[:accessLevel])
self.spi = hash[:spi]
if location = hash[:location]
init_location(location)
end
init_constraints(hash, raw_decl)
if comments_hash = hash[:docComment]
init_doc_comments(comments_hash)
end
init_attributes(hash[:availability] || [])
init_generic_type_params(hash)
end
# Repair problems with SymbolGraph's declprinter
def init_declaration(raw_decl)
# Too much 'Self.TypeName'; omitted arg labels look odd;
# duplicated constraints; swift 5.3 vs. master workaround
self.declaration = raw_decl
.gsub(/\bSelf\./, '')
.gsub(/(?<=\(|, )_: /, '_ arg: ')
.gsub(/ where.*$/, '')
if kind == 'source.lang.swift.decl.class'
declaration.sub!(/\s*:.*$/, '')
end
end
# Remember pieces of methods for later markdown parsing
def init_func_signature(func_signature)
self.parameter_names =
(func_signature[:parameters] || []).map { |h| h[:name] }
end
# Mapping SymbolGraph's declkinds to SourceKit
KIND_MAP = {
'class' => 'class',
'struct' => 'struct',
'enum' => 'enum',
'enum.case' => 'enumelement', # intentional
'protocol' => 'protocol',
'init' => 'function.constructor',
'deinit' => 'function.destructor',
'func.op' => 'function.operator',
'type.method' => 'function.method.class',
'static.method' => 'function.method.static',
'method' => 'function.method.instance',
'func' => 'function.free',
'type.property' => 'var.class',
'static.property' => 'var.static',
'property' => 'var.instance',
'var' => 'var.global',
'subscript' => 'function.subscript',
'type.subscript' => 'function.subscript',
'static.subscript' => 'function.subscript',
'typealias' => 'typealias',
'associatedtype' => 'associatedtype',
}.freeze
# We treat 'static var' differently to 'class var'
def adjust_kind_for_declaration(kind)
return kind unless declaration =~ /\bstatic\b/
kind.gsub(/type/, 'static')
end
def init_kind(kind)
adjusted = adjust_kind_for_declaration(kind)
sourcekit_kind = KIND_MAP[adjusted.sub('swift.', '')]
raise "Unknown symbol kind '#{kind}'" unless sourcekit_kind
self.kind = "source.lang.swift.decl.#{sourcekit_kind}"
end
# Mapping SymbolGraph's ACL to SourceKit
def init_acl(acl)
self.acl = "source.lang.swift.accessibility.#{acl}"
end
# Symbol location - only available for public+ decls
def init_location(loc_hash)
self.location = {}
location[:filename] = loc_hash[:uri].sub(%r{^file://}, '')
location[:line] = loc_hash[:position][:line]
location[:character] = loc_hash[:position][:character]
end
# Generic constraints: in one or both of two places.
# There can be duplicates; these are removed by `Constraint`.
def init_constraints(hash, raw_decl)
raw_constraints = %i[swiftGenerics swiftExtension].flat_map do |key|
next [] unless container = hash[key]
container[:constraints] || []
end
constraints =
Constraint.new_list_for_symbol(raw_constraints, path_components)
if raw_decl =~ / where (.*)$/
constraints +=
Constraint.new_list_from_declaration(Regexp.last_match[1])
end
self.constraints = constraints.sort.uniq
end
# Generic type params
def init_generic_type_params(hash)
self.generic_type_params = Set.new(
if (generics = hash[:swiftGenerics]) &&
(parameters = generics[:parameters])
parameters.map { |p| p[:name] }
else
[]
end,
)
end
def init_doc_comments(comments_hash)
self.doc_comments = comments_hash[:lines]
.map { |l| l[:text] }
.join("\n")
end
# Availability
# Re-encode this as Swift. Should really teach Jazzy about these,
# could maybe then do something smarter here.
def availability_attributes(avail_hash_list)
avail_hash_list.map do |avail|
str = '@available('
if avail[:isUnconditionallyDeprecated]
str += '*, deprecated'
elsif domain = avail[:domain]
str += domain
%i[introduced deprecated obsoleted].each do |event|
if version = avail[event]
str += ", #{event}: #{decode_version(version)}"
end
end
else
warn "Found confusing availability: #{avail}"
next nil
end
str += ", message: \"#{avail[:message]}\"" if avail[:message]
str += ", renamed: \"#{avail[:renamed]}\"" if avail[:renamed]
str + ')'
end.compact
end
def decode_version(hash)
str = hash[:major].to_s
str += ".#{hash[:minor]}" if hash[:minor]
str += ".#{hash[:patch]}" if hash[:patch]
str
end
def spi_attributes
spi ? ['@_spi(Unknown)'] : []
end
def init_attributes(avail_hash_list)
self.attributes =
availability_attributes(avail_hash_list) + spi_attributes
end
# Sort order
include Comparable
def <=>(other)
# Things with location: order by file/line/column
# (pls tell what wheel i am reinventing :/)
if location && other_loc = other.location
if location[:filename] == other_loc[:filename]
if location[:line] == other_loc[:line]
return location[:character] <=> other_loc[:character]
end
return location[:line] <=> other_loc[:line]
end
return location[:filename] <=> other_loc[:filename]
end
# Things with a location before things without a location
return +1 if location.nil? && other.location
return -1 if location && other.location.nil?
# Things without a location: by name and then USR
return usr <=> other.usr if name == other.name
name <=> other.name
end
end
end
end
# rubocop:enable Metrics/ClassLength