Skip to content

xlflow lint

Lint VBA source files for agent-hostile and compile-dialog-prone patterns.

Usage

bash
xlflow lint

Options and Arguments

Option / argumentDescriptionDefault
--jsonReturn structured lint issues.false

Examples

bash
xlflow lint
xlflow lint --json

Notes

IMPORTANT

Syntax-safety checks are always enabled for patterns that could surface as modal VBE compile dialogs.

TIP

Use lint --json in agent loops before push to catch source problems while Excel is still closed.

Rules

The generated static-analysis diagnostic catalog is the authoritative reference for rule metadata, including configuration, default state, scope, precision, preflight behavior, and inline suppression. The summary below explains the lint findings in workflow terms.

CodeSeverityDescription
VB001warningMissing Option Explicit.
VB002warningSelect member access such as Range("A1").Select.
VB003warningActivate member access such as ActiveCell.Activate.
VB004warningBroad On Error Resume Next.
VB005warningPossible implicit Variant, including individual untyped declarators in one Dim statement.
VB006warningModule-level Public variable.
VB007warningAutomation-hostile GUI boundary such as raw dialogs, file pickers, UserForms, message pumps, or external process launches.
VB008errorTypographic quote character that can trigger VBE compile dialogs.
VB009errorLikely C-style quote escape in a VBA string literal.
VB010errorUnterminated Sub, Function, or Property procedure.
VB011errorUnexpected End Sub, End Function, or End Property.
VB012errorMismatched procedure end statement.
VB013errorMissing whitespace before a line-continuation underscore.
VB014errortree-sitter-vba recovered with an ERROR or MISSING node; this is a parser-compatibility signal, not proof that VBA is invalid.
VB015errorA VBA logical line uses more than 24 line-continuation characters.
VB018warningLocal declarations or parameters shadow module-level names, procedure names, or same-scope declarations.
VB019warningMultiple declarators mix typed and untyped names; in VBA each name needs its own As <Type>.
VB020warningProcedure-local variable is declared but never referenced.
VB021warningPrivate procedure is unreachable from known project roots; dynamic callbacks are treated conservatively.
VB022warningConfusing parenthesized call syntax such as Foo (bar).
VB023warningFor Each control variable is undeclared or obviously incompatible.
VB026warningResume is used outside a likely error-handler context.
VB027warningNested With blocks use implicit Excel members whose target can be ambiguous.
VB028errorBare MsgBox or InputBox appears while XlflowUI.bas is present; use XlflowUI or explicit VBA.Interaction.
VB029errorOption Explicit is present and an assignment target or loop control variable is not declared.
VB031errorStandard .bas module is missing Attribute VB_Name.
VB032errorRepeated ? Debug.Print shorthand such as ?? "hoge".
VB037errorDefinite scalar assignment incorrectly uses the Set keyword; blocks source preflight.
VB044warningConfigured local procedure-name string constant does not match its enclosing procedure name.
VB045errorDeterministic argument-count or named-argument binding error; blocks source preflight.
VB046errorDuplicate declaration in the same module, procedure, Enum, or Type scope; blocks source preflight.
VB047errorDeclaration appears in an invalid module/procedure position; blocks source preflight.
VB048errorInvalid procedure parameter declaration; blocks source preflight.
VB049errorInconsistent Property Get/Let/Set accessor contract; blocks source preflight.
VB050errorDeclaration is invalid for the canonical module kind or has an invalid WithEvents/public-member shape; blocks source preflight.
VB051errorMe is used in a standard module; blocks source preflight.
VB052errorProject-local call target is provably missing or known non-callable; blocks source preflight.
VB053errorBare Enum member has multiple visible project candidates with no lexical winner; blocks source preflight.
VB054errorRaiseEvent target is undeclared in the same object module; blocks source preflight.
VB059errorInvalid explicit/standalone call parentheses, Function-expression call parentheses, or explicit Call target; blocks source preflight.
VB060errorAssignment to a Const value; blocks source preflight.
VB061errorFixed array declaration has a constant lower bound greater than its upper bound; blocks source preflight.
VB062errorConditional branch statement uses a form rejected by VBA; blocks source preflight.
VB063errorSelect Case branch is outside its block, duplicates Case Else, or follows Case Else; blocks source preflight.
VB064errorOpen statement has a provably malformed file-mode shape; blocks source preflight.
VB065errorTypeOf expression has a provably malformed syntax shape; blocks source preflight.
VB066warningVBE accepts Property Get with End Sub/End Function, but End Property is the canonical style; non-blocking and inline-suppressible.

