Class: Ibex::LSP::SymbolIndexBuilder

Inherits:
Object
  • Object
show all
Includes:
SymbolIndexPrecedenceReferences, SymbolIndexSourceQueries
Defined in:
lib/ibex/lsp/symbol_index_builder.rb,
sig/ibex/lsp/symbol_index_builder.rbs

Overview

Builds source occurrences and hover metadata from lossless frontend documents.

Instance Method Summary collapse

Methods included from SymbolIndexPrecedenceReferences

#add_rule_reference, #collect_alternative_precedence

Methods included from SymbolIndexSourceQueries

#declaration_reference_names, #location_before?, #parameter_tokens, #token_at, #tokens_between

Constructor Details

#initialize(store, files) ⇒ SymbolIndexBuilder

Returns a new instance of SymbolIndexBuilder.

RBS:

  • (DocumentStore store, Array[String] files) -> void

Parameters:



11
12
13
14
15
16
17
18
19
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 11

def initialize(store, files)
  @store = store
  @files = files
  @occurrences = [] #: Array[SymbolOccurrence]
  @documents = {} #: Hash[String, Frontend::SourceDocument]
  @global_kinds = {} #: Hash[String, Symbol]
  @rule_data = {} #: Hash[String, Hash[Symbol, untyped]]
  @terminal_data = Hash.new { |hash, key| hash[key] = {} } #: Hash[String, Hash[Symbol, untyped]]
end

Instance Method Details

#add_global_reference(path, token, name) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, Frontend::Token token, String name) -> void

Parameters:



207
208
209
210
211
212
213
214
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 207

def add_global_reference(path, token, name)
  return unless token.span

  kind = @global_kinds[name] || :terminal
  data = symbol_data(kind, name)
  add_occurrence(name: name, kind: kind, role: :reference, key: global_key(name),
                 path: path, span: token.span, data: data)
end

#add_named_tokens(path, tokens, names, kind, role) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, Array[Frontend::Token] tokens, Array[String] names, Symbol kind, Symbol role) -> void

Parameters:

  • path (String)
  • tokens (Array[Frontend::Token])
  • names (Array[String])
  • kind (Symbol)
  • role (Symbol)


224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 224

def add_named_tokens(path, tokens, names, kind, role)
  remaining = tokens.dup
  names.each do |name|
    index = remaining.index { |token| token.value == name }
    token = index && remaining.delete_at(index)
    next unless token&.span

    @global_kinds[name] ||= kind
    add_occurrence(name: name, kind: kind, role: role, key: global_key(name),
                   path: path, span: token.span, data: @terminal_data[name])
  end
end

#add_occurrence(**attributes) ⇒ void

This method returns an undefined value.

RBS:

  • (**untyped attributes) -> void

Parameters:

  • attributes (Object)


238
239
240
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 238

def add_occurrence(**attributes)
  @occurrences << SymbolOccurrence.new(**attributes)
end

#build[ Array[SymbolOccurrence], Hash[String, Frontend::SourceDocument] ]

RBS:

  • () -> [Array[SymbolOccurrence], Hash[String, Frontend::SourceDocument]]

Returns:



22
23
24
25
26
27
28
29
30
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 22

def build
  collect_documents
  # Definitions from every closure file must exist before references are classified.
  # rubocop:disable Style/CombinableLoops
  @documents.each { |path, document| collect_definitions(path, document) }
  @documents.each { |path, document| collect_references(path, document) }
  # rubocop:enable Style/CombinableLoops
  [@occurrences.freeze, @documents.freeze]
end

#canonical_include_target(declaration) ⇒ String?

RBS:

  • (Frontend::AST::Include declaration) -> String?

Parameters:

Returns:

  • (String, nil)


258
259
260
261
262
263
264
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 258

def canonical_include_target(declaration)
  candidate = File.expand_path(declaration.path, File.dirname(declaration.loc.file))
  target = @store.loader.canonical_path(candidate, allow_missing: true)
  @store.workspace.root_for(target) && target
rescue SystemCallError
  nil
end

#collect_declaration(path, document, declaration, following) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, Frontend::SourceDocument document, Frontend::AST::declaration declaration, Frontend::Location? following) -> void

Parameters:



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 58

