pyvbaanalysis 1.0.0__py3-none-any.whl

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.
Files changed (93) hide show
  1. pyvbaanalysis/__init__.py +59 -0
  2. pyvbaanalysis/__main__.py +8 -0
  3. pyvbaanalysis/call/__init__.py +23 -0
  4. pyvbaanalysis/call/call_context.py +296 -0
  5. pyvbaanalysis/cli.py +352 -0
  6. pyvbaanalysis/completion/__init__.py +49 -0
  7. pyvbaanalysis/completion/cursor_context.py +26 -0
  8. pyvbaanalysis/completion/event_handlers.py +82 -0
  9. pyvbaanalysis/completion/member_access.py +1097 -0
  10. pyvbaanalysis/completion/type_completion.py +228 -0
  11. pyvbaanalysis/conditional/__init__.py +41 -0
  12. pyvbaanalysis/conditional/conditional_compilation.py +531 -0
  13. pyvbaanalysis/constants/__init__.py +21 -0
  14. pyvbaanalysis/constants/integer_constant_expression.py +226 -0
  15. pyvbaanalysis/data/event_definitions.json +1 -0
  16. pyvbaanalysis/data/excel_host_model.json +1 -0
  17. pyvbaanalysis/data/manifest.json +257 -0
  18. pyvbaanalysis/data/rule_metadata.json +1287 -0
  19. pyvbaanalysis/data/vba_runtime_tables.json +1 -0
  20. pyvbaanalysis/diagnostics/__init__.py +59 -0
  21. pyvbaanalysis/diagnostics/analyze_module.py +177 -0
  22. pyvbaanalysis/diagnostics/argument_inference.py +706 -0
  23. pyvbaanalysis/diagnostics/call_extraction.py +380 -0
  24. pyvbaanalysis/diagnostics/callable_signatures.py +443 -0
  25. pyvbaanalysis/diagnostics/const_expr.py +134 -0
  26. pyvbaanalysis/diagnostics/context.py +130 -0
  27. pyvbaanalysis/diagnostics/dataflow.py +242 -0
  28. pyvbaanalysis/diagnostics/exprwalk.py +110 -0
  29. pyvbaanalysis/diagnostics/model.py +108 -0
  30. pyvbaanalysis/diagnostics/registry.py +263 -0
  31. pyvbaanalysis/diagnostics/rule_metadata.py +243 -0
  32. pyvbaanalysis/diagnostics/rules/__init__.py +2 -0
  33. pyvbaanalysis/diagnostics/rules/argument_shape.py +204 -0
  34. pyvbaanalysis/diagnostics/rules/argument_types.py +80 -0
  35. pyvbaanalysis/diagnostics/rules/arrays.py +1000 -0
  36. pyvbaanalysis/diagnostics/rules/assignments.py +650 -0
  37. pyvbaanalysis/diagnostics/rules/binary_operand_scalar.py +75 -0
  38. pyvbaanalysis/diagnostics/rules/call_arity.py +114 -0
  39. pyvbaanalysis/diagnostics/rules/control_flow.py +651 -0
  40. pyvbaanalysis/diagnostics/rules/declarations.py +1642 -0
  41. pyvbaanalysis/diagnostics/rules/duplicates.py +311 -0
  42. pyvbaanalysis/diagnostics/rules/expressions.py +679 -0
  43. pyvbaanalysis/diagnostics/rules/lexical.py +185 -0
  44. pyvbaanalysis/diagnostics/rules/module_kind.py +389 -0
  45. pyvbaanalysis/diagnostics/rules/numeric_literals.py +49 -0
  46. pyvbaanalysis/diagnostics/rules/object_state.py +327 -0
  47. pyvbaanalysis/diagnostics/rules/parameter_defaults.py +92 -0
  48. pyvbaanalysis/diagnostics/rules/runtime_values.py +382 -0
  49. pyvbaanalysis/diagnostics/rules/shared.py +441 -0
  50. pyvbaanalysis/diagnostics/rules/type_of_is.py +312 -0
  51. pyvbaanalysis/diagnostics/rules/undeclared.py +503 -0
  52. pyvbaanalysis/diagnostics/walker.py +347 -0
  53. pyvbaanalysis/evidence.py +111 -0
  54. pyvbaanalysis/flow/__init__.py +21 -0
  55. pyvbaanalysis/flow/procedure_labels.py +274 -0
  56. pyvbaanalysis/flow/procedure_unstructured.py +65 -0
  57. pyvbaanalysis/host/__init__.py +41 -0
  58. pyvbaanalysis/host/host_model.py +209 -0
  59. pyvbaanalysis/lexer/__init__.py +43 -0
  60. pyvbaanalysis/lexer/keyword_table.py +141 -0
  61. pyvbaanalysis/lexer/stripped_lines.py +41 -0
  62. pyvbaanalysis/lexer/token_helpers.py +115 -0
  63. pyvbaanalysis/lexer/token_kinds.py +113 -0
  64. pyvbaanalysis/lexer/tokenize.py +413 -0
  65. pyvbaanalysis/lexer/trivia.py +65 -0
  66. pyvbaanalysis/parser/__init__.py +22 -0
  67. pyvbaanalysis/parser/fixed_length_string.py +58 -0
  68. pyvbaanalysis/parser/nodes.py +721 -0
  69. pyvbaanalysis/parser/parse_expression.py +621 -0
  70. pyvbaanalysis/parser/parse_module.py +1472 -0
  71. pyvbaanalysis/parser/parser_state.py +110 -0
  72. pyvbaanalysis/parser/type_declaration_suffix.py +29 -0
  73. pyvbaanalysis/project.py +146 -0
  74. pyvbaanalysis/py.typed +0 -0
  75. pyvbaanalysis/reader/__init__.py +49 -0
  76. pyvbaanalysis/reader/loose_file.py +104 -0
  77. pyvbaanalysis/reader/vbe_module.py +137 -0
  78. pyvbaanalysis/reader/workbook.py +104 -0
  79. pyvbaanalysis/runtime/__init__.py +29 -0
  80. pyvbaanalysis/runtime/vba_runtime.py +314 -0
  81. pyvbaanalysis/symbols/__init__.py +60 -0
  82. pyvbaanalysis/symbols/build_module_symbols.py +379 -0
  83. pyvbaanalysis/symbols/name_resolution.py +252 -0
  84. pyvbaanalysis/symbols/project_index.py +942 -0
  85. pyvbaanalysis/symbols/symbol_model.py +371 -0
  86. pyvbaanalysis/types/__init__.py +17 -0
  87. pyvbaanalysis/types/type_inference.py +278 -0
  88. pyvbaanalysis/types/type_names.py +105 -0
  89. pyvbaanalysis-1.0.0.dist-info/METADATA +120 -0
  90. pyvbaanalysis-1.0.0.dist-info/RECORD +93 -0
  91. pyvbaanalysis-1.0.0.dist-info/WHEEL +4 -0
  92. pyvbaanalysis-1.0.0.dist-info/entry_points.txt +2 -0
  93. pyvbaanalysis-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,379 @@
