Repository navigation
Releases: WilliamSmithEdward/pyVBAanalysis
Release list
pyVBAanalysis 3.0.1
Sync pyVBAanalysis to XLIDE 11.1.0, commit
8f64c2ce8d4c7104c7aa3559da0e98b6c178303e.
- Correct module-owned DefType defaults, indexed property setter contracts,
array and enum assignment identities, and bracketed source member names. - Check read-only Excel Range values and scalar host setter assignments.
- Reduce false positives from assumed Office collection contents and mutable
state after calls, aliases and error handlers. - Add Scripting and VBScript RegExp reference models and preserve enum library
ownership. - Support the upstream
errors_onlyanalysis option.
Validation: 38,879 distinct recorded upstream calls, 13,969 corpus module
analyses, and 132 modules from Office, export and VB6 projects all match.
Two shared upstream assignment gaps remain tracked in
xlide_vscode#1303
and #1304.
The Publish workflow attaches security and malware reports and signed build
provenance to this release.
pyVBAanalysis 3.0.0
pyVBAanalysis 3.0.0 syncs the analyzer to XLIDE 11.0.0 at
751fdb2bcec07e2f4dc956d97bed5ae159bfb93a.
- Recognize VBA-qualified zero divisors and bracketed class fields and results.
- Preserve documentation comments and edits across LF, CRLF and CR lines.
- Evaluate deep Null expressions and forward constant dependencies with explicit
stacks, and match upstream's conditional-expression nesting limit. - Normalize host and referenced-library tokens, and include the earlier sync's
Office reference-library and saved-workbook-sheet context. - Include the release provenance, fuzzing, and dependency-lock improvements
documented in CHANGELOG.md since 2.3.1.
Validation: 51,140 Python tests pass (14 skipped); lint and strict type checking
pass. The oracle and Office/export/VB6 differentials match. Four DefLng compile-error
false positives in XLIDE are confirmed to compile in Excel and are tracked in
XLIDE #1235;
the port preserves its correct behavior on these cases.
v2.3.1
Follows XLIDE 10.14.1, from 10.14.0, and adds a command-line flag for projects that ship as one file. The full list is in CHANGELOG.md.
Added
--whole-projectruns the whole-project checks (undeclared-variable,unknown-call,member-not-found) on any input, a single file included. It cannot be combined with--partial-project(#15).- Security checks: CodeQL, Semgrep and pip-audit run on every push, and a release reaches PyPI only when all three pass. This release carries its
security-report.md.
Fixed
property-accessor-signature-mismatchno longer reports a Property Set beside a Get of another type, such as a Variant Get with an Object Set. Excel compiles it; a Let must still match its Get (XLIDE issue #152).
Verified
The oracle corpus, 16 real workbooks, 195 Office files, 693 further projects and 7 VB6 projects give identical findings from both analyzers at 10.14.1.
v2.3.0
Follows XLIDE 10.14.0, from 10.7.1. Upstream measured its analyzer against Excel, Word and PowerPoint and answered each finding (XLIDE issues #96 to #148): 34 new diagnostics, 19 of them runtime errors in code the VBE compiles, and false positives removed on code that compiles and runs. The full list is in CHANGELOG.md.
Added
- Runtime errors the code proves: Integer overflow such as
60 * 60 * 24, a For counter that passes its type, an error handler fallen into,Resumewith no error,Returnwith no GoSub, a property that calls itself, a Collection index or key no element has, a key added twice, a Variant used as an object, a late-bound member that is not there, index 0 into an Office collection,Cells(0, 1), a multi-cell range read as a value, a sheet name Excel refuses, and file numbers used after Close, twice or in the wrong mode. - Compile errors: a Const or literal that overflows, a ByVal or Optional array parameter, a bracketed variable name, a second Deftype for a letter, a Collection beside an operator, a Sub read as a value, an Implements member missing or with another signature,
RemafterThen, a#Constdefined twice or code after a directive, characters VBA does not use, a line over 1023 characters, and date literals the VBE refuses. AnalyzeModuleOptions.raw_rule_outputreturns the rules' own list.
Changed
analyze_modulereturns what XLIDE shows: a runtime-error finding underOn Error Resume Nextis dropped, since the error is handled there, and findings with one code and span are merged.member-not-foundalso works in Word, PowerPoint and Access, where the type library closes the interface.- A hex literal is the signed value of its bits:
&H80000000is -2147483648.
Fixed
- False positives on code that compiles and runs, among them
If c Is Nothing Then Set c = New Collection,Err.Raise vbObjectError + 513, Win32 flag Enums such asGENERIC_READ = &H80000000,If x Then Debug.Print a; b,AddressOfa Sub,Choosewith more than two choices,On Local Error, DAO constants in Access, and a division the code guards. - A procedure nested about 490 blocks deep raised RecursionError. The VBE compiles a thousand levels, and the port now analyzes them.
Performance
A 500-line With block took 14.2 seconds in 2.2.1 and takes 0.19, and a 3,000-member chain took 16 seconds and takes 0.56. On two parts of the test corpus's Office files, analysis is 12% faster than in 2.2.1, new rules included.
Verified
Upstream's own 10.14.0 test suite replays through the port identically (1419 standalone and 992 project calls), and the oracle corpus, 16 real workbooks, 195 Office files of every host, 693 further projects and 7 VB6 projects give identical findings from both analyzers.
v2.2.1
Follows XLIDE 10.7.1, from 10.6.0. Upstream fixed how its lexer and parser read a handful of lines the VBE compiles (XLIDE issues #82 to #90), and this release takes each fix. The full list is in CHANGELOG.md.
Fixed
- What a one-line If runs after a colon belongs to the If. In
If conn Is Nothing Then ACCT = CVErr(xlErrNA): Exit Function, theExit Functionruns only whenconnis Nothing, so the next line is no longer reported as unreachable. ASet x = Nothingor anErasethere no longer makes the next line report an unset object or an unallocated array. - A comment ending in
_runs on to the next line, and a line continuation may have spaces after its_. The line after either is no longer read as code or reported for a missingThen. If x Then:is a one-line If, and the one-wordEndIfcloses a block If.1.,&17,a => banda < > bare read as the VBE reads them.s = "a" &1, which the VBE refuses, is now reported.Needs, 2is a call, so a missing required argument is reported there.Shape.Duplicatereturns a Shape andSparklineGroup.SeriesColora FormatColor, and aChartsorWorksheetscollection can be assigned to aSheetsvariable.- The command line numbers a
.clsor.frmexport's lines from the top of the file. A class module's findings printed four lines early.
Added
LoadedModule.designer_block: the export header the reader stripped ahead ofsource, so a span in the analyzed body can be placed in the file.
Changed
- A contextual keyword such as
Text,SteporOutputis a keyword token only inside the statement that makes it one, so a variable calledtextlexes as an identifier.
Verified
Upstream's own 10.7.1 test suite replays through the port identically (1081 standalone and 936 project calls), and the oracle corpus, 16 real workbooks, 195 Office files of every host and 693 further projects give identical diagnostics from both analyzers.
v2.2.0
Follows XLIDE 10.6.0, from 6.2.0: twelve new diagnostics, early binding checked against the libraries a project references, and member lookups that follow the type library's own extensibility flags. The full list is in CHANGELOG.md.
New checks
invalid-option-statement(error): a malformed Option statement, such asOption Base 2orOption Explicit Foo.missing-library-reference(error): a type or constant qualified with an Office library the project does not reference, such asDim doc As Word.Documentin a workbook with no reference to Word. It is reported only against a known reference list, whichanalyze_office_fileand the CLI read from the container.- Dead code, at
information:unused-variable,variable-never-read,unused-procedureandunreachable-code, with edits that remove the dead code. - Doc comments, at
warning: six checks that compare the XML tags of a'''block with the declaration it documents. - Member calls are checked for arity and argument types, on host objects and project classes alike:
Application.Calculate(1), a bareErr.Raise,p.Save "bad"on a class whoseSavetakes a Long.
Before you upgrade
The CLI's default --fail-level information fails on any diagnostic, so a workbook with one unused variable now exits 1. Pass --fail-level warning to gate on warnings and errors only.
member-not-found reports a member absent from an Excel interface only when the type library closes that interface, as the VBE does. Application.Match and the other worksheet functions reached through Application are no longer reported.
Fixed
False positives 2.1.1 reported on code that compiles, among them: undeclared-variable on XlDirection.xlUp and on a UserForm's own controls, object-variable-not-set on a variable declared As New, member-not-found on a member the type library hides such as ws.OnEntry, readonly-member-assignment on a comparison such as ElseIf w.Part = "b" Then, and event-handler-module-scope on the Document events of Word's ThisDocument.
Verified
Against the upstream analyzer it mirrors. Upstream's own test suite was replayed through the port, 1055 standalone and 929 project calls, every result identical. The oracle corpus (1818 diagnostics), 16 real workbooks, 195 Office files from Excel, Word, PowerPoint and Access, and 693 further projects give identical diagnostics from both analyzers.
v2.1.1
Follows XLIDE 6.2.0, which fixes the issue this port reported upstream (xlide_vscode#68). The vendored data is byte-identical to 6.1.2, so only the manifest's version moves; the analyzer change below is the whole release.
A module's own procedures shadow the host's globals
A module VARIABLE named rows already resolved from its declaration, but a procedure of the same name did not:
Public Property Get rows() As Widget
End Property
Public Function Where(ByVal p As Variant) As Widget
End Function
Public Function Use(ByVal p As Variant) As Widget
Set Use = rows.Where(p) ' rows fell through to Excel's global Rows
End FunctionThe names that collide are the ones every workbook uses: rows, columns, cells, selection, names, sheets, application. A Function or Property Get now yields its return type; a Sub, or a Property with only Let/Set, yields nothing readable but still shadows the global rather than letting the host answer for it.
What changes for you
This port never emitted the false positive upstream had, because it resolved such a receiver to nothing at all rather than to the wrong type. The fix replaces that silence with a correct binding, so a receiver that used to go unchecked is now checked: with project context, a genuinely absent member on one of these names reports member-not-found where it previously passed. Analyzed standalone, without project context, behaviour is unchanged.
Verification
Differential against the upstream 6.2.0 analyzer: 418 of 418 oracle cases identical in both directions, and identical on a real workbook's modules. 16 real workbooks stay silent. 3,039 tests green; ruff and mypy clean; CI green across Python 3.10 to 3.13 and the language matrix on Linux and Windows.
Upstream's other 6.2.0 work is the seven refactorings, which are editor code actions and out of scope for this port.
v2.1.0
Sync to XLIDE 6.1.2, the analyzer's first data re-pin since 3.1.4. Upstream had moved 192 commits, 53 of them touching the analyzer.
Verified by a differential against the upstream analyzer over the whole oracle corpus: 418 of 418 cases identical, in both directions — nothing upstream reports that this misses, nothing this reports that upstream does not.
Fixed: arms of one #If chain are alternatives, not duplicates
Only one arm of a chain is ever built, so two declarations in different arms are not duplicates. With a compiler constant the analyzer cannot evaluate, the ordinary idiom reported findings on legal code:
#If CUSTOMFLAG Then
Public Const MODE As String = "a"
#Else
Public Const MODE As String = "b"
#End IfThat reported duplicate-module-variable, and the same shape around a Sub reported duplicate-procedure. The tracker gains mutually_exclusive and in_same_branch, and eight rule sites now use them: duplicate procedures, module members, declarations and labels, undefined labels, For/Next pairing, Else branch order, and Option and Implements placement.
A #Const directive may also precede Option Explicit now. A directive is not a declaration, and the live VBE compiles it there.
Added: ambiguous-project-procedure
VBA is content for two modules to export the same public procedure name, but refuses to compile an unqualified call to that name from a module declaring neither. The finding sits at the call site, not the declarations, because a project that exports a name twice and always qualifies its calls is legal VBA and common. Silent when the call is qualified, when the calling module declares the name itself, when a local shadows it, or when only one module exports it.
Changed: missing-return-assignment covers typed functions
The rule previously reported only untyped functions. It now covers every Function and Property Get, so a typed Function that never assigns its return reports. An empty member of a module another module declares with Implements stays silent, since that is a contract for an implementer to fill in; a body whose work is to raise stays silent too.
This reports on code that was previously quiet. The rule is warning severity and can be turned off through severity_overrides.
The widening is also where the interesting work was. It reported 145 findings across 16 real workbooks that were previously silent, while upstream reported none on the same input, so the divergence was local. Three detector gaps, each isolated to a minimal case:
- assigning a field of the returned value (
MsToSystemTime.wYear = ...) is a return assignment - so is an assignment inside a single-line
If, whose branches the statement walk never entered - a name that SPELLS a keyword is still an assignment target, so
Function Read()assigningRead = Trueread as never assigning its return
All 16 workbooks are silent again.
Data
Re-pinned to XLIDE 6.1.2: 418 oracle cases (was 415), 122 audited codes (was 121), 119 rules (was 118). The host models grew with upstream's move to the whole documented object model, Excel 229 to 651 types and Word 364 to 627, which takes the wheel from 0.94 MB to 1.84 MB. The models still load lazily, so import cost is unchanged.
Known limits
Upstream's form-control member work is not ported, and the oracle corpus carries no UserForm designer cases, so that area is untested here rather than verified.
3,033 tests green; ruff and mypy clean; CI green across Python 3.10 to 3.13 and the language matrix on Linux and Windows.
v2.0.0
VBA is now analyzed against the object model of the Office host it actually belongs to. Ports the XLIDE host-model seam and the Word, PowerPoint and Access models (xlide_vscode 790e6ea and e56098b, its issues #24 and #25).
The false positive this removes
Legal Word VBA, analyzed as a project, previously reported four findings, every one of them wrong:
undeclared-variable 'ActiveDocument' (twice)
undeclared-variable 'wdOrientPortrait'
member-not-found 'Excel.Range.TypeText'
Under the Word host it reports none.
What is new
- A host seam.
analyze_projectandanalyze_module_options_fortake ahosttoken;AnalyzeModuleOptionscarrieshostbesidehost_model. Absent means Excel, so every existing caller is unchanged. A named host with no model asserts no host knowledge rather than falling back to Excel's. - Word, PowerPoint and Access object models, extracted mechanically from the generated XLIDE host modules by the same pipeline that has always produced the Excel model: 364, 201 and 188 member-bearing types, and 3,742, 1,480 and 1,116 enum constants. Every type is non-exhaustive, so absence never becomes a finding.
analyze_office_fileandread_office_modulesread Excel (.xlsm,.xlsb,.xlam,.xls), Word (.docm,.dotm,.doc), PowerPoint (.pptm,.potm) and Access (.accdb,.mdb, read-only). The extension selects the host, so a Word document is analyzed against Word without the caller saying anything. The CLI accepts them all.
Three defects found while verifying the work
- An empty model is not uniformly quieter than Excel's. Member lookups go silent, but the rules asking whether a bare name is legal would answer no for every global the host injects, turning an Outlook project's own surface into a wall of
undeclared-variablefindings. Those rules now stay silent under an unmodelled host, for the same reason they already do on a partial project view. - The host-model memos keyed on a bare
id(model)in plain dicts holding no reference to the model, so they grew one entry per model object ever seen and a collected model's id could be recycled by a later one, serving one model's index for another. They now useIdentityLru, which holds its keys alive and stays bounded. HostObjectModeltypes its maps asMapping, so the frozen empty singleton type-checks and no shared model can be mutated in place.
Compatibility
analyze_workbook and read_workbook_modules keep their Excel-only contract. Word's ThisDocument classifies as a document module through the host-generic VB_PredeclaredId + VB_Exposed pair, since it names no CLSID; a .bas extension or the container's own standard flag still outranks that signature. Differentialled across 116 modules in 16 real workbooks: nothing reclassified, and all 16 stay silent.
Known limits
- Legacy
.pptis not readable. pyOpenVBA 3.4.0 lists it but reads it as a plain CFB, while the VBA project lives in a zlib-compressed CFB inside anExOleObjStgrecord. The extension is rejected up front rather than failing later with a parse error that reads like file corruption. Reported as pyOpenVBA #17. - Access is read-only, following pyOpenVBA: Access executes compiled p-code, so a source write would silently change nothing.
2,963 tests green; ruff and mypy clean; CI green across Python 3.10 to 3.13 and the language matrix on Linux and Windows.
v1.4.2
Parity fix following XLIDE's own combining-mark fix (its issue #8).
Fixed
- Identifier continuation now accepts every Unicode mark category. The 1.4.1 fix listed only
MnandMc; XLIDE widened its equivalent patterns to\p{M}, which also covers enclosing marks (Me), so an identifier carrying one still split here. agent.md makes the XLIDE TypeScript the executable spec for this port, and widening what continues a name can only remove false positives, so the predicate now accepts any category beginning withM.
Enclosing marks in VBA identifiers are vanishingly rare, which is why nothing caught it earlier: the oracle probe covered Mn and the language matrix covers Mn and Mc. It surfaced from verifying XLIDE's fix against this one side by side.
Install: pip install --upgrade pyvbaanalysis