def collect_declaration(path, document, declaration, following)
  tokens = tokens_between(document, declaration.loc, following)
  case declaration
  when Frontend::AST::Tokens
    add_named_tokens(path, tokens.drop(1), declaration.names, :terminal, :definition)
  when Frontend::AST::DisplayName
    @terminal_data[declaration.name][:display] = declaration.value
  when Frontend::AST::SemanticType
    @terminal_data[declaration.name][:type] = declaration.value
  when Frontend::AST::Precedence
    collect_precedence(declaration)
  when Frontend::AST::Include
    collect_include(path, tokens, declaration)
  end
end

#collect_declaration_references(path, document, declaration, following) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, Frontend::SourceDocument document, Frontend::AST::declaration declaration, Frontend::Location? following) -> void

Parameters:



154
155
156
157
158
159
160
161
162
163
164
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 154

def collect_declaration_references(path, document, declaration, following)
  names = declaration_reference_names(declaration)
  return if names.empty?

  remaining = tokens_between(document, declaration.loc, following).drop(1)
  names.each do |name|
    index = remaining.index { |token| token.value == name }
    token = index && remaining.delete_at(index)
    add_global_reference(path, token, name) if token
  end
end

#collect_definitions(path, document) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, Frontend::SourceDocument document) -> void

Parameters:



44
45
46
47
48
49
50
51
52
53
54
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 44

def collect_definitions(path, document)
  ast = document.ast
  return unless ast

  declarations = ast.declarations
  declarations.each_with_index do |declaration, index|
    following = declarations[index + 1]&.loc || ast.rules.first&.loc
    collect_declaration(path, document, declaration, following)
  end
  ast.rules.each { |rule| collect_rule_definition(path, document, rule) }
end

#collect_documentsvoid

This method returns an undefined value.

RBS:

  • () -> void



35
36
37
38
39
40
41
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 35

def collect_documents
  @files.each do |path|
    snapshot = @store.snapshot_for(path)
    document = snapshot&.fetch(:document)
    @documents[path] = document if document
  end
end

#collect_include(path, tokens, declaration) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, Array[Frontend::Token] tokens, Frontend::AST::Include declaration) -> void

Parameters:



86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 86

def collect_include(path, tokens, declaration)
  token = tokens.find { |candidate| candidate.type == :literal }
  return unless token&.span

  target = canonical_include_target(declaration)
  return unless target

  add_occurrence(
    name: declaration.path, kind: :include, role: :reference, key: [:include, target],
    path: path, span: token.span, data: { target: target }
  )
end

#collect_item(path, document, item, rule, rule_id) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, Frontend::SourceDocument document, Frontend::AST::item item, Frontend::AST::Rule rule, Integer rule_id) -> void

Parameters:



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 180

def collect_item(path, document, item, rule, rule_id)
  case item
  when Frontend::AST::SymbolReference, Frontend::AST::ParameterizedReference
    collect_named_item(path, document, item, rule, rule_id)
    item.arguments.each { |child| collect_item(path, document, child, rule, rule_id) } if
      item.is_a?(Frontend::AST::ParameterizedReference)
  when Frontend::AST::Optional, Frontend::AST::Star, Frontend::AST::Plus
    collect_item(path, document, item.item, rule, rule_id)
  when Frontend::AST::Group
    item.alternatives.flatten.each { |child| collect_item(path, document, child, rule, rule_id) }
  when Frontend::AST::SeparatedList
    collect_item(path, document, item.item, rule, rule_id)
    collect_item(path, document, item.separator, rule, rule_id)
  end
end

#collect_named_item(path, document, item, rule, rule_id) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, Frontend::SourceDocument document, Frontend::AST::SymbolReference | Frontend::AST::ParameterizedReference item, Frontend::AST::Rule rule, Integer rule_id) -> void

Parameters:



199
200
201
202
203
204
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 199

def collect_named_item(path, document, item, rule, rule_id)
  token = token_at(document, item.loc, item.name)
  return unless token&.span

  add_rule_reference(path, token, item.name, rule, rule_id)
end

#collect_parameters(path, document, rule, lhs_token) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, Frontend::SourceDocument document, Frontend::AST::Rule rule, Frontend::Token lhs_token) -> void

Parameters:



123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 123