Core declaration, member-access, error-handling, and procedure-scope checks are AST-backed. They ignore comments and strings, distinguish module-level declarations from procedure-local declarations, and report individual declarators such as a in Dim a, b As Long. VB029 also resolves public declarations from standard modules across the project, so a valid project-level assignment is not reported as undeclared.

Disable configurable lint rules with [lint].disabled_rules:

toml
[lint]
disabled_rules = ["VB002", "VB006"]

Legacy per-rule booleans such as forbid_select = false remain accepted for compatibility, but xlflow emits a deprecation warning. If both formats disagree, disabled_rules takes precedence and xlflow reports a conflict warning.

Use inline suppression comments for intentional local exceptions while keeping rules enabled globally:

vb
' xlflow:disable-next-line VB002
Range("A1").Select

Range("A2").Select ' xlflow:disable-line VB002

Multiple IDs may be listed with spaces. Unknown IDs, unsupported preflight-blocking IDs, and suppressions that no longer match a lint diagnostic are reported as warnings.

Safety diagnostics VB008 through VB015, VB028, VB029, VB031, VB032, VB037, and VB045 through VB065 are always enabled and cannot be suppressed inline because they prevent VBE compile dialogs before push or run opens Excel. VB066 is a separate accepted-form style warning and is intentionally inline-suppressible and non-blocking.

VB030 remains a warning for inferred or otherwise uncertain argument compatibility. VB045 is reserved for deterministic argument binding errors confirmed by the VBE contract.

VB046 compares declaration names case-insensitively within their containing module, procedure, Enum, or user-defined Type. Property accessor groups are allowed when valid; repeated accessors and Property/non-Property collisions are errors. Repeated Option Explicit, Option Base, Option Compare, and Option Private Module directives are also errors. VB047 reports Option statements or declarations in positions VBA does not permit while allowing procedure-local Dim, Static, and Const; class-module Implements clauses may precede Option directives. Conditional-compilation branches are compared only when their coexistence can be proven.

VB048 validates procedure parameter declarations against compile-time VBA contracts, including optional/required ordering, ParamArray, array shape, resolved UDT passing, parameter limits, and definitely invalid optional defaults. Unknown or ambiguous type-dependent checks remain fail-open. VB049 validates the shared index/value parameter contract and return/value types of Property Get/Let/Set accessors; unresolved type compatibility remains fail-open. Both rules are unsuppressible errors and block source preflight. VB050 validates Event/Friend/Implements/WithEvents and object-module public-member placement using canonical module metadata; unknown external type information remains fail-open. VB051 reports only an AST Me token in a standard module.

VB052 reports only when the canonical project resolver proves that a call target is local and either missing or a known non-callable declaration. Bare names that may bind to external libraries, built-ins, late-bound receivers, dynamic invocation (Application.Run / CallByName), or incomplete snapshots remain quiet. VB053 reports an unqualified Enum member only when multiple complete project candidates remain ambiguous; duplicate TypeLib records for a globally exposed constant are an external fallback and remain fail-open. Qualification, a unique lexical winner, and incomplete TypeLib/project state are also fail-open. VB054 reports an undeclared RaiseEvent identifier only against the complete event declarations of its containing object module. These three diagnostics are unsuppressible errors, block source preflight, and use the same resolver snapshot as analyze and LSP Full diagnostics.

VB059 uses the CST and procedure context to separate compile-invalid call forms from VB022's legal-but-confusing style warning. It reports missing parentheses after explicit Call, empty or multi-argument parentheses on a standalone call, and a Function used in an expression without required parentheses. The parenthesized ByRef/ByVal idiom (Name (arg1), (arg2)) and other accepted unusual forms remain quiet; parser recovery without a matching call context is not evidence for this rule.

