For a detailed reference of the LSP code flows, use-chain resolution, variable scoping, and rename logic, see docs/architecture.md.
make build # build dexter binary
make test # run all tests
make lint # run after completing a set of changesInstall golangci-lint if needed:
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4- Our guiding principles for all code written are: We want maximum performance and speed, but without sacrificing correctness of results. It's okay to sacrifice some readability in the name of performance, but be mindful of the tradeoffs.
- When fixing reported bugs, write the regression test first, verify the failure, and then write the fix.
- When given real code causing bugs, use fake/generic module and function names in tests (e.g.
MyApp.Accounts,SharedLib.Worker) — never use real names from the user's codebase, even for regression tests - When writing code that affects the in-document runtime parsing that is similar to the index parsing (or vice versa), see if the code can be unified to reduce deduplication. But we should never do this at the cost of performance. Indexing must be kept fast no matter what.
- Keep the CLI commands (
init,reindex,lookup) working independently of the LSP server - Parser tests should cover real-world Elixir patterns from large codebases
- Version strings (
VersionandIndexVersion) live ininternal/version/version.go
Bump IndexVersion (alongside Version) whenever a parser change or schema change would make existing indexes produce
wrong results. The LSP server checks this on startup and triggers a forced rebuild on mismatch.
| Area changed | Also check |
|---|---|
parseUsingBody / use-chain logic |
lookupInUsingEntry, resolveModuleViaUseChainWithOpts, findModulesWhoseUsingImports |
resolveBareFunctionModule |
All callers pass lineNum — used by LookupFunctionInFile to prefer the enclosing module |
Rename edits (buildTextEdits) |
findFunctionTokenColumns (skips keyword keys), includeKeyword flag on import sites |
Variable scoping (variables.go) |
Pin operator handling, with multi-clause, stab body rebinds |
ExtractAliases |
LSP handlers use ExtractAliasesInScope(text, lineNum) — scope-aware. Only Completion and CodeAction use the unscoped ExtractAliases intentionally |
| Any new store query | Add an index if the query will run on hot paths (definition, hover, references) |
Use parser.TokenWalker for new code that iterates over tokens. It provides:
- Consistent depth tracking with automatic clamping (never goes negative)
- Forward progress guarantees via
EnsureProgress() - Module/function detection helpers
- Statement boundary handling
See internal/parser/token_walker.go for the API and token_walk_test.go for examples.
The consistency tests in elixir_test.go (TestModuleScopeConsistency, TestDepthTrackingConsistency) verify that similar functions handle edge cases the same way. Add new edge cases there when fixing scope-related bugs.
- Nested modules:
defmodule Inner doinsideOutercreatesOuter.Innerin the store but the raw line only saysInner. UseLookupModulesInFile/LookupEnclosingModulerather than regex-scanning lines for module names. as:aliases:alias Foo.Bar, as: Baz—Baz≠ last segment ofFoo.Bar. Detected inPrepareRenameby comparing the alias short name tomoduleLastSegment(resolved).- Import-only sites in rename: Lines like
import Module, only: [fn: 1]have the function name as a keyword key. They're flaggedincludeKeyword: truesobuildTextEditsusesfindAllTokenColumnsinstead offindFunctionTokenColumns. - Dynamic use-chain imports:
import unquote(mod)goes intooptBindings, notimports. StaticfindModulesWhoseUsingImportswon't find these — the fast opt-binding path in the References handler handles them instead. - ExUnit.CaseTemplate: Uses
using opts doinstead ofdefmacro __using__. Only recognised when the module hasuse ExUnit.CaseTemplate. The body may delegate to a helper function (using_block(opts)) —parseHelperQuoteBlockfollows this. - Performance:
findModulesWhoseUsingImportsis expensive (~30-65ms). The References handler skips it when the fast opt-binding check already found injectors.