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.
- pyvbaanalysis/__init__.py +59 -0
- pyvbaanalysis/__main__.py +8 -0
- pyvbaanalysis/call/__init__.py +23 -0
- pyvbaanalysis/call/call_context.py +296 -0
- pyvbaanalysis/cli.py +352 -0
- pyvbaanalysis/completion/__init__.py +49 -0
- pyvbaanalysis/completion/cursor_context.py +26 -0
- pyvbaanalysis/completion/event_handlers.py +82 -0
- pyvbaanalysis/completion/member_access.py +1097 -0
- pyvbaanalysis/completion/type_completion.py +228 -0
- pyvbaanalysis/conditional/__init__.py +41 -0
- pyvbaanalysis/conditional/conditional_compilation.py +531 -0
- pyvbaanalysis/constants/__init__.py +21 -0
- pyvbaanalysis/constants/integer_constant_expression.py +226 -0
- pyvbaanalysis/data/event_definitions.json +1 -0
- pyvbaanalysis/data/excel_host_model.json +1 -0
- pyvbaanalysis/data/manifest.json +257 -0
- pyvbaanalysis/data/rule_metadata.json +1287 -0
- pyvbaanalysis/data/vba_runtime_tables.json +1 -0
- pyvbaanalysis/diagnostics/__init__.py +59 -0
- pyvbaanalysis/diagnostics/analyze_module.py +177 -0
- pyvbaanalysis/diagnostics/argument_inference.py +706 -0
- pyvbaanalysis/diagnostics/call_extraction.py +380 -0
- pyvbaanalysis/diagnostics/callable_signatures.py +443 -0
- pyvbaanalysis/diagnostics/const_expr.py +134 -0
- pyvbaanalysis/diagnostics/context.py +130 -0
- pyvbaanalysis/diagnostics/dataflow.py +242 -0
- pyvbaanalysis/diagnostics/exprwalk.py +110 -0
- pyvbaanalysis/diagnostics/model.py +108 -0
- pyvbaanalysis/diagnostics/registry.py +263 -0
- pyvbaanalysis/diagnostics/rule_metadata.py +243 -0
- pyvbaanalysis/diagnostics/rules/__init__.py +2 -0
- pyvbaanalysis/diagnostics/rules/argument_shape.py +204 -0
- pyvbaanalysis/diagnostics/rules/argument_types.py +80 -0
- pyvbaanalysis/diagnostics/rules/arrays.py +1000 -0
- pyvbaanalysis/diagnostics/rules/assignments.py +650 -0
- pyvbaanalysis/diagnostics/rules/binary_operand_scalar.py +75 -0
- pyvbaanalysis/diagnostics/rules/call_arity.py +114 -0
- pyvbaanalysis/diagnostics/rules/control_flow.py +651 -0
- pyvbaanalysis/diagnostics/rules/declarations.py +1642 -0
- pyvbaanalysis/diagnostics/rules/duplicates.py +311 -0
- pyvbaanalysis/diagnostics/rules/expressions.py +679 -0
- pyvbaanalysis/diagnostics/rules/lexical.py +185 -0
- pyvbaanalysis/diagnostics/rules/module_kind.py +389 -0
- pyvbaanalysis/diagnostics/rules/numeric_literals.py +49 -0
- pyvbaanalysis/diagnostics/rules/object_state.py +327 -0
- pyvbaanalysis/diagnostics/rules/parameter_defaults.py +92 -0
- pyvbaanalysis/diagnostics/rules/runtime_values.py +382 -0
- pyvbaanalysis/diagnostics/rules/shared.py +441 -0
- pyvbaanalysis/diagnostics/rules/type_of_is.py +312 -0
- pyvbaanalysis/diagnostics/rules/undeclared.py +503 -0
- pyvbaanalysis/diagnostics/walker.py +347 -0
- pyvbaanalysis/evidence.py +111 -0
- pyvbaanalysis/flow/__init__.py +21 -0
- pyvbaanalysis/flow/procedure_labels.py +274 -0
- pyvbaanalysis/flow/procedure_unstructured.py +65 -0
- pyvbaanalysis/host/__init__.py +41 -0
- pyvbaanalysis/host/host_model.py +209 -0
- pyvbaanalysis/lexer/__init__.py +43 -0
- pyvbaanalysis/lexer/keyword_table.py +141 -0
- pyvbaanalysis/lexer/stripped_lines.py +41 -0
- pyvbaanalysis/lexer/token_helpers.py +115 -0
- pyvbaanalysis/lexer/token_kinds.py +113 -0
- pyvbaanalysis/lexer/tokenize.py +413 -0
- pyvbaanalysis/lexer/trivia.py +65 -0
- pyvbaanalysis/parser/__init__.py +22 -0
- pyvbaanalysis/parser/fixed_length_string.py +58 -0
- pyvbaanalysis/parser/nodes.py +721 -0
- pyvbaanalysis/parser/parse_expression.py +621 -0
- pyvbaanalysis/parser/parse_module.py +1472 -0
- pyvbaanalysis/parser/parser_state.py +110 -0
- pyvbaanalysis/parser/type_declaration_suffix.py +29 -0
- pyvbaanalysis/project.py +146 -0
- pyvbaanalysis/py.typed +0 -0
- pyvbaanalysis/reader/__init__.py +49 -0
- pyvbaanalysis/reader/loose_file.py +104 -0
- pyvbaanalysis/reader/vbe_module.py +137 -0
- pyvbaanalysis/reader/workbook.py +104 -0
- pyvbaanalysis/runtime/__init__.py +29 -0
- pyvbaanalysis/runtime/vba_runtime.py +314 -0
- pyvbaanalysis/symbols/__init__.py +60 -0
- pyvbaanalysis/symbols/build_module_symbols.py +379 -0
- pyvbaanalysis/symbols/name_resolution.py +252 -0
- pyvbaanalysis/symbols/project_index.py +942 -0
- pyvbaanalysis/symbols/symbol_model.py +371 -0
- pyvbaanalysis/types/__init__.py +17 -0
- pyvbaanalysis/types/type_inference.py +278 -0
- pyvbaanalysis/types/type_names.py +105 -0
- pyvbaanalysis-1.0.0.dist-info/METADATA +120 -0
- pyvbaanalysis-1.0.0.dist-info/RECORD +93 -0
- pyvbaanalysis-1.0.0.dist-info/WHEEL +4 -0
- pyvbaanalysis-1.0.0.dist-info/entry_points.txt +2 -0
- 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
|
+
)
|