pyvbaanalysis 1.4.2__tar.gz → 2.1.0__tar.gz
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-1.4.2 → pyvbaanalysis-2.1.0}/CHANGELOG.md +149 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/PKG-INFO +19 -11
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/README.md +16 -8
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/docs/api-reference.md +6 -3
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/docs/diagnostics-catalogue.md +3 -2
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/docs/usage.md +43 -10
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyproject.toml +1 -1
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/__init__.py +13 -2
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/cli.py +6 -4
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/conditional/conditional_compilation.py +80 -3
- pyvbaanalysis-2.1.0/pyvbaanalysis/data/access_host_model.json +1 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/data/diagnostic_influence_audit.json +14 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/data/event_definitions.json +1 -1
- pyvbaanalysis-2.1.0/pyvbaanalysis/data/excel_host_model.json +1 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/data/manifest.json +12 -10
- pyvbaanalysis-2.1.0/pyvbaanalysis/data/powerpoint_host_model.json +1 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/data/rule_metadata.json +11 -0
- pyvbaanalysis-2.1.0/pyvbaanalysis/data/vba_runtime_tables.json +1 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/data/vbe_oracle_cases.json +64 -0
- pyvbaanalysis-2.1.0/pyvbaanalysis/data/word_host_model.json +1 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/analyze_module.py +39 -10
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/context.py +9 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/registry.py +30 -8
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/assignments.py +91 -15
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/control_flow.py +54 -20
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/declarations.py +28 -6
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/duplicates.py +190 -58
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/late_binding.py +5 -3
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/module_kind.py +10 -3
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/undeclared.py +8 -5
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/walker.py +18 -1
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/host/__init__.py +17 -1
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/host/host_model.py +72 -29
- pyvbaanalysis-2.1.0/pyvbaanalysis/host/host_registry.py +115 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/parser/nodes.py +6 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/parser/parse_module.py +25 -1
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/project.py +11 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/reader/__init__.py +14 -1
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/reader/vbe_module.py +27 -7
- pyvbaanalysis-2.1.0/pyvbaanalysis/reader/workbook.py +230 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/symbols/project_index.py +11 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_cli.py +1 -1
- pyvbaanalysis-2.1.0/tests/test_conditional_arms.py +212 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_evidence.py +2 -2
- pyvbaanalysis-2.1.0/tests/test_host_seam.py +469 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rule_metadata.py +5 -5
- pyvbaanalysis-2.1.0/tests/test_upstream_sync_6_1_2.py +169 -0
- pyvbaanalysis-2.1.0/tools/extract_host_model.mjs +31 -0
- pyvbaanalysis-1.4.2/pyvbaanalysis/data/excel_host_model.json +0 -1
- pyvbaanalysis-1.4.2/pyvbaanalysis/data/vba_runtime_tables.json +0 -1
- pyvbaanalysis-1.4.2/pyvbaanalysis/reader/workbook.py +0 -106
- pyvbaanalysis-1.4.2/tools/extract_host_model.mjs +0 -12
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/.gitattributes +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/.github/dependabot.yml +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/.github/workflows/ci.yml +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/.github/workflows/publish.yml +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/.gitignore +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/CONTRIBUTING.md +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/LICENSE +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/__main__.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/call/__init__.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/call/call_context.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/completion/__init__.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/completion/cursor_context.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/completion/event_handlers.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/completion/member_access.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/completion/type_completion.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/conditional/__init__.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/constants/__init__.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/constants/integer_constant_expression.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/__init__.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/argument_inference.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/call_extraction.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/callable_signatures.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/const_expr.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/dataflow.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/exprwalk.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/inline_suppression.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/model.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rule_metadata.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/__init__.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/argument_shape.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/argument_types.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/arrays.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/binary_operand_scalar.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/call_arity.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/expressions.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/lexical.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/numeric_literals.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/object_state.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/parameter_defaults.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/runtime_values.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/shared.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/diagnostics/rules/type_of_is.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/evidence.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/flow/__init__.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/flow/procedure_labels.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/flow/procedure_unstructured.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/identity_cache.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/lexer/__init__.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/lexer/keyword_table.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/lexer/stripped_lines.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/lexer/token_helpers.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/lexer/token_kinds.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/lexer/tokenize.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/lexer/trivia.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/parser/__init__.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/parser/fixed_length_string.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/parser/parse_expression.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/parser/parser_state.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/parser/type_declaration_suffix.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/py.typed +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/reader/loose_file.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/runtime/__init__.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/runtime/vba_runtime.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/symbols/__init__.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/symbols/build_module_symbols.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/symbols/name_resolution.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/symbols/symbol_model.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/types/__init__.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/types/type_inference.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/types/type_names.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/conftest.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/oracle_support.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_conditional_compilation.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_dataflow.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_diagnostics_engine.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_host_model.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_inline_suppression.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_integer_constant_expression.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_language_matrix.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_lexer.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_member_access_chains.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_parser.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_project.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_project_index.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_public_api.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_reader.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_real_world_false_positives.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_registry_parity.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_ambiguous_enum_member.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_argument_shape.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_argument_types.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_arrays.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_assignments.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_assignments_types.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_binary_operand_scalar.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_call_arity.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_control_flow.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_declarations.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_division_by_zero.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_duplicates.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_event_handler_module_scope.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_expression_calls.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_expressions.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_invalid_as_type_names.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_is_operator_non_object.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_late_binding.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_lexical.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_member_not_found.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_module_kind.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_numeric_literals.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_object_state.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_parameter_defaults.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_property_setter_value_parameters.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_runtime_values.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_scalar_and_foreach.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_type_of_is.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_typeof_is_always_false.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_rules_undeclared.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_runtime_tables.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_statement_token_derivation.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_stripped_lines.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_symbols.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_token_helpers.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_type_completion.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tests/test_type_names.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tools/extract_event_definitions.mjs +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tools/extract_rule_metadata.mjs +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tools/extract_runtime_tables.mjs +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tools/generate_diagnostics_catalogue.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tools/generate_manifest.py +0 -0
- {pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/tools/oracle/build_combining_mark_probe.py +0 -0
|
@@ -5,6 +5,155 @@ All notable changes to pyVBAanalysis are recorded here. The format follows
|
|
|
5
5
|
follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html): a minor version
|
|
6
6
|
per milestone.
|
|
7
7
|
|
|
8
|
+
## 2.1.0 - 2026-09-05
|
|
9
|
+
|
|
10
|
+
Sync to XLIDE 6.1.2, the analyzer's first re-pin since 3.1.4. Verified by a
|
|
11
|
+
differential against the upstream analyzer over the whole oracle corpus: 418 of
|
|
12
|
+
418 cases identical, in both directions.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
* `ambiguousProjectProcedure`: VBA is content for two modules to export the same
|
|
17
|
+
public procedure name, but it refuses to compile an UNQUALIFIED call to that
|
|
18
|
+
name from a module declaring neither. The finding sits at the call site, not
|
|
19
|
+
the declarations, because a project that exports a name twice and always
|
|
20
|
+
qualifies its calls is legal VBA and common. Silent when the call is
|
|
21
|
+
qualified, when the calling module declares the name itself, when a local or
|
|
22
|
+
parameter shadows it, or when only one module exports it.
|
|
23
|
+
* `ConditionalActivityTracker.mutually_exclusive` and `in_same_branch`, the
|
|
24
|
+
primitive the arm-aware rules below need, plus
|
|
25
|
+
`ProjectIndex.implemented_interface_names()` and the
|
|
26
|
+
`AnalyzeModuleOptions.implemented_interfaces` option it feeds.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
* Rules no longer pair declarations from mutually exclusive `#If` arms. Only one
|
|
31
|
+
arm of a chain is ever built, so two declarations in different arms are
|
|
32
|
+
alternatives rather than duplicates. With a compiler constant the analyzer
|
|
33
|
+
cannot evaluate, the ordinary `#If VBA7 / #Else` idiom reported
|
|
34
|
+
`duplicate-procedure` and `duplicate-module-variable` on legal code. The same
|
|
35
|
+
reasoning now covers duplicate declarations and labels, undefined labels,
|
|
36
|
+
`For`/`Next` pairing, `Else` branch order, `Option` placement, and `Implements`
|
|
37
|
+
placement (XLIDE issue #58).
|
|
38
|
+
* A `#Const` directive may precede `Option Explicit`. A conditional-compilation
|
|
39
|
+
directive is not a declaration, and the live VBE compiles it there
|
|
40
|
+
(oracle case `const_directive_before_option_explicit_compile`).
|
|
41
|
+
* Three gaps in return-assignment detection, each of which the widened rule below
|
|
42
|
+
would otherwise have turned into a false positive:
|
|
43
|
+
assigning a field of the returned value (`MsToSystemTime.wYear = ...`) is a
|
|
44
|
+
return assignment; so is an assignment inside a single-line `If`, whose
|
|
45
|
+
branches the statement walk never entered; and a name that SPELLS a keyword is
|
|
46
|
+
still an assignment target, so `Function Read()` assigning `Read = True` was
|
|
47
|
+
read as never assigning its return. Measured on one real workbook these three
|
|
48
|
+
accounted for nine false findings.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
* `missingReturnAssignment` now covers every Function and Property Get, not only
|
|
53
|
+
untyped ones, so a typed Function that never assigns its return reports. An
|
|
54
|
+
empty member of a module some other module declares with `Implements` stays
|
|
55
|
+
silent: that is a contract for an implementer to fill in, not unfinished code.
|
|
56
|
+
A body whose work is to raise stays silent too. **This reports on code that was
|
|
57
|
+
previously quiet**; the rule is warning severity and can be turned off through
|
|
58
|
+
`severity_overrides`.
|
|
59
|
+
* The vendored host models grew with upstream's move to the whole documented
|
|
60
|
+
object model: Excel 229 to 651 types, Word 364 to 627, PowerPoint 201 to 464,
|
|
61
|
+
Access 188 to 451. The wheel grows from 0.94 MB to 1.84 MB; the models still
|
|
62
|
+
load lazily, so import cost is unchanged.
|
|
63
|
+
* Data re-pinned to XLIDE 6.1.2: 418 oracle cases (was 415), 122 audited codes
|
|
64
|
+
(was 121), 119 rules (was 118).
|
|
65
|
+
|
|
66
|
+
### Known limits
|
|
67
|
+
|
|
68
|
+
* Upstream's form-control member work is not ported, and the oracle corpus has no
|
|
69
|
+
UserForm designer cases, so that area is untested here rather than verified.
|
|
70
|
+
|
|
71
|
+
## 2.0.0 - 2026-08-19
|
|
72
|
+
|
|
73
|
+
Multi-host analysis: VBA is now measured against the object model of the Office
|
|
74
|
+
host it actually belongs to. Ports the XLIDE host-model seam and the Word,
|
|
75
|
+
PowerPoint and Access models (xlide_vscode 790e6ea and e56098b, its issues #24
|
|
76
|
+
and #25).
|
|
77
|
+
|
|
78
|
+
### Added
|
|
79
|
+
|
|
80
|
+
* A host seam on the analysis entry points. `analyze_project` and
|
|
81
|
+
`analyze_module_options_for` take a `host` token (`"excel"`, `"word"`,
|
|
82
|
+
`"powerpoint"`, `"access"`, ...), and `AnalyzeModuleOptions` carries `host`
|
|
83
|
+
alongside the existing `host_model`. The semantics are deliberately
|
|
84
|
+
asymmetric: absent means Excel, so every existing caller is unchanged, while a
|
|
85
|
+
NAMED host with no model asserts no host knowledge rather than falling back to
|
|
86
|
+
Excel's. An explicit `host_model` still outranks the token.
|
|
87
|
+
* Word, PowerPoint and Access object models, vendored as
|
|
88
|
+
`data/{word,powerpoint,access}_host_model.json` and extracted mechanically by
|
|
89
|
+
`tools/extract_host_model.mjs` from the generated XLIDE host modules, the same
|
|
90
|
+
pipeline that has always produced the Excel model. 364, 201 and 188
|
|
91
|
+
member-bearing types; 3,742, 1,480 and 1,116 enum constants. Every type is
|
|
92
|
+
non-exhaustive by construction, so absence never becomes a finding.
|
|
93
|
+
* `analyze_office_file(path)` and `read_office_modules(path)` read any container
|
|
94
|
+
pyOpenVBA opens: Excel (`.xlsm`, `.xlsb`, `.xlam`, `.xls`), Word (`.docm`,
|
|
95
|
+
`.dotm`, `.doc`), PowerPoint (`.pptm`, `.potm`) and Access (`.accdb`, `.mdb`,
|
|
96
|
+
read-only). The extension selects the host, so a Word document is analyzed
|
|
97
|
+
against Word's model without the caller saying anything. The CLI accepts them
|
|
98
|
+
all on the same footing.
|
|
99
|
+
* `host_token_for_file_name` and `host_object_model_for_token` on the public API.
|
|
100
|
+
|
|
101
|
+
### Fixed
|
|
102
|
+
|
|
103
|
+
* Word VBA no longer false-positives against Excel's object model. Analyzing a
|
|
104
|
+
Word module as a project previously reported four findings on legal code:
|
|
105
|
+
`ActiveDocument` twice and `wdOrientPortrait` as undeclared variables, and
|
|
106
|
+
`Selection.TypeText` as `member-not-found` against `Excel.Range`. Under the
|
|
107
|
+
Word host it reports none.
|
|
108
|
+
* Host metadata is per-model throughout, closing the Excel defaults that
|
|
109
|
+
remained under the seam. Application-member injection is keyed by model
|
|
110
|
+
(`Volatile` is a known bare call under Excel and unknown under Word), and the
|
|
111
|
+
four rules that consulted host metadata with a bare Excel default now take the
|
|
112
|
+
caller's model: undeclared variables, unknown call, ambiguous enum references,
|
|
113
|
+
and late-bound Friend members. `Me` types host-correctly in a document module:
|
|
114
|
+
`ThisWorkbook` is `Excel.Workbook` under Excel, `ThisDocument` is
|
|
115
|
+
`Word.Document` under Word, and nothing is asserted anywhere else.
|
|
116
|
+
* Word's `ThisDocument` classifies as a document module. It declares
|
|
117
|
+
`VB_Base = "1Normal.ThisDocument"`, naming no CLSID, so GUID matching missed
|
|
118
|
+
it; the host-generic signature is the `VB_PredeclaredId` + `VB_Exposed` pair
|
|
119
|
+
that Office gives every document module. A `.bas` extension or the container's
|
|
120
|
+
own standard-module flag still outranks that signature, since either states
|
|
121
|
+
outright that the module is standard. Differentialled across 116 modules in 16
|
|
122
|
+
real workbooks: nothing reclassified.
|
|
123
|
+
* A named host with no object model no longer reports what it cannot know. An
|
|
124
|
+
empty model is not uniformly quieter than Excel's: member lookups go silent
|
|
125
|
+
because no type resolves, but the rules that ask whether a bare name is legal
|
|
126
|
+
would answer no for every global the host injects, turning an Outlook
|
|
127
|
+
project's own surface into a wall of `undeclared-variable` findings. The four
|
|
128
|
+
rules that need host knowledge now stay silent under an unmodelled host, for
|
|
129
|
+
the same reason they already do on a partial project view.
|
|
130
|
+
* The host-model memos no longer key on a bare `id(model)`. Three caches
|
|
131
|
+
(`_host_model_index`, `_host_constant_index`, and the host-member name set)
|
|
132
|
+
used a plain `dict[int, ...]` that kept no reference to the model, so entries
|
|
133
|
+
accumulated one per model object ever seen and a collected model's id could be
|
|
134
|
+
recycled by a later one, serving one model's index for another. They now use
|
|
135
|
+
the repo's `IdentityLru`, which holds its keys alive and stays bounded. Latent
|
|
136
|
+
before this release, since only the permanently-live Excel singleton was ever
|
|
137
|
+
passed; reachable now that callers choose models.
|
|
138
|
+
|
|
139
|
+
### Changed
|
|
140
|
+
|
|
141
|
+
* `analyze_workbook` and `read_workbook_modules` keep their Excel-only contract
|
|
142
|
+
unchanged. Handed a container the generic reader could open, they now name
|
|
143
|
+
`analyze_office_file` in the error rather than only rejecting the extension.
|
|
144
|
+
* README, the usage guide, the API reference and agent.md describe the four
|
|
145
|
+
hosts; agent.md's pyOpenVBA floor was stale at 3.0.1 and now reads 3.4.0.
|
|
146
|
+
|
|
147
|
+
### Known limits
|
|
148
|
+
|
|
149
|
+
* Legacy `.ppt` is not readable. pyOpenVBA 3.4.0 lists it but reads it as a
|
|
150
|
+
plain CFB, while the VBA project lives in a zlib-compressed CFB inside an
|
|
151
|
+
`ExOleObjStg` record, so every open fails on the missing `dir` stream. The
|
|
152
|
+
extension is rejected up front rather than failing later with a parse error
|
|
153
|
+
that reads like file corruption. Reported as pyOpenVBA issue #17.
|
|
154
|
+
* Access is read-only, following pyOpenVBA: Access executes compiled p-code, so
|
|
155
|
+
a source write would silently change nothing.
|
|
156
|
+
|
|
8
157
|
## 1.4.2 - 2026-08-03
|
|
9
158
|
|
|
10
159
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: pyvbaanalysis
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Pure-Python static analysis for Excel VBA
|
|
5
5
|
Project-URL: Homepage, https://github.com/WilliamSmithEdward/pyVBAanalysis
|
|
6
6
|
Project-URL: Repository, https://github.com/WilliamSmithEdward/pyVBAanalysis
|
|
@@ -24,16 +24,18 @@ Requires-Dist: twine>=5; extra == 'build'
|
|
|
24
24
|
Provides-Extra: dev
|
|
25
25
|
Requires-Dist: mypy==2.3.0; extra == 'dev'
|
|
26
26
|
Requires-Dist: pytest==9.1.1; extra == 'dev'
|
|
27
|
-
Requires-Dist: ruff==0.16.
|
|
27
|
+
Requires-Dist: ruff==0.16.2; extra == 'dev'
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
|
|
30
30
|
# pyVBAanalysis
|
|
31
31
|
|
|
32
|
-
Static analysis for
|
|
33
|
-
errors the VBA compiler would catch, without opening
|
|
32
|
+
Static analysis for Office VBA. It reads your macros and reports likely bugs and the
|
|
33
|
+
errors the VBA compiler would catch, without opening Office or running any code.
|
|
34
34
|
|
|
35
|
-
Point it at
|
|
36
|
-
|
|
35
|
+
Point it at an Excel workbook, a Word document, a PowerPoint deck, an Access
|
|
36
|
+
database, or a set of exported module files, and it returns the problems it finds,
|
|
37
|
+
each with the exact line and a plain explanation. Each file is measured against its
|
|
38
|
+
own host's object model, so Word code is never judged by Excel's surface.
|
|
37
39
|
|
|
38
40
|
## What it checks
|
|
39
41
|
|
|
@@ -63,9 +65,9 @@ Python 3.10 or later. Nothing else to set up.
|
|
|
63
65
|
Analyze a workbook:
|
|
64
66
|
|
|
65
67
|
```python
|
|
66
|
-
from pyvbaanalysis import
|
|
68
|
+
from pyvbaanalysis import analyze_office_file
|
|
67
69
|
|
|
68
|
-
for module, problems in
|
|
70
|
+
for module, problems in analyze_office_file("Budget.xlsm").items():
|
|
69
71
|
for p in problems:
|
|
70
72
|
print(module, p.severity.value, p.code, p.message)
|
|
71
73
|
```
|
|
@@ -105,8 +107,14 @@ clean, so it drops into a CI check.
|
|
|
105
107
|
|
|
106
108
|
## Scope
|
|
107
109
|
|
|
108
|
-
This analyzes
|
|
109
|
-
|
|
110
|
+
This analyzes the VBA inside Office files. It does not run macros and does not need
|
|
111
|
+
Office installed.
|
|
112
|
+
|
|
113
|
+
Each file is measured against its own host's object model, so a Word document is
|
|
114
|
+
never judged by Excel's surface. Readable containers: Excel (.xlsm, .xlsb, .xlam,
|
|
115
|
+
.xls), Word (.docm, .dotm, .doc), PowerPoint (.pptm, .potm) and Access (.accdb,
|
|
116
|
+
.mdb, read-only). Legacy .ppt is not readable yet, because its VBA project sits in
|
|
117
|
+
a compressed record the reader does not open.
|
|
110
118
|
|
|
111
119
|
## Documentation
|
|
112
120
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# pyVBAanalysis
|
|
2
2
|
|
|
3
|
-
Static analysis for
|
|
4
|
-
errors the VBA compiler would catch, without opening
|
|
3
|
+
Static analysis for Office VBA. It reads your macros and reports likely bugs and the
|
|
4
|
+
errors the VBA compiler would catch, without opening Office or running any code.
|
|
5
5
|
|
|
6
|
-
Point it at
|
|
7
|
-
|
|
6
|
+
Point it at an Excel workbook, a Word document, a PowerPoint deck, an Access
|
|
7
|
+
database, or a set of exported module files, and it returns the problems it finds,
|
|
8
|
+
each with the exact line and a plain explanation. Each file is measured against its
|
|
9
|
+
own host's object model, so Word code is never judged by Excel's surface.
|
|
8
10
|
|
|
9
11
|
## What it checks
|
|
10
12
|
|
|
@@ -34,9 +36,9 @@ Python 3.10 or later. Nothing else to set up.
|
|
|
34
36
|
Analyze a workbook:
|
|
35
37
|
|
|
36
38
|
```python
|
|
37
|
-
from pyvbaanalysis import
|
|
39
|
+
from pyvbaanalysis import analyze_office_file
|
|
38
40
|
|
|
39
|
-
for module, problems in
|
|
41
|
+
for module, problems in analyze_office_file("Budget.xlsm").items():
|
|
40
42
|
for p in problems:
|
|
41
43
|
print(module, p.severity.value, p.code, p.message)
|
|
42
44
|
```
|
|
@@ -76,8 +78,14 @@ clean, so it drops into a CI check.
|
|
|
76
78
|
|
|
77
79
|
## Scope
|
|
78
80
|
|
|
79
|
-
This analyzes
|
|
80
|
-
|
|
81
|
+
This analyzes the VBA inside Office files. It does not run macros and does not need
|
|
82
|
+
Office installed.
|
|
83
|
+
|
|
84
|
+
Each file is measured against its own host's object model, so a Word document is
|
|
85
|
+
never judged by Excel's surface. Readable containers: Excel (.xlsm, .xlsb, .xlam,
|
|
86
|
+
.xls), Word (.docm, .dotm, .doc), PowerPoint (.pptm, .potm) and Access (.accdb,
|
|
87
|
+
.mdb, read-only). Legacy .ppt is not readable yet, because its VBA project sits in
|
|
88
|
+
a compressed record the reader does not open.
|
|
81
89
|
|
|
82
90
|
## Documentation
|
|
83
91
|
|
|
@@ -11,11 +11,14 @@ These are re-exported at the package root for convenience:
|
|
|
11
11
|
| Symbol | Role |
|
|
12
12
|
| --- | --- |
|
|
13
13
|
| `analyze_module(source, opts=None)` | Analyze one module's source; returns `list[VbaDiagnostic]`. Never raises. |
|
|
14
|
-
| `analyze_project(modules, *, only=None, severity_overrides=None, conditional_compilation=None, whole_project=True, inline_suppression=True)` | Analyze a set of `ModuleInput`s with cross-module context; returns `dict[str, list[VbaDiagnostic]]`. `whole_project=False` for a partial set. |
|
|
14
|
+
| `analyze_project(modules, *, only=None, severity_overrides=None, conditional_compilation=None, whole_project=True, inline_suppression=True, host=None)` | Analyze a set of `ModuleInput`s with cross-module context; returns `dict[str, list[VbaDiagnostic]]`. `whole_project=False` for a partial set. `host` names the Office host (absent means Excel). |
|
|
15
15
|
| `analyze_loose_file(path, *, severity_overrides=None, conditional_compilation=None, whole_project=False, inline_suppression=True)` | Analyze one `.bas`/`.cls`/`.frm` file (partial by default; the whole-project checks are skipped). |
|
|
16
16
|
| `analyze_loose_files(paths, *, only=None, severity_overrides=None, conditional_compilation=None, whole_project=True, inline_suppression=True)` | Analyze several loose files as one project. |
|
|
17
17
|
| `analyze_workbook(path, *, only=None, severity_overrides=None, conditional_compilation=None, inline_suppression=True)` | Analyze the VBA in an Excel workbook (via pyOpenVBA). |
|
|
18
|
-
| `
|
|
18
|
+
| `analyze_office_file(path, *, only=None, severity_overrides=None, conditional_compilation=None, inline_suppression=True)` | The same for any readable Office container (Excel, Word, PowerPoint, Access); the extension selects the host model. |
|
|
19
|
+
| `host_token_for_file_name(name)` | The host a container implies (`excel`, `word`, `powerpoint`, `access`), or `None`. |
|
|
20
|
+
| `host_object_model_for_token(host)` | The model a host token selects: `None` for Excel (the default), the host's own model, or the empty model when a named host has none. |
|
|
21
|
+
| `analyze_module_options_for(index, name, kind, *, severity_overrides=None, conditional_compilation=None, whole_project=True, inline_suppression=True, host=None)` | Build per-module `AnalyzeModuleOptions` from a populated `ProjectIndex`. |
|
|
19
22
|
| `build_project_index(modules)` | A `ProjectIndex` with every module registered. |
|
|
20
23
|
| `AnalyzeModuleOptions` | Inputs for `analyze_module` (name, kind, project context, overrides). |
|
|
21
24
|
| `VbaDiagnostic` | A single diagnostic (`code`, `message`, `severity`, `span`, `spec_reference`). |
|
|
@@ -69,7 +72,7 @@ them directly, but they are public and stable.
|
|
|
69
72
|
| `pyvbaanalysis.conditional` | Conditional-compilation indexing and activity (`index_conditional_compilation`, `evaluate_conditional_expression`, ...). |
|
|
70
73
|
| `pyvbaanalysis.types` | Type-name helpers: `normalize_type`, `is_known_scalar_type`, `is_numeric_type`, `numeric_literal_bounds`. |
|
|
71
74
|
| `pyvbaanalysis.completion` | Member-completion surface and project-type resolution (`resolve_member_surface_at`, `resolve_type_name`, ...). |
|
|
72
|
-
| `pyvbaanalysis.host` | The Excel host object
|
|
75
|
+
| `pyvbaanalysis.host` | The Excel, Word, PowerPoint and Access host object models and the host registry (`get_excel_object_model`, `host_object_model_for_token`, `resolve_host_alias`, ...). |
|
|
73
76
|
| `pyvbaanalysis.runtime` | VBA runtime functions, constants, and objects (`resolve_runtime_function`, ...). |
|
|
74
77
|
| `pyvbaanalysis.call` | Call-statement shape helpers. |
|
|
75
78
|
| `pyvbaanalysis.flow` | Procedure labels and unstructured-flow detection. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Diagnostic catalogue
|
|
2
2
|
|
|
3
|
-
The diagnostic codes pyVBAanalysis can emit, generated from the rule metadata (`tools/generate_diagnostics_catalogue.py`). This table lists the
|
|
3
|
+
The diagnostic codes pyVBAanalysis can emit, generated from the rule metadata (`tools/generate_diagnostics_catalogue.py`). This table lists the 119 rule-metadata codes across 6 categories. A further 3 structural block-balance codes (`mismatched-end-keyword`, `missing-block-closer`, `unmatched-block-closer`) are emitted by the parser pass and are not in the metadata table, for a full set of 122 codes.
|
|
4
4
|
|
|
5
5
|
Each code is reported only when it is provably correct; anything unknown or ambiguous stays quiet (the no-false-positive discipline). The **kind** column says what a code means: a *compile error* is rejected by the VBE compiler, a *runtime error* is a deterministic Run-time error, a *runtime risk* is a likely fault, and *style* is advisory.
|
|
6
6
|
|
|
@@ -69,11 +69,12 @@ Override a code's severity with `AnalyzeModuleOptions.severity_overrides` (or th
|
|
|
69
69
|
| `undeclared-variable` | Variable not defined | error | compile error | MS-VBAL 5.2.4.1.1 |
|
|
70
70
|
| `unknown-call` | Sub or Function not defined | error | compile error | MS-VBAL 5.4.2.1 |
|
|
71
71
|
|
|
72
|
-
## Semantic (
|
|
72
|
+
## Semantic (51)
|
|
73
73
|
|
|
74
74
|
| Code | Title | Default | Kind | Spec reference |
|
|
75
75
|
| --- | --- | --- | --- | --- |
|
|
76
76
|
| `ambiguous-enum-member` | Ambiguous Enum member reference | error | compile error | VBE compiler: Ambiguous name detected |
|
|
77
|
+
| `ambiguous-project-procedure` | Ambiguous unqualified call to a name two modules export | error | compile error | VBE compiler: Ambiguous name detected |
|
|
77
78
|
| `argument-count` | Wrong number of arguments | error | compile error | MS-VBAL 5.4.2.1 |
|
|
78
79
|
| `argument-object-type-mismatch` | Object argument type mismatch | error | compile error | MS-VBAL 5.3.1 |
|
|
79
80
|
| `argument-shape-mismatch` | Argument shape (array/Type vs scalar) mismatch | error | compile error | MS-VBAL 5.3.1 (argument passing) / VBE compiler: ByRef argument type mismatch; array or user-defined type expected |
|
|
@@ -136,21 +136,53 @@ analyze_loose_files(["Module1.bas", "Widget.cls"]) # several, as one project
|
|
|
136
136
|
skipped for it (see "Whole project vs a single file" below). Use `analyze_loose_files`
|
|
137
137
|
to analyze several files together with shared cross-module context.
|
|
138
138
|
|
|
139
|
-
###
|
|
139
|
+
### Office macro containers
|
|
140
140
|
|
|
141
|
-
The
|
|
141
|
+
The container reader reads VBA directly out of an Office file via pyOpenVBA (the one
|
|
142
142
|
runtime dependency). pyOpenVBA is imported lazily, so `import pyvbaanalysis` stays
|
|
143
|
-
light.
|
|
143
|
+
light.
|
|
144
|
+
|
|
145
|
+
| Host | Extensions |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| Excel | `.xlsm`, `.xlsb`, `.xlam`, `.xls` |
|
|
148
|
+
| Word | `.docm`, `.dotm`, `.doc` |
|
|
149
|
+
| PowerPoint | `.pptm`, `.potm` |
|
|
150
|
+
| Access | `.accdb`, `.mdb` (read-only) |
|
|
144
151
|
|
|
145
152
|
```python
|
|
146
|
-
from pyvbaanalysis import
|
|
153
|
+
from pyvbaanalysis import analyze_office_file
|
|
147
154
|
|
|
148
|
-
|
|
149
|
-
|
|
155
|
+
analyze_office_file("Book.xlsm") # dict: module name -> diagnostics
|
|
156
|
+
analyze_office_file("Report.docm") # resolved against Word's model
|
|
157
|
+
analyze_office_file("Book.xlsm", only=["Sheet1"]) # one module by name, full context
|
|
150
158
|
```
|
|
151
159
|
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
The extension selects the host, so Word code is measured against Word's object model
|
|
161
|
+
and never against Excel's. Analyzing a Word module under Excel's model reports
|
|
162
|
+
members and constants that are perfectly legal in Word (`Selection.TypeText`,
|
|
163
|
+
`ActiveDocument`, `wdOrientPortrait`), which is the false positive this avoids.
|
|
164
|
+
|
|
165
|
+
`analyze_workbook` remains the Excel-only entry point and is unchanged.
|
|
166
|
+
|
|
167
|
+
A path that is not a readable container, or a container with no readable VBA, raises
|
|
168
|
+
`WorkbookReadError`. Legacy `.ppt` is not readable: pyOpenVBA reads it as a plain
|
|
169
|
+
CFB, but its VBA project sits inside a compressed record, so the extension is
|
|
170
|
+
rejected rather than failing later with a parse error
|
|
171
|
+
([pyOpenVBA #17](https://github.com/WilliamSmithEdward/pyOpenVBA/issues/17)).
|
|
172
|
+
|
|
173
|
+
### Choosing the host yourself
|
|
174
|
+
|
|
175
|
+
When you already have the module text, name the host directly. Absent means Excel,
|
|
176
|
+
so existing calls are unchanged; a named host with no model asserts no host
|
|
177
|
+
knowledge at all rather than falling back to Excel's.
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from pyvbaanalysis import analyze_project
|
|
181
|
+
|
|
182
|
+
analyze_project(modules, host="word") # Word's object model
|
|
183
|
+
analyze_project(modules, host="outlook") # no model yet: stays quiet, never Excel's
|
|
184
|
+
analyze_project(modules) # Excel, exactly as before
|
|
185
|
+
```
|
|
154
186
|
|
|
155
187
|
The reader loads the workbook and its VBA into memory and does not bound the input
|
|
156
188
|
size, so impose your own limit (for example a maximum file size) before pointing it
|
|
@@ -227,8 +259,9 @@ pyvbaanalysis Book.xlsm --format json
|
|
|
227
259
|
```
|
|
228
260
|
|
|
229
261
|
A path may be a loose file, a folder of loose files (analyzed together as one
|
|
230
|
-
project), or an
|
|
231
|
-
`
|
|
262
|
+
project), or an Office macro container, whose extension selects the host model.
|
|
263
|
+
`pyvbaanalysis --version` prints the version, and `python -m pyvbaanalysis` is
|
|
264
|
+
equivalent to the `pyvbaanalysis` command.
|
|
232
265
|
|
|
233
266
|
Flags:
|
|
234
267
|
|
|
@@ -38,7 +38,7 @@ dependencies = ["pyOpenVBA>=3.4.0"]
|
|
|
38
38
|
# underneath main (ruff 0.16 broke a green tree with zero repo commits).
|
|
39
39
|
# Dependabot's weekly pip updates turn each tool upgrade into its own gated
|
|
40
40
|
# PR, where a red CI run blocks the bump instead of breaking main.
|
|
41
|
-
dev = ["pytest==9.1.1", "ruff==0.16.
|
|
41
|
+
dev = ["pytest==9.1.1", "ruff==0.16.2", "mypy==2.3.0"]
|
|
42
42
|
build = ["build>=1.2", "twine>=5"]
|
|
43
43
|
|
|
44
44
|
[project.scripts]
|
|
@@ -9,6 +9,8 @@ The headline entry points are re-exported here for convenience:
|
|
|
9
9
|
* analyze_project(modules): analyze a set of modules with cross-module context.
|
|
10
10
|
* analyze_workbook(path): analyze the VBA in an Excel workbook (read via pyOpenVBA,
|
|
11
11
|
the one external runtime dependency).
|
|
12
|
+
* analyze_office_file(path): the same for any Office macro container (Excel, Word,
|
|
13
|
+
PowerPoint, or read-only Access), resolving against that host's object model.
|
|
12
14
|
* analyze_loose_file(path) / analyze_loose_files(paths): analyze loose
|
|
13
15
|
.bas / .cls / .frm export files.
|
|
14
16
|
|
|
@@ -30,10 +32,16 @@ from .diagnostics import (
|
|
|
30
32
|
validate_severity_overrides,
|
|
31
33
|
)
|
|
32
34
|
from .project import analyze_module_options_for, analyze_project, build_project_index
|
|
33
|
-
from .
|
|
35
|
+
from .host import host_object_model_for_token, host_token_for_file_name
|
|
36
|
+
from .reader import (
|
|
37
|
+
analyze_loose_file,
|
|
38
|
+
analyze_loose_files,
|
|
39
|
+
analyze_office_file,
|
|
40
|
+
analyze_workbook,
|
|
41
|
+
)
|
|
34
42
|
from .symbols import ModuleInput, ModuleSymbolKind, ProjectIndex, ProjectIndexOptions
|
|
35
43
|
|
|
36
|
-
__version__ = "1.
|
|
44
|
+
__version__ = "2.1.0"
|
|
37
45
|
|
|
38
46
|
__all__ = [
|
|
39
47
|
"AnalyzeModuleOptions",
|
|
@@ -50,9 +58,12 @@ __all__ = [
|
|
|
50
58
|
"analyze_loose_files",
|
|
51
59
|
"analyze_module",
|
|
52
60
|
"analyze_module_options_for",
|
|
61
|
+
"analyze_office_file",
|
|
53
62
|
"analyze_project",
|
|
54
63
|
"analyze_workbook",
|
|
55
64
|
"build_project_index",
|
|
65
|
+
"host_object_model_for_token",
|
|
66
|
+
"host_token_for_file_name",
|
|
56
67
|
"line_col",
|
|
57
68
|
"rule_metadata_by_code",
|
|
58
69
|
"validate_severity_overrides",
|
|
@@ -30,13 +30,14 @@ from .diagnostics import (
|
|
|
30
30
|
validate_severity_overrides,
|
|
31
31
|
)
|
|
32
32
|
from .project import analyze_project
|
|
33
|
+
from .host import host_token_for_file_name
|
|
33
34
|
from .reader import (
|
|
34
|
-
EXCEL_EXTENSIONS,
|
|
35
35
|
LOOSE_EXTENSIONS,
|
|
36
|
+
OFFICE_EXTENSIONS,
|
|
36
37
|
LooseFileReadError,
|
|
37
38
|
WorkbookReadError,
|
|
38
39
|
load_loose_module,
|
|
39
|
-
|
|
40
|
+
read_office_modules,
|
|
40
41
|
)
|
|
41
42
|
from .symbols import ModuleInput
|
|
42
43
|
|
|
@@ -151,7 +152,7 @@ def _analyze_workbook_group(
|
|
|
151
152
|
errors: list[str] = []
|
|
152
153
|
for path in paths:
|
|
153
154
|
try:
|
|
154
|
-
modules =
|
|
155
|
+
modules = read_office_modules(path)
|
|
155
156
|
except WorkbookReadError as exc:
|
|
156
157
|
errors.append(f"{path}: {exc}")
|
|
157
158
|
continue
|
|
@@ -163,6 +164,7 @@ def _analyze_workbook_group(
|
|
|
163
164
|
only=only or None,
|
|
164
165
|
severity_overrides=severity_overrides or None,
|
|
165
166
|
inline_suppression=inline_suppression,
|
|
167
|
+
host=host_token_for_file_name(path.name),
|
|
166
168
|
)
|
|
167
169
|
sources = {m.name: m.source for m in modules}
|
|
168
170
|
results.append(_ProjectResult(str(path), diagnostics, sources))
|
|
@@ -332,7 +334,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
332
334
|
print("error: unknown diagnostic code(s): " + ", ".join(unknown_codes), file=sys.stderr)
|
|
333
335
|
return 2
|
|
334
336
|
|
|
335
|
-
workbook_paths = [p for p in paths if p.is_file() and p.suffix.lower() in
|
|
337
|
+
workbook_paths = [p for p in paths if p.is_file() and p.suffix.lower() in OFFICE_EXTENSIONS]
|
|
336
338
|
other_paths = [p for p in paths if p not in workbook_paths]
|
|
337
339
|
loose_paths = _gather_loose_paths(other_paths)
|
|
338
340
|
unknown = [p for p in loose_paths if p.suffix.lower() not in LOOSE_EXTENSIONS]
|
{pyvbaanalysis-1.4.2 → pyvbaanalysis-2.1.0}/pyvbaanalysis/conditional/conditional_compilation.py
RENAMED
|
@@ -114,10 +114,42 @@ class _ConditionalFrame:
|
|
|
114
114
|
seen_unknown: bool
|
|
115
115
|
|
|
116
116
|
|
|
117
|
+
@dataclass(frozen=True, slots=True)
|
|
118
|
+
class _ConditionalArm:
|
|
119
|
+
"""One arm of one `#If` chain, as a persistent stack.
|
|
120
|
+
|
|
121
|
+
``parent`` is the enclosing chain's arm. Immutable, so an event can keep the
|
|
122
|
+
arm that was in effect when it was recorded without copying the stack, and
|
|
123
|
+
one arm is one object, which makes identity the comparison for
|
|
124
|
+
``in_same_branch``.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
chain: int
|
|
128
|
+
#: 0 for the `#If`, then one per `#ElseIf` / `#Else`.
|
|
129
|
+
index: int
|
|
130
|
+
parent: "_ConditionalArm | None"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _arms_diverge(a: _ConditionalArm | None, b: _ConditionalArm | None) -> bool:
|
|
134
|
+
"""Whether two arm stacks disagree about which arm of a shared chain they are
|
|
135
|
+
in. The branches then exclude each other whatever the constants are worth.
|
|
136
|
+
Stacks are as deep as the source nests directives, so the walk is short."""
|
|
137
|
+
outer = a
|
|
138
|
+
while outer is not None:
|
|
139
|
+
inner = b
|
|
140
|
+
while inner is not None:
|
|
141
|
+
if outer.chain == inner.chain:
|
|
142
|
+
return outer.index != inner.index
|
|
143
|
+
inner = inner.parent
|
|
144
|
+
outer = outer.parent
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
|
|
117
148
|
@dataclass(slots=True)
|
|
118
149
|
class _ConditionalActivityEvent:
|
|
119
150
|
start: int
|
|
120
151
|
activity: ConditionalActivity
|
|
152
|
+
branch: _ConditionalArm | None = None
|
|
121
153
|
|
|
122
154
|
|
|
123
155
|
class ConditionalActivityTracker:
|
|
@@ -128,7 +160,7 @@ class ConditionalActivityTracker:
|
|
|
128
160
|
def __init__(self, events: list[_ConditionalActivityEvent]) -> None:
|
|
129
161
|
self._events = events
|
|
130
162
|
|
|
131
|
-
def
|
|
163
|
+
def _event_for_span(self, span: Span) -> _ConditionalActivityEvent | None:
|
|
132
164
|
# Directives starting at or after the queried offset are not applied.
|
|
133
165
|
lo = -1
|
|
134
166
|
hi = len(self._events) - 1
|
|
@@ -138,11 +170,41 @@ class ConditionalActivityTracker:
|
|
|
138
170
|
lo = mid
|
|
139
171
|
else:
|
|
140
172
|
hi = mid - 1
|
|
141
|
-
return self._events[lo]
|
|
173
|
+
return self._events[lo] if lo >= 0 else None
|
|
174
|
+
|
|
175
|
+
def activity_for_span(self, span: Span) -> ConditionalActivity:
|
|
176
|
+
event = self._event_for_span(span)
|
|
177
|
+
return event.activity if event is not None else ConditionalActivity.ACTIVE
|
|
142
178
|
|
|
143
179
|
def is_inactive(self, span: Span) -> bool:
|
|
144
180
|
return self.activity_for_span(span) is ConditionalActivity.INACTIVE
|
|
145
181
|
|
|
182
|
+
def mutually_exclusive(self, a: Span, b: Span) -> bool:
|
|
183
|
+
"""Whether the two spans sit in different arms of one `#If` chain, and so
|
|
184
|
+
are never compiled together however the constants evaluate."""
|
|
185
|
+
left = self._event_for_span(a)
|
|
186
|
+
right = self._event_for_span(b)
|
|
187
|
+
return _arms_diverge(
|
|
188
|
+
left.branch if left is not None else None,
|
|
189
|
+
right.branch if right is not None else None,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def in_same_branch(self, a: Span, b: Span) -> bool:
|
|
193
|
+
"""Whether the two spans sit under exactly the same arms, so every build
|
|
194
|
+
either compiles both or neither.
|
|
195
|
+
|
|
196
|
+
Stricter than "not mutually exclusive": spans in two SEPARATE chains are
|
|
197
|
+
neither exclusive nor in the same branch, because a build may take one and
|
|
198
|
+
not the other. Rules that pair two pieces of one construct need this, since
|
|
199
|
+
a pairing made across different chains is a guess about a build that may
|
|
200
|
+
never exist.
|
|
201
|
+
"""
|
|
202
|
+
left = self._event_for_span(a)
|
|
203
|
+
right = self._event_for_span(b)
|
|
204
|
+
return (left.branch if left is not None else None) is (
|
|
205
|
+
right.branch if right is not None else None
|
|
206
|
+
)
|
|
207
|
+
|
|
146
208
|
|
|
147
209
|
def create_conditional_activity_tracker(
|
|
148
210
|
module: ModuleNode, env: ConditionalCompilationEnvironment | None = None
|
|
@@ -162,10 +224,25 @@ def _collect_conditional_activity_events(
|
|
|
162
224
|
project_constants = _lowercased(effective_env.project_constants)
|
|
163
225
|
stack: list[_ConditionalFrame] = []
|
|
164
226
|
current = ConditionalActivity.ACTIVE
|
|
227
|
+
branch: _ConditionalArm | None = None
|
|
228
|
+
chains = 0
|
|
165
229
|
events: list[_ConditionalActivityEvent] = []
|
|
166
230
|
for occ in directives:
|
|
167
231
|
current = _apply_conditional_directive(occ.directive, effective_env, project_constants, stack, current)
|
|
168
|
-
|
|
232
|
+
kind = occ.directive.directive_kind
|
|
233
|
+
if kind is ConditionalDirectiveKind.IF:
|
|
234
|
+
branch = _ConditionalArm(chain=chains, index=0, parent=branch)
|
|
235
|
+
chains += 1
|
|
236
|
+
elif kind in (ConditionalDirectiveKind.ELSE_IF, ConditionalDirectiveKind.ELSE):
|
|
237
|
+
# An `#ElseIf` with no open `#If` is a parse-level error; leave the
|
|
238
|
+
# stack alone rather than inventing an arm for it.
|
|
239
|
+
if branch is not None:
|
|
240
|
+
branch = _ConditionalArm(chain=branch.chain, index=branch.index + 1, parent=branch.parent)
|
|
241
|
+
elif kind is ConditionalDirectiveKind.END_IF:
|
|
242
|
+
branch = branch.parent if branch is not None else None
|
|
243
|
+
events.append(
|
|
244
|
+
_ConditionalActivityEvent(start=occ.directive.span.start, activity=current, branch=branch)
|
|
245
|
+
)
|
|
169
246
|
return events
|
|
170
247
|
|
|
171
248
|
|