Contributing¶
All contributions are welcome! Besides code contributions, this includes things like documentation improvements, bug reports, and feature requests.
You should first check if there is a GitHub issue already open or related to what you would like to contribute. If there is, please comment on that issue to let others know you are working on it. If there is not, please open a new issue to discuss your contribution.
Not all contributions need to start with an issue, such as typo fixes in documentation or version bumps to Python or Django that require no internal code changes, but generally, it is a good idea to open an issue first.
We adhere to a version of Django's Code of Conduct in all interactions and expect all contributors to do the same. Please read the Code of Conduct before contributing.
Development¶
For a detailed look at how the codebase works — data flow, the Salsa database, the template pipeline — see ARCHITECTURE.md.
The project is written in Rust with a Python subprocess for Django introspection. It uses a Cargo workspace with all crates under crates/. A few conventions to be aware of:
- Dependency versions are centralized in
[workspace.dependencies]in the rootCargo.toml. Individual crates reference them withdep.workspace = trueand never specify versions directly. - Internal crates are listed before third-party crates in each crate's
[dependencies], separated by a blank line. Both groups are kept in alphabetical order. - Lints are configured once in
[workspace.lints]in the rootCargo.toml. Each crate opts in with[lints] workspace = true. - Versioning: Only the
djlsbinary crate carries the release version. All library crates useversion = "0.0.0".
Code contributions are welcome from developers of all backgrounds. Rust expertise is valuable for the LSP server and core components, but Python and Django developers should not be deterred by the Rust codebase — Django expertise is just as valuable. Understanding Django's internals and common development patterns helps inform what features would be most valuable.
So far it's all been built by a simple country CRUD web developer learning Rust along the way — send help!
AI Policy¶
Someone is going to read your PR. Be considerate of that — make sure what you're submitting is something you'd want to review yourself.
AI tools are fine to use. How the code got written matters less than whether it's good. But you're the one submitting it, so you're the one responsible for it. If you can't explain a change, don't submit it. If you haven't tested it, don't submit it. If it doesn't fit the codebase, it's going to need rework.
Mentioning that you used AI is appreciated but not required. We'll assume good faith. That said, a pattern of sloppy submissions speaks for itself regardless of how the code was produced.
- If you submit it, you own it. "The AI wrote it" is not an explanation.
- Read the diff. Understand what it does and why.
- Test your work. Don't submit code you haven't verified.
- Make sure it fits — existing patterns, naming conventions, architecture.
The project includes an AGENTS.md file with guidelines for AI coding agents. If you're using an AI tool that supports it, point it there.
Before opening a PR, make sure the tests, clippy, formatting, and linting all pass.
Changelog¶
The project maintains a CHANGELOG.md following Keep a Changelog. All notable changes should be documented under the [Unreleased] heading in the appropriate section.
Sections (github.com/joshuadavidthomas/django-language-server/tree/main/use only those that apply):
Added— new featuresChanged— changes in existing functionalityDeprecated— soon-to-be removed featuresRemoved— now removed featuresFixed— bug fixesSecurity— vulnerability fixes
Writing entries:
- Keep entries short and factual — describe what changed, not why
- Use past tense verbs: "Added", "Fixed", "Removed", "Bumped", etc.
- Wrap crate names, types, commands, and config keys in backticks
- Prefix internal changes (github.com/joshuadavidthomas/django-language-server/tree/main/refactors, crate restructuring, CI) with
**Internal**: - List user-facing entries before
**Internal**:entries within each section
Examples:
### Added
- Added `diagnostics.severity` configuration option for configuring diagnostic severity levels.
### Changed
- Bumped Rust toolchain from 1.90 to 1.91.
- **Internal**: Extracted concrete Salsa database into new `djls-db` crate.
### Fixed
- Fixed false positive errors for quoted strings with spaces (https://github.com/joshuadavidthomas/django-language-server/tree/main/e.g., `{% translate "Contact the owner" %}`).
Version Updates¶
Python¶
The project uses noxfile.py as the single source of truth for supported Python versions. The PY_VERSIONS list in this file controls:
- Auto-generated documentation: cogapp reads
PY_VERSIONSto generate Python version classifiers inpyproject.tomland the supported versions list inREADME.md - CI/CD test matrix: GitHub Actions workflows call the
gha_matrixnox session to generate the test matrix fromPY_VERSIONS, so all supported Python versions are tested automatically - Local testing: The
testsnox session usesPY_VERSIONSto parametrize test runs across all supported Python versions
Note
When possible, prefer submitting additions and removals in separate pull requests. This makes it easier to review changes and track the impact of each version update independently.
To update the list of supported Python versions:
-
Update
noxfile.py, adding or removing version constants as needed and updating thePY_VERSIONSlist accordingly.For example, to add Python 3.14 and remove Python 3.9:
-
Regenerate auto-generated content:
This updates:
- The
requires-pythonfield inpyproject.toml - Python version trove classifiers in
pyproject.toml - Supported versions list in
README.md
- The
-
Update the lock file:
-
Test the changes:
Use
just testallrather thanjust testto ensure all Python versions are tested. Thejust testcommand only runs against the default versions (github.com/joshuadavidthomas/django-language-server/tree/main/the oldest supported Python and Django LTS) and won't catch issues with newly added versions.Alternatively, you can test only a specific Python version across all Django versions by
noxdirectly: -
Update
CHANGELOG.md, adding entries for any versions added or removed.
Django¶
The project uses noxfile.py as the single source of truth for supported Django versions. The DJ_VERSIONS list in this file controls:
- Auto-generated documentation: cogapp reads
DJ_VERSIONSto generate Django version classifiers inpyproject.tomland the supported versions list inREADME.md - CI/CD test matrix: GitHub Actions workflows call the
gha_matrixnox session to generate the test matrix fromDJ_VERSIONS, so all supported Django versions are tested automatically - Local testing: The
testsnox session usesDJ_VERSIONSto parametrize test runs across all supported Django versions
Note
When possible, prefer submitting additions and removals in separate pull requests. This makes it easier to review changes and track the impact of each version update independently.
To update the list of supported Django versions:
-
Update
noxfile.py, adding or removing version constants as needed and updating theDJ_VERSIONSlist accordingly.For example, to add Django 6.1 and remove Django 4.2:
-
Update any Python version constraints in the
should_skip()function if the new Django version has specific Python requirements. -
Regenerate auto-generated content:
This updates:
- Django version trove classifiers in
pyproject.toml - Supported versions list in
README.md - Supported versions list in
docs/installation.md
- Django version trove classifiers in
-
Update the lock file:
-
Test the changes:
Use
just testallrather thanjust testto ensure all Django versions are tested. Thejust testcommand only runs against the default versions (github.com/joshuadavidthomas/django-language-server/tree/main/the oldest supported Python and Django LTS) and won't catch issues with newly added versions.Alternatively, you can test only a specific Django version across all Python versions by using
noxdirectly: -
Update
CHANGELOG.md, adding entries for any versions added or removed. -
For major Django releases: If adding support for a new major Django version (github.com/joshuadavidthomas/django-language-server/tree/main/e.g., Django 6.0), the language server version should be bumped to match per DjangoVer versioning. For example, when adding Django 6.0 support, bump the server from v5.x.x to v6.0.0.
Profiling¶
The just dev profile command runs benchmarks under valgrind-codspeed, the same callgrind fork used in CI. It produces deterministic per-function instruction counts with call trees, and automatically strips harness overhead.
just dev profile <bench> [filter]
# Examples:
just dev profile diagnostics collect_diagnostics_realistic
just dev profile parser parse_template
Prerequisites¶
You'll need jq, rg, and the codspeed fork of valgrind (github.com/joshuadavidthomas/django-language-server/tree/main/not stock valgrind):
git clone --depth 1 https://github.com/CodSpeedHQ/valgrind-codspeed /tmp/valgrind-codspeed
cd /tmp/valgrind-codspeed
./autogen.sh
./configure --prefix=$HOME/.local
make -j$(https://github.com/joshuadavidthomas/django-language-server/tree/main/nproc)
make install
Make sure $HOME/.local/bin is on your PATH. Verify with:
Justfile¶
The repository includes a Justfile that provides all common development tasks with a consistent interface. Running just without arguments shows all available commands and their descriptions.
$ just
$ # just --list --list-submodules
Available recipes:
bumpver *ARGS
check *ARGS
clean
clippy *ARGS
corpus *ARGS
fmt *ARGS
lint *ARGS # run pre-commit on all files
run *ARGS
test *ARGS
testall *ARGS
dev:
debug # TODO: djls-tmux binary was removed in #214, this recipe needs updating
explore FILENAME="djls.db"
inspect
profile bench filter="" # Profile a bench with callgrind
record FILENAME="djls.db"
docs:
build LOCATION="site" # Build documentation
serve PORT="8000" # Serve documentation locally