LSP Lint Rule Authoring Guide¶
This guide explains how to add, test, and ship lint rules for dsdld.
For baseline rule catalog and suppression schema, see docs/reference/lsp/lint-rules.md.
1. Architecture¶
Primary interfaces:
include/llvmdsdl/LSP/Lint.hlib/LSP/Lint.cpptest/unit/LspLintTests.cpp
Key types:
LintRule: one deterministic rule unit.LintFinding: one emitted finding with optional fix edits.LintRegistry: rule registration and plugin loading.LintEngine: execution and suppression filtering.
2. Rule Design Checklist¶
Before coding, define:
- Stable rule ID (for suppression/config compatibility).
- Severity (
Info,Warning,Error). - Deterministic ordering behaviour.
- Fix safety (if emitting autofix edits).
- Scope (statement-level, file-level, namespace-level).
3. Adding A Built-In Rule¶
3.1 Implement the rule class¶
Add a LintRule subclass in lib/LSP/Lint.cpp:
class ExampleRule final : public LintRule
{
public:
[[nodiscard]] std::string id() const override
{
return "example.rule_id";
}
[[nodiscard]] std::string title() const override
{
return "Example lint rule";
}
void run(const LintDocument& document, std::vector<LintFinding>& findings) const override
{
// Analyze document.ast and/or document.sourceText.
// Append deterministic findings.
}
};
3.2 Register factory¶
In registerBuiltinRules(LintRegistry&), add:
registry.registerRuleFactory([]() { return std::make_unique<ExampleRule>(); });
3.3 Keep output deterministic¶
Use stable iteration order and stable location sorting behaviour. Avoid non-deterministic containers as final emit order without sorting.
4. Autofix Rules¶
Set these on a finding when fix is available:
finding.hasFix = truefinding.preferredFix = truewhen this should be first quick-fix choice- Populate
finding.fixeswithLintFixEditentries
Each edit is (line, character, length, newText) in zero-based coordinates.
Guidelines:
- Emit minimal edits.
- Avoid overlapping edits in one finding.
- Keep fix idempotent where practical.
- Do not alter semantics unless rule is explicitly semantic.
5. Suppression Compatibility¶
Your rule ID participates in all suppression surfaces:
- Workspace settings:
lint.disabledRules - Per-file settings:
lint.fileSuppressions - Source comment:
# dsdld-lint-disable: <rule-id>
Use a stable ID because users may persist suppressions in repositories.
6. Testing Requirements¶
Update or add tests in test/unit/LspLintTests.cpp:
- Rule fires on expected fixture.
- Rule does not fire on conformant fixture.
- Autofix payload (if any) matches expected edits.
- Suppression behaviour works.
- Determinism check remains stable.
If diagnostics goldens are affected, update:
test/unit/lint/golden/lint_fixture_diagnostics.golden
Run:
cmake --build build/matrix/dev-homebrew --config RelWithDebInfo --target llvmdsdl-unit-tests
build/matrix/dev-homebrew/test/unit/RelWithDebInfo/llvmdsdl-unit-tests
7. Plugin Rule Packs¶
LintRegistry supports dynamic rule loading. Plugin library must export:
extern "C" void llvmdsdlRegisterLintRules(llvmdsdl::lsp::LintRegistry& registry);
Inside that function, register one or more factories.
Runtime configuration path:
- Set
lint.pluginLibrariesinworkspace/didChangeConfiguration. LintEngineloads the libraries during construction.
Current implementation uses dlopen/dlsym (POSIX dynamic loading).
8. Documentation Requirements For New Rules¶
When adding/removing/changing rules:
- Update
docs/reference/lsp/lint-rules.mdbaseline list and behaviour notes. - Mention autofix behaviour if applicable.
- Add migration notes if rule IDs changed.
9. Review Gate¶
A lint-rule change is ready when:
- Unit tests pass.
- Diagnostics remain deterministic.
- Suppression surfaces behave as documented.
- Rule documentation is updated.