README
¶
tree-sitter-vba
A Tree-sitter grammar for Visual Basic for Applications (VBA), targeting
exported Excel/VBA source files such as .bas, .cls, and .frm.
The playground runs this parser through WebAssembly entirely in your browser.
Use it to test parser compatibility, inspect synchronized concrete syntax trees,
and see the official highlights.scm query in action; source code is not
uploaded or sent to a parsing service.
This grammar is designed as a parsing foundation for editor and tooling use cases, including syntax highlighting, folding, tags, outline extraction, symbol inspection, linting, formatting, and future LSP integrations.
Why another VBA grammar?
Several tree-sitter grammars exist for Visual Basic-family languages, but VBA has its own syntax and practical edge cases, especially in code exported from the VBE.
This project focuses on practical Excel/VBA source compatibility:
.bas,.cls, and.frmfiles exported from the VBE- real-world VBA fixtures
- UserForm metadata
- conditional compilation
- error handling
- Excel-style member access and procedure calls
- editor queries for highlights, folds, and tags
- permissive MIT licensing
Status
This is a v0.x public release.
The grammar is already usable for syntax-aware tooling such as highlighting,
folding, tags, outline extraction, and initial symbol analysis. The current test
suite covers 229 focused corpus cases, generated Select Case stress coverage
through 500 clauses, and 478 checked-in VBA example files without ERROR or
MISSING recovery nodes.
It is not yet a complete VBA grammar. Node names and tree shapes may still change before v1.0.0.
Installation
npm install tree-sitter tree-sitter-vba
Usage
const Parser = require("tree-sitter");
const VBA = require("tree-sitter-vba");
const parser = new Parser();
parser.setLanguage(VBA);
const tree = parser.parse(`
Sub Hello()
Debug.Print "Hello"
End Sub
`);
console.log(tree.rootNode.toString());
Go Usage
The Go binding is self-contained when installed through Go modules; its package directory includes the generated C parser needed by cgo.
package main
import (
"fmt"
tree_sitter "github.com/tree-sitter/go-tree-sitter"
tree_sitter_vba "github.com/harumiWeb/tree-sitter-vba/bindings/go"
)
func main() {
parser := tree_sitter.NewParser()
defer parser.Close()
parser.SetLanguage(tree_sitter.NewLanguage(tree_sitter_vba.Language()))
tree := parser.Parse([]byte("Sub Hello()\nEnd Sub\n"), nil)
defer tree.Close()
fmt.Println(tree.RootNode().ToSexp())
}
Example output:
(source_file
(sub_declaration
name: (identifier)
(parameter_list)
body: (block
(call_statement
callee: (qualified_member_expression
receiver: (identifier)
operator: "."
member: (identifier))
arguments: (unparenthesized_argument_list
(string_literal))))))
Node.js native build requirements
The npm package currently builds its native addon from source during installation. Prebuilt native binaries may be added in a future release.
A supported Python installation, a C/C++ toolchain, and the platform
requirements documented by node-gyp are required.
On Windows, Visual Studio 2022 Build Tools with the Desktop development with C++ workload is recommended.
At minimum, Windows users typically need:
- Python 3.11 or later
- Visual Studio 2022 Build Tools
- MSVC C++ x64/x86 build tools
- Windows 10 SDK or Windows 11 SDK
If multiple Python installations are present, configure npm to use the intended Python executable:
npm config set python "C:\Users\<you>\AppData\Local\Programs\Python\Python312\python.exe"
For reliable native builds on Windows, run installation from:
x64 Native Tools Command Prompt for VS 2022
Supported syntax
The grammar currently supports:
- apostrophe comments and
Remcomments - string, integer, floating-point, boolean, date,
Nothing,Null, andEmptyliterals - decimal and hexadecimal literals with common VBA type characters, including
Currency (
@) and LongLong (^), exponent notation such as1E-3, and abbreviated decimal forms such as.5and1. - identifiers with common VBA type-declaration characters, including
@and^ - identifiers, simple type clauses, dotted type names, and array type suffixes
AttributestatementsOption Explicit,Option Private Module,Option Compare, andOption BaseImplementsstatementsSub,Function, andProperty Get/Let/SetproceduresEventdeclarationsRaiseEventstatementsDim,Static,WithEvents, visibility-based variable declarations, arrays,ReDim,Erase, andConst- default type declaration statements such as
DefIntandDefStr TypeandEnumdeclarations- external
Declare FunctionandDeclare Subdeclarations, includingPtrSafe,Lib, andAlias - simple assignments and
Setassignments Name oldPath As newPathfile rename statements- calls, named arguments, omitted arguments, call-site
ByVal, member access, bang member access, and leading-dot member access ? exprDebug.Print shorthand statements, including comma- and semicolon-separated output argumentsNewexpressions andAs Newdeclarations- fixed-length string declarations
AddressOfexpressions- common VBA operator precedence for arithmetic, concatenation, comparison, and
logical operators;
=,<>,<,<=,>,>=,Is, andLikecomparisons are represented ascomparison_expression - block
If, including colon-separated multi-statement single-line branches;Select Case; and nested single-lineFor,For Each,Do,While/Wend, andWith Nextcounter lists such asNext iandNext j, iOn Error, computedOn ... GoTo/GoSub,Resume,GoTo, labels, standaloneEnd, andExitstatements- common file I/O statements:
Open,Input #,Line Input #,Print #,Close,Get #,Put #,Lock,Unlock,Seek, andReset, including commonAccess ... Sharedlocking clauses - simple runtime statements:
Stop,Beep,Load, andUnload - Access report
Linedrawing calls that use coordinate ranges such asMe.Line (x, y)-(x2, y2) TypeOf ... Is ...checks, including dotted type names such asAccess.Line- numeric line labels, numbered statements, and numbered control-flow delimiters
- conditional compilation with
#Const,#If,#ElseIf,#Else, and#End If, including statement branches inside procedures, members insideTypeandEnumdeclarations, and alternative procedure headers - line continuations, including trailing whitespace, and colon-separated
statements and
Enummembers - minimal
.frmand.clsexport metadata, includingVERSION,Begin ... End,BeginProperty ... EndProperty, GUID form blocks, and.frxblob references - initial
highlights.scm,folds.scm, andtags.scmqueries
Declaration node API
This release line is still pre-1.0.0, and declaration node shapes may change
when doing so makes syntactic metadata more directly available to downstream
tools.
Declaration consumers should prefer these structural nodes and fields over source-text scanning:
property_get_declaration,property_let_declaration, andproperty_set_declarationdistinguish property accessors directly.declare_sub_statementanddeclare_function_statementdistinguish external declaration kind directly.- declaration headers expose stable fields such as
visibility,name,parameters,type,library,alias,ptrsafe_modifier,body, andendwhere applicable. Procedure terminators are exposed throughend_sub_statement,end_function_statement, andend_property_statementnodes. - procedure declarations expose additional procedure modifiers through a
modifiersfield, separate fromvisibility. - variable, constant, type-member, and parameter declarations expose stable
name,bounds,type,initializer,passing_mode,optional_modifier,paramarray_modifier, anddefault_valuefields where syntactically valid. with_events_modifier,static_modifier,byval_modifier,byref_modifier,optional_modifier,paramarray_modifier, andptrsafe_modifierare explicit modifier nodes.implements_statementexposes its target asname, andattribute_statementexposesnameandvalue.
Expression node API
Member access and calls expose stable fields for analysis tools:
qualified_member_expressionusesreceiver,operator, andmember.implicit_member_expressionusesoperatorandmember; it has noreceiver, matching leading-dot and leading-bang VBA syntax.call_expressionusesfunctionandarguments, whereargumentsis anargument_list.call_statementusescalleeand, when arguments are present,arguments. Parenthesized arguments useargument_list; statement-style arguments useunparenthesized_argument_list.
Control-flow node API
for_statementandfor_each_statementexpose optional terminator counters throughnext_variables: next_variable_list;next_variableis no longer emitted.next_variable_listalways contains the ordered identifiers fromNext, whether there is one counter or several.- A colon-separated single-line
Ifbranch with multiple statements is aninline_statement_sequence; one-statement branches keep their direct child. shared_next_for_bodyrepresents an enclosing loop whose nested loop is closed by the sameNext ...counter list.
Known limitations
This grammar parses VBA syntax only.
It does not currently provide:
- type checking
- semantic analysis
- Excel Object Model or COM reference knowledge
- validation of identifier, member, type, procedure, workbook, or reference existence
- validation of context-sensitive statement placement
- a formatter
- an LSP server
- complete coverage of every VBA expression edge case
- semantic interpretation of
.frmdesigner metadata
For example, invalid placement of Exit For, unresolved procedure calls, invalid
Excel object members, or missing workbook references are intentionally left to
downstream tools.
General expression-level = comparison is still context-limited to avoid
ambiguity with assignment.
Queries
This package includes initial Tree-sitter queries for:
queries/highlights.scm
queries/folds.scm
queries/tags.scm
These queries are intended as a starting point for editor integrations and tooling. They may evolve as the grammar stabilizes.
Development
Install dependencies:
pnpm install
Generate the parser:
pnpm generate
After grammar changes, keep the Go module artifact in sync:
cp src/parser.c bindings/go/parser.c
pnpm check:go-parser
Run corpus tests:
pnpm test
Parse example files:
pnpm parse:examples
This recursively parses the checked-in VBA examples and fails if any parse tree
contains an ERROR or MISSING node.
Run queries against the example files:
pnpm query:examples
This validates that highlights.scm, folds.scm, and tags.scm can run
against the checked-in examples.
Run the coarse parser benchmark:
pnpm bench
This reports file counts, total bytes, parse time, node counts, and
ERROR/MISSING counts for the checked-in examples. It is intended to catch
large regressions, not to provide a strict microbenchmark.
Run the full local check:
pnpm check
Build and test the standalone browser parser:
pnpm build:wasm
pnpm test:wasm
Compare the native and browser parser targets on representative corpus and exported-module fixtures:
pnpm test:parity
The parity suite checks root node types, ERROR / MISSING recovery, and
normalized syntax-tree structure. It uses the standalone Wasm parser through
web-tree-sitter; the browser consumer smoke test separately covers loading
and parsing in Chromium.
The complete browser artifact, release, versioning, and loading contract is
documented in docs/specs/wasm-artifact.md.
In brief, versioned releases attach tree-sitter-vba.wasm and its SHA-256
checksum to the matching GitHub Release. The artifact is not committed or
included in the npm package. A browser consumer uses the matching
web-tree-sitter@0.26.9 runtime:
import { Language, Parser } from "web-tree-sitter";
await Parser.init({ locateFile: () => "/assets/web-tree-sitter.wasm" });
const language = await Language.load("/assets/tree-sitter-vba.wasm");
const parser = new Parser();
parser.setLanguage(language);
const tree = parser.parse("Sub Hello()\nEnd Sub\n");
try {
if (!tree) throw new Error("Parser did not return a syntax tree");
console.log(tree.rootNode.toString());
} finally {
tree?.delete();
parser.delete();
}
For a complete minimal static example, including local WebAssembly runtime
assets and ERROR / MISSING reporting, see
examples/browser-consumer.
Testing
Tree-sitter grammar behavior is tested with corpus files under:
test/corpus/
Community-reported production syntax regressions are kept permanently under
test/corpus/regressions/; each fixture records whether it was already
supported or required a grammar change.
When changing grammar.js, always add or update focused corpus tests. Do not
weaken existing expectations just to make a grammar change pass.
The repository also includes real-world exported VBA examples. These examples are parsed in CI to catch regressions against practical Excel/VBA code.
Broken or incomplete examples can be kept under:
examples/broken/
These fixtures are intentionally excluded from pnpm parse:examples. Add
focused recovery expectations under test/corpus/recovery.txt when the
surrounding tree shape should remain stable.
Design principles
This repository parses VBA syntax only.
It does not validate whether identifiers, types, members, procedures, workbook
objects, or references are semantically valid. Those concerns belong in
downstream tools such as xlflow, xlflow-lsp, editor extensions, or other
analysis tools.
Node names should remain stable once introduced because downstream query files
and integrations may depend on them. However, because this is a v0.x release,
node names and tree shapes may still change before v1.0.0.
Versioning
Recommended interpretation of the current release line:
0.1.x: parser coverage fixes, query fixes, and non-breaking improvements0.2.x: expanded real-world VBA coverage; tree shapes may still evolve0.x.0: notable grammar expansion or tree-shape changes1.0.0: node names and tree shapes are considered stable for downstream use
Related projects
This grammar is intended to support practical VBA tooling, including the harumiWeb/xlflow ecosystem for AI-assisted Excel/VBA development.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Third-party examples
The examples/third_party directory may contain third-party fixture files under
their original licenses. These files are provided for parser coverage only and
are not licensed under this repository's MIT license unless explicitly stated.
See THIRD_PARTY_LICENSE.md for the project inventory,
upstream sources, acknowledgements, and local license records.