The gap
aspen can format a file and it can analyse a project, but there is no way to express a finding that will not be fixed automatically.
vb6format is fix-only. FormatPass can rewrite tokens and nothing else, and fmt_source returns a String. A pass has no channel to say "this is wrong and I am deliberately not touching it".
aspen check is project-only. It needs a .vbp, runs the full semantic analysis, and stops at the first error.
That leaves a class of checks with nowhere to live: cheap, per-file, and not safely auto-fixable. The one that brought me here is non-ASCII characters in identifiers. VB6 accepts Public Function Añadir(), and a code base written in Spanish, French or German is full of them. They are a real portability problem — almost nothing downstream of VB6 accepts them — but renaming an identifier is a judgment call about a public API, not something a formatter should do behind your back. Today there is no way to even report it.
Proposal: follow ruff's split
ruff solved this exact problem for Python and the vocabulary is already familiar to most people, which is the main reason to borrow it rather than invent something:
aspen fmt stays what it is: deterministic layout, no opinions, always applied. The equivalent of ruff format.
aspen check grows a per-file lint layer over the CST, selectable by rule. The equivalent of ruff check.
Concretely:
A rule registry. Each rule carries a stable code, a name, a category and a fixability:
|
|
| code |
stable, e.g. N001, referenced in config and in noqa-style suppressions |
| fixability |
Safe, Unsafe, None — ruff's distinction, and the piece that is missing today |
| default |
on or off; portability rules like the one above are off by default |
Diagnostics. vb6parse::errors already has Severity, spans, labels, notes and pretty printing through ariadne. A finding is that plus a rule code, so lint output and parse errors look the same to the user.
Config. .aspen.toml already exists and is already parsed with serde. Add the selection to it:
[format]
indent-size = 4
[lint]
select = ["W", "N001"]
ignore = ["W002"]
CLI. --select / --ignore / --fix / --diff / --statistics, and ruff's exit codes: 0 clean, 1 findings, 2 the run itself failed.
Seed rules, enough to prove the shape:
- non-ASCII character in an identifier — no fix
- mixed line endings in one file — safe fix
- trailing whitespace — safe fix
Why this is worth doing
Two of these checks are the difference between a tool you run by hand and a tool you can put in CI or a pre-commit hook. Per-file and parse-only means it stays fast enough for the latter, which the project-level check cannot be.
I am happy to implement it. Opening this first rather than arriving with the diff, because it adds a public API to vb6format/vb6parse and the shape should be yours to decide — in particular whether the lint layer belongs in a new crate, inside vb6format, or inside vb6semantic.
Related: the three fixes in #2 are prerequisites in practice — until fmt can read Windows-1252 files and exits non-zero on failure, no gate built on it means anything.
The gap
aspencan format a file and it can analyse a project, but there is no way to express a finding that will not be fixed automatically.vb6formatis fix-only.FormatPasscan rewrite tokens and nothing else, andfmt_sourcereturns aString. A pass has no channel to say "this is wrong and I am deliberately not touching it".aspen checkis project-only. It needs a.vbp, runs the full semantic analysis, and stops at the first error.That leaves a class of checks with nowhere to live: cheap, per-file, and not safely auto-fixable. The one that brought me here is non-ASCII characters in identifiers. VB6 accepts
Public Function Añadir(), and a code base written in Spanish, French or German is full of them. They are a real portability problem — almost nothing downstream of VB6 accepts them — but renaming an identifier is a judgment call about a public API, not something a formatter should do behind your back. Today there is no way to even report it.Proposal: follow ruff's split
ruffsolved this exact problem for Python and the vocabulary is already familiar to most people, which is the main reason to borrow it rather than invent something:aspen fmtstays what it is: deterministic layout, no opinions, always applied. The equivalent ofruff format.aspen checkgrows a per-file lint layer over the CST, selectable by rule. The equivalent ofruff check.Concretely:
A rule registry. Each rule carries a stable code, a name, a category and a fixability:
N001, referenced in config and innoqa-style suppressionsSafe,Unsafe,None— ruff's distinction, and the piece that is missing todayDiagnostics.
vb6parse::errorsalready hasSeverity, spans, labels, notes and pretty printing through ariadne. A finding is that plus a rule code, so lint output and parse errors look the same to the user.Config.
.aspen.tomlalready exists and is already parsed with serde. Add the selection to it:CLI.
--select/--ignore/--fix/--diff/--statistics, and ruff's exit codes:0clean,1findings,2the run itself failed.Seed rules, enough to prove the shape:
Why this is worth doing
Two of these checks are the difference between a tool you run by hand and a tool you can put in CI or a pre-commit hook. Per-file and parse-only means it stays fast enough for the latter, which the project-level
checkcannot be.I am happy to implement it. Opening this first rather than arriving with the diff, because it adds a public API to
vb6format/vb6parseand the shape should be yours to decide — in particular whether the lint layer belongs in a new crate, insidevb6format, or insidevb6semantic.Related: the three fixes in #2 are prerequisites in practice — until
fmtcan read Windows-1252 files and exits non-zero on failure, no gate built on it means anything.