v0.19: Add lsp, fmt, and lint tools #1514
No reviewers
Labels
No labels
UX
active development
backlog
blocker
bootstrap
bounty
bug
dependencies
discussion
documentation
duplicate
enhancement
flaky test
help wanted
invalid
javascript
question
release
tendentious
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
mighty-gerbils/gerbil!1514
Loading…
Reference in a new issue
No description provided.
Delete branch "jay/gerbil:v0.19-lsp-fmt-lint"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Three new tools ship with Gerbil and share one analysis engine. That engine uses Gerbil's own expander and compiler, not heuristics over the source text.
gerbil lspgxlspgerbil fmtgxfmtgerbil lintgxlintgxlsp
gerbil.pkg). Macros and imports resolve exactly asgxcresolves them.gerbil lintreports. Compiler errors arrive about 1 s after typing stops, from a low-priority worker, so typing stays fast.analysisTimeoutandindexWorkspace.gxfmt
gxfmt check|fix PATH..., and stdin through-or--stdin-filename PATH..gxfmt: per-directory indentation rules for your own forms, e.g.(my-def defun),((with-tx) 2). Rules areN,defun,blockorcall.; gxfmt: off/; gxfmt: on: regions left byte-for-byte unchanged.:gerbil/tools/gxfmt/formatterexportsformat-sourceandformat-source-lines.:gerbil/tools/gxfmt/rulesexportsload-indent-rulesanddefault-indent-rules.gxlint
read-error,expansion-error: always on.compile-error: the errorsgxc -Owould raise, such as arity or type mismatches and unknown keywords or slots.unused-import,unused-definition,unused-local,set-imported,unused-suppression: on by default.unused-parameter: off by default.; gxlint: ignore ID,ignore-line,ignore-file..gxlint: per-directoryenable,disable,severity,ignore GLOB,exclude GLOB,side-effect-modules,features.--format json, with--fail-level,-jand--timeout. Exit status is 0 when clean, 1 for findings, 2 for errors.Other changes
src/tools/analysis/: the shared engine, with expansion, symbol facts, the project index and the worker pool.etc/gerbil-mode.el: Emacs indentation now matches gxfmt. It reads.gxfmtand honorsgxfmt: offregions. It also reads here-strings and#u8(correctly and indents with spaces only. A test checks that its rule table matches gxfmt's.scheme-mode's global indentation. As a result,(put 'x 'scheme-indent-function n)no longer affects gerbil-mode. Use.gxfmtorgerbil-put-indentinstead.src/.gxfmt: indentation rules for Gerbil's own macros.src/tools/{gxlsp,gxfmt,gxlint}.md,doc/reference/dev/bach.md.Compared with existing work
readplus matching on form names (def,defstruct…); symbols found by text around the cursorincludes resolve as ingxcgxc -Son the saved filegerbil lintpretty-print, which drops commentsgerbil lsp,gerbil fmtandgerbil lintgerbil lintandgerbil fmt.Notes
gxc, analysis runs the user's macros. They run in worker processes, never in the server, and nothing is written to disk../build.sh test tools/analysis/... tools/gxlint/... tools/gxlsp/... tools/gxfmt/...gives 25 modules and 279 cases. Also tested end to end in Emacs 30 with Eglot.this will probably need some changes with typed-gerbil, better to merge it before.
it will take me a little while to review this, it is very large.
astra is reviewing.
Request changes on
19a0a675db1c04d82e8b7a2254758d85633ef393. The reusable analysis engine belongs in stdlib; that should be a merge requirement alongside fixing the correctness problems below.[P1] Rename can silently change program behavior—and its verifier approves it.
index.ss:419 records an occurrence only when its source spelling matches the symbol name. That drops pipe-quoted identifiers. The fallback scan repeats the same mistake in tokens.ss:65.
Reproduced with:
Renaming the inner
xtoyproduces:fnow returns1instead of2. I exercised the rename handler and verification callback: verification returned(verified)and accepted the edit. Re-expanding does not establish safety when verification uses the same incomplete occurrence index. Preserve reader-normalized identifier identity and account for every affected reference; refuse rename when coverage cannot be established.[P1] Formatting destroys an unrelated file occupying its temporary filename.
gxfmt.ss:232 uses the predictable sibling
.NAME.gxfmt-tmp, opens it withtruncate: #t, then renames it over the source. I reproduced this with a pre-existing.source.ss.gxfmt-tmp: its contents were overwritten and the file disappeared.Concurrent invocations also collide. Use an exclusively created temporary file in the destination directory. The stdlib already provides this machinery in :std/io/tempfile.
[P2] Glob exclusions silently skip unintended files.
config.ss:154 discards the slash following
**and tries the remainder at every character position. Consequently:Both should be false. This affects exclusions and diagnostic suppression.
**/must consume complete directory components, including zero components.[P2] Signature parameter offsets violate the protocol.
signature-help.ss:76 uses the negotiated document encoding for signature-label offsets. Those offsets are specifically UTF-16. For
(λ x)with UTF-8 negotiated, I reproduced[4,5]forx; the required range is[3,4].It also always sends offset pairs without checking
labelOffsetSupport. Use UTF-16 offsets when supported and substring labels otherwise. Both requirements are described in the official signature-help specification.[P2] Deferred analysis has no global worker limit.
scheduler.ss:791 creates a deferred worker per environment, and scheduler.ss:883 starts them outside
max-live-workers. Opening documents across enough environments can therefore start arbitrarily many processes. The PR itself estimates 400–800 MB per worker. Lower process priority does not bound memory use. Deferred work needs a bounded queue and an explicit concurrency budget.The structural changes I would require are:
Put the reusable engine in stdlib with a usable library API. Source positions, lexical analysis, expansion/indexing, binding facts, formatting, lint checks and rename verification should expose typed results independently of CLI or LSP transport. Keep command dispatch, protocol encoding, subprocess hosting and editor scheduling in tools. Currently even
analyze-bufferis housed in the worker module and returns its wire representation.Remove the hand-maintained expander fork. expander-mirror.ss:3 explicitly documents 532 lines of copied expander behavior that must track core changes. It also replaces the global reader procedure to implement source overlays. Expose buffer/source-provider and diagnostic-collection hooks in the expander. Moving this file unchanged into stdlib would preserve the maintenance problem and introduce unsafe concurrency assumptions into a library API.
Consolidate the scanners. Formatter tokenization, cursor context, navigation, lexical-text detection and suppression handling each implement overlapping lexical rules. This already produces inconsistent behavior: I reproduced the cursor scanner reporting
codeinside a here-string, while the formatter lexer explicitly handles here-strings. A shared tolerant lexer should provide token spans and lexical state.Use shared traversal procedures or compiler visitor machinery. Indexing, definition collection, binding classification and parameter collection each dispatch over expanded core forms. Centralize traversal and phase handling, with operation-specific callbacks or methods. The compiler already has the
defcompile-methodframework. Surface syntax recognition also needs consolidation; separate lists of recognized binding forms will drift as macros evolve.Generate repetitive wire codecs from declarations. payload.ss manually maintains structures, exports, serializers and matching deserializers. A small schema macro can generate the mechanical parts while retaining explicit code for compression and special fields. This is a concrete opportunity for macros to remove duplicated knowledge.
There is also straightforward stdlib duplication:
:std/struct/lru:std/string/path—path-simplify,subpath?:std/cli/getopt, already used by the formatter:std/logstring-suffix?and substring searchThe formatter and linter also duplicate their stdin-reading loops, and several components independently traverse source directories. Shared procedures with explicit policy arguments would remove that repetition.
Validation: 13 test modules passed using the installed Gerbil runtime with the PR's sources loaded. The broader run encountered two source-path expectation failures and subsequently terminated with exit 137; I cannot claim a complete suite pass. The rename, temporary-file collision, glob, signature-offset and here-string findings were separately reproduced. The rename reproduction used the real handler and verifier with an in-process stand-in for worker transport. No repository source files were changed.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.