def collect_parameters(path, document, rule, lhs_token)
  return if rule.parameters.empty?

  rule_id = lhs_token.span&.start_byte || 0
  rule.parameters.zip(parameter_tokens(document, lhs_token)).each do |parameter, token|
    next unless token&.span

    add_occurrence(
      name: parameter, kind: :parameter, role: :definition,
      key: parameter_key(path, rule_id, parameter), path: path, span: token.span,
      data: { rule: rule.lhs }
    )
  end
end

#collect_precedence(declaration) ⇒ void

This method returns an undefined value.

RBS:

  • (Frontend::AST::Precedence declaration) -> void

Parameters:



75
76
77
78
79
80
81
82
83
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 75

def collect_precedence(declaration)
  declaration.levels.each_with_index do |level, index|
    level.symbols.each do |name|
      @terminal_data[name][:precedence] = {
        direction: declaration.direction, associativity: level.associativity, level: index
      }
    end
  end
end

#collect_references(path, document) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, Frontend::SourceDocument document) -> void

Parameters:



139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 139

def collect_references(path, document)
  ast = document.ast
  return unless ast

  ast.declarations.each_with_index do |declaration, index|
    following = ast.declarations[index + 1]&.loc || ast.rules.first&.loc
    collect_declaration_references(path, document, declaration, following)
  end
  ast.rules.each_with_index do |rule, index|
    collect_rule_references(path, document, rule, ast.rules[index + 1]&.loc)
  end
end

#collect_rule_definition(path, document, rule) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, Frontend::SourceDocument document, Frontend::AST::Rule rule) -> void

Parameters:



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 100

def collect_rule_definition(path, document, rule)
  token = token_at(document, rule.loc, rule.lhs)
  return unless token&.span

  @global_kinds[rule.lhs] ||= :rule
  signature = "#{rule.lhs}#{parameter_signature(rule.parameters)}"
  signature = "%inline #{signature}" if rule.inline
  data = {
    signature: signature,
    documentation: rule.documentation,
    inline: rule.inline,
    parameters: rule.parameters
  }
  @rule_data[rule.lhs] ||= data
  add_occurrence(
    name: rule.lhs, kind: :rule, role: :definition, key: global_key(rule.lhs),
    path: path, span: token.span, data: data
  )
  collect_parameters(path, document, rule, token)
end

#collect_rule_references(path, document, rule, following_rule) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, Frontend::SourceDocument document, Frontend::AST::Rule rule, Frontend::Location? following_rule) -> void

Parameters:



168
169
170
171
172
173
174
175
176
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 168

def collect_rule_references(path, document, rule, following_rule)
  definition = token_at(document, rule.loc, rule.lhs)
  rule_id = definition&.span&.start_byte || 0
  rule.alternatives.each_with_index do |alternative, index|
    alternative.items.each { |item| collect_item(path, document, item, rule, rule_id) }
    following = rule.alternatives[index + 1]&.loc || following_rule
    collect_alternative_precedence(path, document, alternative, following, rule, rule_id)
  end
end

#global_key(name) ⇒ Array[untyped]

RBS:

  • (String name) -> Array[untyped]

Parameters:

  • name (String)

Returns:

  • (Array[untyped])


243
244
245
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 243

def global_key(name)
  [:symbol, name]
end

#parameter_key(path, rule_id, name) ⇒ Array[untyped]

RBS:

  • (String path, Integer rule_id, String name) -> Array[untyped]

Parameters:

  • path (String)
  • rule_id (Integer)
  • name (String)

Returns:

  • (Array[untyped])


248
249
250
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 248

def parameter_key(path, rule_id, name)
  [:parameter, path, rule_id, name]
end

#parameter_signature(parameters) ⇒ String

RBS:

  • (Array[String] parameters) -> String

Parameters:

  • parameters (Array[String])

Returns:

  • (String)


253
254
255
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 253

def parameter_signature(parameters)
  parameters.empty? ? "" : "(#{parameters.join(', ')})"
end

#symbol_data(kind, name) ⇒ Hash[Symbol, untyped]

RBS:

  • (Symbol kind, String name) -> Hash[Symbol, untyped]

Parameters:

  • kind (Symbol)
  • name (String)

Returns:

  • (Hash[Symbol, untyped])


217
218
219
220
221
# File 'lib/ibex/lsp/symbol_index_builder.rb', line 217

def symbol_data(kind, name)
  data = kind == :rule ? @rule_data[name] : @terminal_data[name]
  empty = {} #: Hash[Symbol, untyped]
  data || empty
end