VB062 reports only conditional branch shapes that are provably rejected by VBA; ambiguous If/ElseIf/Else recovery remains the generic VB014 diagnostic. VB063 reports Case outside Select Case, duplicate Case Else, and a Case after Case Else while leaving malformed/recovered blocks to VB014. VB064 reports a missing or malformed Open ... For <mode> shape only when the parser identifies the broken mode node. VB065 reports only provably malformed TypeOf expression shapes. All four rules are unsuppressible, preflight-blocking errors; parser ambiguity remains fail-closed under VB014 without speculative classification.

VB014 is fail-closed for push and run, but parser recovery alone does not prove that Excel will reject the VBA. Its JSON issue may include parser_node (ERROR or MISSING), parser_token, and a short source-line context; when xlflow can confidently match an unclosed multiline block, it also includes block_kind, expected_closer, opening_line, and opening_column. In that case the diagnostic location marks where the closer is expected and the message identifies the opener, for example Possible missing 'End If' for multiline If block opened at line 8. When a parent closer is aligned exactly with its outer opener and all skipped nested openers are indented further, that parent closer is highlighted for the inner missing terminator. Conditional compilation and other ambiguous structures keep the generic recovery diagnostic. Inspect that context and validate the source in the target host before changing otherwise-valid VBA merely to satisfy parser compatibility.

Rules VB019, VB020, VB022, VB023, and VB026 are enabled by default. VB023 validates the For Each control variable; iterable-source validation is shared with array-sensitive analysis, accepts known dynamic arrays, and remains fail-open for unknown, external, and unresolved Variant sources. Disable VB020 with disabled_rules = ["VB020"] when a project intentionally keeps scratch locals. Heavier project-wide rules such as detect_unused_private_procedures = true (VB021) stay conservative opt-ins; new xlflow.toml files include commented examples. Use analyze for semantic runtime-risk checks such as unqualified Excel access, error-handler fallthrough, Application state leaks, Range.Find Nothing guards, and object Nothing guards combined with dereferences in non-short-circuit boolean expressions.

To keep runtime-error diagnostics useful after procedure renames, opt into VB044 with a local constant convention:

toml
[lint.procedure_name_constant]
enabled = true
constant_name = "PROCEDURE_NAME"

The rule checks existing direct string literals only; it never inserts a missing constant or rewrites source during xlflow lint. It supports Sub, Function, and all Property procedures in standard, class, document, and UserForm modules. The LSP offers a Quick Fix that updates only the mismatched string literal.

JSON Output Example

Failed --json output uses the xlflow envelope plus command-specific fields.

json
{
  "status": "failed",
  "command": "lint",
  "error": {
    "code": "lint_failed",
    "message": "1 lint issue(s) found"
  },
  "logs": [],
  "issues": [
    {
      "code": "VB005",
      "severity": "warning",
      "file": "src/modules/Main.bas",
      "line": 7,
      "column": 7,
      "message": "Declare an explicit type with As <Type>."
    }
  ]
}

For VB014, the optional recovery metadata identifies the first concrete recovery node:

json
{
  "code": "VB014",
  "severity": "error",
  "file": "src/modules/Main.bas",
  "line": 7,
  "column": 12,
  "kind": "parser_recovery",
  "parser_node": "MISSING",
  "parser_token": "End Sub",
  "context": "Public Sub Main()",
  "message": "VBA parser recovery detected; inspect the reported source context before pushing to Excel."
}

When to use this command

Use xlflow lint when the task matches the command description above. For a goal-oriented workflow, start with the How-to guides and return here for exact options.

Prerequisites

Check the project configuration and run xlflow doctor --json before workbook-backed operations. Source-only commands can run without Excel; commands that read or mutate a workbook require Windows Excel and VBIDE access.

What this command reads and changes

The command reads the inputs and configuration described in its syntax and examples. Treat source files, the saved workbook, and a live session as separate states; add --session when the live workbook is authoritative. Any mutation is reversible only when a backup or explicit session save boundary exists.

Effect on source-of-truth state

Use xlflow status --json before and after the command. A source edit normally requires push; a workbook edit normally requires pull; a dirty live session requires save --session or an intentional discard.

Common workflows

Combine this command with the relevant source/workbook/session workflow, and use --json in scripts and agent loops.

Common failures

Read the structured error.code, exit code, and recovery metadata instead of scraping terminal text. The symptom-oriented troubleshooting guide maps installation, execution, session, VS Code, and WSL failures to recovery steps.

Released under the MIT License.