1
+ """Builds the symbol view of a single module from its parser AST.
2
+
3
+ Ported from xlide_vscode/src/analyzer/symbols/buildModuleSymbols.ts. Pure
4
+ AST -> symbol projection. The XLIDE builder also attaches XML `'''` doc comments;
5
+ that is editor-only (agent.md Risk 7) and omitted here.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from dataclasses import dataclass
12
+
13
+ from ..conditional import (
14
+ ConditionalActivityTracker,
15
+ ConditionalCompilationEnvironment,
16
+ create_conditional_activity_tracker,
17
+ )
18
+ from ..parser.nodes import (
19
+ AttributeNode,
20
+ BodyNode,
21
+ DeclareNode,
22
+ EnumNode,
23
+ EventNode,
24
+ ModuleNode,
25
+ ParameterNode,
26
+ ProcedureNode,
27
+ ProcKind,
28
+ Span,
29
+ TypeNode,
30
+ VariableGroupNode,
31
+ )
32
+ from ..parser.parse_module import parse_module
33
+ from .symbol_model import (
34
+ ModuleSymbolKind,
35
+ ModuleSymbols,
36
+ SymbolVisibility,
37
+ VbaSymbol,
38
+ VbaSymbolAttribute,
39
+ VbaSymbolKind,
40
+ )
41
+
42
+ _RETURN_ARRAY_RE = re.compile(r"\(\s*\)\s*$")
43
+
44
+ _VISIBILITY_BY_WORD: dict[str, SymbolVisibility] = {
45
+ "public": SymbolVisibility.PUBLIC,
46
+ "private": SymbolVisibility.PRIVATE,
47
+ "friend": SymbolVisibility.FRIEND,
48
+ "global": SymbolVisibility.GLOBAL,
49
+ "dim": SymbolVisibility.DIM,
50
+ "static": SymbolVisibility.STATIC,
51
+ }
52
+
53
+ _PROC_SYMBOL_KIND: dict[ProcKind, VbaSymbolKind] = {
54
+ ProcKind.SUB: VbaSymbolKind.SUB,
55
+ ProcKind.FUNCTION: VbaSymbolKind.FUNCTION,
56
+ ProcKind.PROPERTY_GET: VbaSymbolKind.PROPERTY_GET,
57
+ ProcKind.PROPERTY_LET: VbaSymbolKind.PROPERTY_LET,
58
+ ProcKind.PROPERTY_SET: VbaSymbolKind.PROPERTY_SET,
59
+ }
60
+
61
+
62
+ @dataclass(slots=True)
63
+ class BuildModuleSymbolsOptions:
64
+ """Inputs to symbol building: conditional-compilation env and a parsed AST."""
65
+
66
+ conditional_compilation: ConditionalCompilationEnvironment | None = None
67
+ parsed_module: ModuleNode | None = None
68
+
69
+
70
+ def _is_inactive(activity: ConditionalActivityTracker | None, span: Span) -> bool:
71
+ return activity is not None and activity.is_inactive(span)
72
+
73
+
74
+ def _to_visibility(word: str | None) -> SymbolVisibility | None:
75
+ return _VISIBILITY_BY_WORD.get((word or "").lower())
76
+
77
+
78
+ def _proc_visibility(modifiers: list[str]) -> SymbolVisibility | None:
79
+ for m in modifiers:
80
+ v = _to_visibility(m)
81
+ if v is not None and v is not SymbolVisibility.DIM and v is not SymbolVisibility.STATIC:
82
+ return v
83
+ return None
84
+
85
+
86
+ def _symbol_attribute(node: AttributeNode) -> VbaSymbolAttribute:
87
+ dot = node.name.find(".")
88
+ if dot > 0 and dot + 1 < len(node.name):
89
+ return VbaSymbolAttribute(
90
+ name=node.name[dot + 1 :],
91
+ target_name=node.name[:dot],
92
+ value_raw=node.value_raw,
93
+ name_span=node.name_span,
94
+ full_span=node.span,
95
+ )
96
+ return VbaSymbolAttribute(
97
+ name=node.name, value_raw=node.value_raw, name_span=node.name_span, full_span=node.span
98
+ )
99
+
100
+
101
+ def _attach_member_attributes(
102
+ symbols: list[VbaSymbol], attributes: list[VbaSymbolAttribute]
103
+ ) -> None:
104
+ for attr in attributes:
105
+ if not attr.target_name:
106
+ continue
107
+ lower_target = attr.target_name.lower()
108
+ for symbol in symbols:
109
+ if symbol.name.lower() != lower_target:
110
+ continue
111
+ symbol.attributes = [*(symbol.attributes or []), attr]
112
+
113
+
114
+ def _collect_locals(
115
+ body: list[BodyNode],
116
+ module_name: str,
117
+ container_name: str,
118
+ out: list[VbaSymbol],
119
+ activity: ConditionalActivityTracker | None,
120
+ ) -> None:
121
+ for node in body:
122
+ if _is_inactive(activity, node.span):
123
+ continue
124
+ if isinstance(node, VariableGroupNode):
125
+ for decl in node.declarations:
126
+ out.append(
127
+ VbaSymbol(
128
+ name=decl.name,
129
+ kind=VbaSymbolKind.CONSTANT if node.is_const else VbaSymbolKind.LOCAL_VARIABLE,
130
+ name_span=decl.name_span or decl.span,
131
+ full_span=decl.span,
132
+ module_name=module_name,
133
+ container_name=container_name,
134
+ visibility=_to_visibility(node.modifier),
135
+ as_type=decl.as_type,
136
+ fixed_length=decl.fixed_length,
137
+ default_raw=decl.default_raw,
138
+ is_array=decl.is_array,
139
+ array_bounds=decl.array_bounds,
140
+ )
141
+ )
142
+ else:
143
+ child = getattr(node, "body", None)
144
+ if isinstance(child, list):
145
+ _collect_locals(child, module_name, container_name, out, activity)
146
+
147
+
148
+ def _build_parameter_symbol(
149
+ param: ParameterNode, module_name: str, container_name: str
150
+ ) -> VbaSymbol:
151
+ return VbaSymbol(
152
+ name=param.name,
153
+ kind=VbaSymbolKind.PARAMETER,
154
+ name_span=param.name_span or param.span,
155
+ full_span=param.span,
156
+ module_name=module_name,
157
+ container_name=container_name,
158
+ as_type=param.as_type,
159
+ optional=param.optional,
160
+ param_array=param.param_array,
161
+ by_val=param.by_val,
162
+ by_ref=param.by_ref,
163
+ is_array=param.is_array,
164
+ default_raw=param.default_raw,
165
+ )
166
+
167
+
168
+ def _procedure_return_is_array(return_type: str | None) -> bool:
169
+ return _RETURN_ARRAY_RE.search(return_type or "") is not None
170
+
171
+
172
+ def _build_procedure(
173
+ proc: ProcedureNode,
174
+ module_name: str,
175
+ flat: list[VbaSymbol],
176
+ activity: ConditionalActivityTracker | None,
177
+ ) -> VbaSymbol:
178
+ children: list[VbaSymbol] = []
179
+ symbol = VbaSymbol(
180
+ name=proc.name,
181
+ kind=_PROC_SYMBOL_KIND[proc.proc_kind],
182
+ name_span=proc.name_span or proc.span,
183
+ full_span=proc.span,
184
+ module_name=module_name,
185
+ visibility=_proc_visibility(proc.modifiers),
186
+ as_type=proc.return_type,
187
+ is_array=_procedure_return_is_array(proc.return_type),
188
+ attributes=[_symbol_attribute(a) for a in proc.attributes] if proc.attributes else None,
189
+ children=children,
190
+ )
191
+ for param in proc.params:
192
+ param_symbol = _build_parameter_symbol(param, module_name, proc.name)
193
+ children.append(param_symbol)
194
+ flat.append(param_symbol)
195
+ locals_: list[VbaSymbol] = []
196
+ _collect_locals(proc.body, module_name, proc.name, locals_, activity)
197
+ for local in locals_:
198
+ children.append(local)
199
+ flat.append(local)
200
+ return symbol
201
+
202
+
203
+ def _build_declare(declare: DeclareNode, module_name: str, flat: list[VbaSymbol]) -> VbaSymbol:
204
+ children: list[VbaSymbol] = []
205
+ symbol = VbaSymbol(
206
+ name=declare.name,
207
+ kind=VbaSymbolKind.DECLARE,
208
+ name_span=declare.name_span or declare.span,
209
+ full_span=declare.span,
210
+ module_name=module_name,
211
+ visibility=_to_visibility(declare.visibility),
212
+ as_type=declare.return_type,
213
+ declare_kind="Function" if declare.is_function else "Sub",
214
+ ptr_safe=declare.ptr_safe,
215
+ lib_name=declare.lib_name,
216
+ alias_name=declare.alias_name,
217
+ children=children,
218
+ )
219
+ for param in declare.params:
220
+ param_symbol = _build_parameter_symbol(param, module_name, declare.name)
221
+ children.append(param_symbol)
222
+ flat.append(param_symbol)
223
+ return symbol
224
+
225
+
226
+ def _build_event(event: EventNode, module_name: str, flat: list[VbaSymbol]) -> VbaSymbol:
227
+ children: list[VbaSymbol] = []
228
+ symbol = VbaSymbol(
229
+ name=event.name,
230
+ kind=VbaSymbolKind.EVENT,
231
+ name_span=event.name_span or event.span,
232
+ full_span=event.span,
233
+ module_name=module_name,
234
+ visibility=_to_visibility(event.visibility),
235
+ children=children,
236
+ )
237
+ for param in event.params:
238
+ param_symbol = _build_parameter_symbol(param, module_name, event.name)
239
+ children.append(param_symbol)
240
+ flat.append(param_symbol)
241
+ return symbol
242
+
243
+
244
+ def _build_type(node: TypeNode, module_name: str, flat: list[VbaSymbol]) -> VbaSymbol:
245
+ children: list[VbaSymbol] = []
246
+ symbol = VbaSymbol(
247
+ name=node.name,
248
+ kind=VbaSymbolKind.TYPE,
249
+ name_span=node.name_span or node.span,
250
+ full_span=node.span,
251
+ module_name=module_name,
252
+ visibility=_to_visibility(node.visibility),
253
+ children=children,
254
+ )
255
+ for field_node in node.fields:
256
+ field_symbol = VbaSymbol(
257
+ name=field_node.name,
258
+ kind=VbaSymbolKind.TYPE_FIELD,
259
+ name_span=field_node.name_span or field_node.span,
260
+ full_span=field_node.span,
261
+ module_name=module_name,
262
+ container_name=node.name,
263
+ as_type=field_node.as_type,
264
+ fixed_length=field_node.fixed_length,
265
+ )
266
+ children.append(field_symbol)
267
+ flat.append(field_symbol)
268
+ return symbol
269
+
270
+
271
+ def _build_enum(node: EnumNode, module_name: str, flat: list[VbaSymbol]) -> VbaSymbol:
272
+ children: list[VbaSymbol] = []
273
+ symbol = VbaSymbol(
274
+ name=node.name,
275
+ kind=VbaSymbolKind.ENUM,
276
+ name_span=node.name_span or node.span,
277
+ full_span=node.span,
278
+ module_name=module_name,
279
+ visibility=_to_visibility(node.visibility),
280
+ children=children,
281
+ )
282
+ for member in node.members:
283
+ member_symbol = VbaSymbol(
284
+ name=member.name,
285
+ kind=VbaSymbolKind.ENUM_MEMBER,
286
+ name_span=member.name_span or member.span,
287
+ full_span=member.span,
288
+ module_name=module_name,
289
+ container_name=node.name,
290
+ default_raw=member.value_raw,
291
+ )
292
+ children.append(member_symbol)
293
+ flat.append(member_symbol)
294
+ return symbol
295
+
296
+
297
+ def _build_module_variables(
298
+ group: VariableGroupNode,
299
+ module_name: str,
300
+ root_children: list[VbaSymbol],
301
+ flat: list[VbaSymbol],
302
+ ) -> None:
303
+ for decl in group.declarations:
304
+ symbol = VbaSymbol(
305
+ name=decl.name,
306
+ kind=VbaSymbolKind.CONSTANT if group.is_const else VbaSymbolKind.MODULE_VARIABLE,
307
+ name_span=decl.name_span or decl.span,
308
+ full_span=decl.span,
309
+ module_name=module_name,
310
+ visibility=_to_visibility(group.modifier),
311
+ as_type=decl.as_type,
312
+ fixed_length=decl.fixed_length,
313
+ default_raw=decl.default_raw,
314
+ is_array=decl.is_array,
315
+ array_bounds=decl.array_bounds,
316
+ )
317
+ root_children.append(symbol)
318
+ flat.append(symbol)
319
+
320
+
321
+ def build_module_symbols(
322
+ module_name: str,
323
+ module_kind: ModuleSymbolKind,
324
+ source: str,
325
+ options: BuildModuleSymbolsOptions | None = None,
326
+ ) -> ModuleSymbols:
327
+ """Build the ModuleSymbols view of a module from its source text."""
328
+ options = options or BuildModuleSymbolsOptions()
329
+ module = options.parsed_module if options.parsed_module is not None else parse_module(source)
330
+ activity = create_conditional_activity_tracker(module, options.conditional_compilation)
331
+ root_children: list[VbaSymbol] = []
332
+ flat: list[VbaSymbol] = []
333
+ module_attributes: list[VbaSymbolAttribute] = []
334
+ member_attributes: list[VbaSymbolAttribute] = []
335
+
336
+ for member in module.members:
337
+ if _is_inactive(activity, member.span):
338
+ continue
339
+ if isinstance(member, AttributeNode):
340
+ attr = _symbol_attribute(member)
341
+ if attr.target_name:
342
+ member_attributes.append(attr)
343
+ else:
344
+ module_attributes.append(attr)
345
+ elif isinstance(member, ProcedureNode):
346
+ proc = _build_procedure(member, module_name, flat, activity)
347
+ root_children.append(proc)
348
+ flat.append(proc)
349
+ elif isinstance(member, TypeNode):
350
+ type_symbol = _build_type(member, module_name, flat)
351
+ root_children.append(type_symbol)
352
+ flat.append(type_symbol)
353
+ elif isinstance(member, EnumNode):
354
+ enum_symbol = _build_enum(member, module_name, flat)
355
+ root_children.append(enum_symbol)
356
+ flat.append(enum_symbol)
357
+ elif isinstance(member, VariableGroupNode):
358
+ _build_module_variables(member, module_name, root_children, flat)
359
+ elif isinstance(member, DeclareNode):
360
+ declare_symbol = _build_declare(member, module_name, flat)
361
+ root_children.append(declare_symbol)
362
+ flat.append(declare_symbol)
363
+ elif isinstance(member, EventNode):
364
+ event_symbol = _build_event(member, module_name, flat)
365
+ root_children.append(event_symbol)
366
+ flat.append(event_symbol)
367
+
368
+ _attach_member_attributes(root_children, member_attributes)
369
+
370
+ root = VbaSymbol(
371
+ name=module_name,
372
+ kind=VbaSymbolKind.MODULE,
373
+ name_span=Span(module.span.start, module.span.start),
374
+ full_span=module.span,
375
+ module_name=module_name,
376
+ children=root_children,
377
+ attributes=module_attributes,
378
+ )
379
+ return ModuleSymbols(module_name=module_name, module_kind=module_kind, root=root, all=flat)
@@ -0,0 +1,252 @@
1
+ """Context-aware source resolver for bare identifiers (MS-VBAL 5.3).
2
+
3
+ Ported from xlide_vscode/src/analyzer/symbols/nameResolution.ts. Ordered
4
+ local -> module -> project, with a tri-state ambiguous/unresolved outcome and
5
+ property-accessor-family collapsing. The XLIDE synthetic return-variable symbol
6
+ carries a `doc`; that is editor-only and omitted here (agent.md Risk 7).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import enum
12
+ from collections.abc import Sequence
13
+ from dataclasses import dataclass, field
14
+
15
+ from .symbol_model import ModuleSymbols, VbaSymbol, VbaSymbolKind
16
+
17
+
18
+ class BareIdentifierContext(str, enum.Enum):
19
+ EXPRESSION = "expression"
20
+ CALL = "call"
21
+ ASSIGNMENT_TARGET = "assignmentTarget"
22
+ MEMBER_RECEIVER = "memberReceiver"
23
+ TYPE_NAME = "typeName"
24
+ NEW_EXPRESSION = "newExpression"
25
+
26
+
27
+ class BareIdentifierResolutionScope(str, enum.Enum):
28
+ LOCAL = "local"
29
+ MODULE = "module"
30
+ PROJECT = "project"
31
+ AMBIGUOUS = "ambiguous"
32
+ UNRESOLVED = "unresolved"
33
+
34
+
35
+ @dataclass(slots=True)
36
+ class BareIdentifierResolution:
37
+ name: str
38
+ lower_name: str
39
+ context: BareIdentifierContext
40
+ scope: BareIdentifierResolutionScope
41
+ definitions: list[VbaSymbol]
42
+ reason: str
43
+ tier: BareIdentifierResolutionScope | None = None
44
+
45
+
46
+ @dataclass(slots=True)
47
+ class BareIdentifierResolutionInput:
48
+ current_module: ModuleSymbols
49
+ name: str
50
+ context: BareIdentifierContext
51
+ enclosing_procedure: VbaSymbol | None = None
52
+ offset: int | None = None
53
+ project_visible_symbols: list[VbaSymbol] = field(default_factory=list)
54
+
55
+
56
+ def resolve_bare_identifier_binding(
57
+ input: BareIdentifierResolutionInput,
58
+ ) -> BareIdentifierResolution:
59
+ """Resolve a bare identifier to its source-backed definition(s), searching local then module then project scope and reporting ambiguous or unresolved when no single binding wins."""
60
+ lower_name = input.name.lower()
61
+ local = local_identifier_matches(input.enclosing_procedure, lower_name, input.context, input.offset)
62
+ if len(local) > 0:
63
+ return _resolution(input, lower_name, _ambiguous_scope(local, BareIdentifierResolutionScope.LOCAL), local)
64
+
65
+ module = module_level_identifier_matches(input.current_module, lower_name, input.context)
66
+ if len(module) > 0:
67
+ return _resolution(input, lower_name, _ambiguous_scope(module, BareIdentifierResolutionScope.MODULE), module)
68
+
69
+ current_lower = input.current_module.module_name.lower()
70
+ project = [
71
+ symbol
72
+ for symbol in input.project_visible_symbols
73
+ if symbol.module_name.lower() != current_lower
74
+ and symbol.name.lower() == lower_name
75
+ and _symbol_allowed_in_context(symbol, input.context)
76
+ ]
77
+ if len(project) > 0:
78
+ return _resolution(input, lower_name, _ambiguous_scope(project, BareIdentifierResolutionScope.PROJECT), project)
79
+
80
+ return BareIdentifierResolution(
81
+ name=input.name,
82
+ lower_name=lower_name,
83
+ context=input.context,
84
+ scope=BareIdentifierResolutionScope.UNRESOLVED,
85
+ definitions=[],
86
+ reason=f"No source-backed {input.context.value} binding found for '{input.name}'.",
87
+ )
88
+
89
+
90
+ def local_identifier_matches(
91
+ procedure: VbaSymbol | None,
92
+ lower_name: str,
93
+ context: BareIdentifierContext,
94
+ offset: int | None = None,
95
+ ) -> list[VbaSymbol]:
96
+ if (
97
+ procedure is None
98
+ or context is BareIdentifierContext.TYPE_NAME
99
+ or context is BareIdentifierContext.NEW_EXPRESSION
100
+ ):
101
+ return []
102
+ out: list[VbaSymbol] = []
103
+ return_variable = _procedure_return_variable(procedure, context, offset)
104
+ if return_variable is not None and return_variable.name.lower() == lower_name:
105
+ out.append(return_variable)
106
+ for symbol in procedure.children or []:
107
+ if _is_local_identifier_symbol(symbol) and symbol.name.lower() == lower_name:
108
+ out.append(symbol)
109
+ return out
110
+
111
+
112
+ def module_level_identifier_matches(
113
+ mod: ModuleSymbols, lower_name: str, context: BareIdentifierContext
114
+ ) -> list[VbaSymbol]:
115
+ out: list[VbaSymbol] = []
116
+ for symbol in mod.root.children or []:
117
+ if symbol.name.lower() == lower_name and _symbol_allowed_in_context(symbol, context):
118
+ out.append(symbol)
119
+ if symbol.kind is VbaSymbolKind.ENUM:
120
+ for member in symbol.children or []:
121
+ if member.name.lower() == lower_name and _symbol_allowed_in_context(member, context):
122
+ out.append(member)
123
+ return out
124
+
125
+
126
+ def source_identifier_names(
127
+ current_module: ModuleSymbols,
128
+ enclosing_procedure: VbaSymbol | None = None,
129
+ project_visible_symbols: Sequence[VbaSymbol] = (),
130
+ ) -> set[str]:
131
+ """Collect the lowercased names of every source-backed identifier visible from a context: locals, module members (and enum members), and project-visible symbols."""
132
+ out: set[str] = set()
133
+ return_variable = _procedure_return_variable(enclosing_procedure, BareIdentifierContext.EXPRESSION)
134
+ if return_variable is not None:
135
+ out.add(return_variable.name.lower())
136
+ for symbol in (enclosing_procedure.children if enclosing_procedure is not None else None) or []:
137
+ if _is_local_identifier_symbol(symbol):
138
+ out.add(symbol.name.lower())
139
+ for symbol in current_module.root.children or []:
140
+ out.add(symbol.name.lower())
141
+ if symbol.kind is VbaSymbolKind.ENUM:
142
+ for member in symbol.children or []:
143
+ out.add(member.name.lower())
144
+ for symbol in project_visible_symbols:
145
+ out.add(symbol.name.lower())
146
+ return out
147
+
148
+
149
+ def _procedure_return_variable(
150
+ procedure: VbaSymbol | None, context: BareIdentifierContext, offset: int | None = None
151
+ ) -> VbaSymbol | None:
152
+ if (
153
+ procedure is None
154
+ or not _procedure_returns_through_name(procedure)
155
+ or context is BareIdentifierContext.CALL
156
+ ):
157
+ return None
158
+ if offset is not None and offset <= procedure.name_span.end:
159
+ return None
160
+ return VbaSymbol(
161
+ name=procedure.name,
162
+ kind=VbaSymbolKind.LOCAL_VARIABLE,
163
+ name_span=procedure.name_span,
164
+ full_span=procedure.name_span,
165
+ module_name=procedure.module_name,
166
+ container_name=procedure.name,
167
+ as_type=procedure.as_type,
168
+ is_array=procedure.is_array,
169
+ )
170
+
171
+
172
+ def _procedure_returns_through_name(procedure: VbaSymbol) -> bool:
173
+ return procedure.kind is VbaSymbolKind.FUNCTION or procedure.kind is VbaSymbolKind.PROPERTY_GET
174
+
175
+
176
+ def _is_local_identifier_symbol(symbol: VbaSymbol) -> bool:
177
+ return (
178
+ symbol.kind is VbaSymbolKind.PARAMETER
179
+ or symbol.kind is VbaSymbolKind.LOCAL_VARIABLE
180
+ or (symbol.kind is VbaSymbolKind.CONSTANT and bool(symbol.container_name))
181
+ )
182
+
183
+
184
+ def _symbol_allowed_in_context(symbol: VbaSymbol, context: BareIdentifierContext) -> bool:
185
+ if context is BareIdentifierContext.TYPE_NAME or context is BareIdentifierContext.NEW_EXPRESSION:
186
+ return symbol.kind is VbaSymbolKind.TYPE or symbol.kind is VbaSymbolKind.ENUM
187
+ return True
188
+
189
+
190
+ def _ambiguous_scope(
191
+ definitions: Sequence[VbaSymbol], fallback: BareIdentifierResolutionScope
192
+ ) -> BareIdentifierResolutionScope:
193
+ if len(definitions) <= 1 or _is_property_accessor_family(definitions):
194
+ return fallback
195
+ return BareIdentifierResolutionScope.AMBIGUOUS
196
+
197
+
198
+ def _is_property_accessor_family(definitions: Sequence[VbaSymbol]) -> bool:
199
+ if len(definitions) <= 1:
200
+ return False
201
+ first = definitions[0]
202
+ return all(
203
+ symbol.module_name.lower() == first.module_name.lower()
204
+ and symbol.name.lower() == first.name.lower()
205
+ and symbol.kind
206
+ in (VbaSymbolKind.PROPERTY_GET, VbaSymbolKind.PROPERTY_LET, VbaSymbolKind.PROPERTY_SET)
207
+ for symbol in definitions
208
+ )
209
+
210
+
211
+ def _resolution(
212
+ input: BareIdentifierResolutionInput,
213
+ lower_name: str,
214
+ scope: BareIdentifierResolutionScope,
215
+ definitions: list[VbaSymbol],
216
+ ) -> BareIdentifierResolution:
217
+ if scope is BareIdentifierResolutionScope.AMBIGUOUS:
218
+ tier = _definition_tier(input.current_module, definitions)
219
+ elif scope is BareIdentifierResolutionScope.UNRESOLVED:
220
+ tier = None
221
+ else:
222
+ tier = scope
223
+ owner = definitions[0].module_name if definitions else input.current_module.module_name
224
+ label = (
225
+ "ambiguous source-backed"
226
+ if scope is BareIdentifierResolutionScope.AMBIGUOUS
227
+ else f"{scope.value} source-backed"
228
+ )
229
+ return BareIdentifierResolution(
230
+ name=input.name,
231
+ lower_name=lower_name,
232
+ context=input.context,
233
+ scope=scope,
234
+ tier=tier,
235
+ definitions=definitions,
236
+ reason=f"{label} {input.context.value} binding for '{input.name}' in {owner}.",
237
+ )
238
+
239
+
240
+ def _definition_tier(
241
+ current_module: ModuleSymbols, definitions: Sequence[VbaSymbol]
242
+ ) -> BareIdentifierResolutionScope | None:
243
+ if not definitions:
244
+ return None
245
+ first = definitions[0]
246
+ if _is_local_identifier_symbol(first):
247
+ return BareIdentifierResolutionScope.LOCAL
248
+ return (
249
+ BareIdentifierResolutionScope.MODULE
250
+ if first.module_name.lower() == current_module.module_name.lower()
251
+ else BareIdentifierResolutionScope.PROJECT
252
+ )