Skip to content

Commit b5f0b4d

Browse files
Refactor djls-project by Django domain (#699)
1 parent d29ba0f commit b5f0b4d

56 files changed

Lines changed: 1333 additions & 1251 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ARCHITECTURE.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ If you're already familiar with LSP, `crates/djls-server/src/server.rs` is a goo
2727

2828
If you want to understand how templates get parsed, start in `crates/djls-templates/src/` — the lexer and a hand-written recursive descent parser live there.
2929

30-
If you're curious about how the server validates tags without a Django runtime, look at `crates/djls-project/src/specs/analysis/` — it extracts validation rules from Python templatetag source purely through static analysis. If anyone's tried this before, they and their project didn't make it out with their sanity intact because I've never come across one (and who says I'll make it out with mine?). It's early and rough, but it's one of the more interesting parts of the project.
30+
If you're curious about how the server validates tags without a Django runtime, look at `crates/djls-project/src/templates/tags/analysis/` — it extracts validation rules from Python templatetag source purely through static analysis. If anyone's tried this before, they and their project didn't make it out with their sanity intact because I've never come across one (and who says I'll make it out with mine?). It's early and rough, but it's one of the more interesting parts of the project.
3131

3232
If you're interested in how parsing, extraction, and project knowledge come together to give templates *meaning*, start with `crates/djls-semantic/src/lib.rs` — that's where "is this tag valid here?" actually gets answered.
3333

@@ -62,7 +62,7 @@ This crate should stay boring. It wires traits to concrete state and handles loc
6262

6363
### `crates/djls-project`
6464

65-
The project model. This crate owns mechanical facts about a Django project: the `Project` Salsa input, Python interpreter and search-path discovery, module resolution, Django settings extraction, template directories, template libraries, discovered template files, template origins/resolution, Python spec extraction, model graph extraction, and project refresh. It owns two tiers of source recognizers: pure recognizers in `extraction/` for Django settings values and template tag registrations, and Salsa-assisted recognizers in `specs/` for tag rules, block specs, filter arities, and Django model graphs.
65+
The project model. This crate owns mechanical facts about a Django project: the `Project` Salsa input, Python interpreter and search-path discovery, module resolution, Django settings extraction, template directories, template libraries, discovered template files, template origins/resolution, template tag/filter extraction, model graph extraction, and project refresh. Its internals are organized by Django domain: `settings/` handles settings-source extraction and source resolution, `templates/` handles template origins, directories, libraries, registrations, tag rules, filter arities, and symbols, and `models/` handles Django model graph extraction.
6666

6767
`djls-project` depends on `djls-source` for filesystem/source access, but it does not depend on `djls-semantic`. That one-way boundary lets semantic analysis consume observed source facts without project discovery needing to know about template validation, scoping, or diagnostics.
6868

@@ -100,7 +100,7 @@ This crate also owns:
100100

101101
**Architecture Invariant:** extraction currently only captures constraints on *static template syntax* — argument counts and literal keyword positions knowable at parse time. Many templatetag functions also validate *runtime values* (type checks, truthiness checks on resolved variables), but those guards depend on what template variables resolve to during rendering, which the server cannot currently determine. If type inference is added in the future ([#424](https://github.com/joshuadavidthomas/django-language-server/issues/424)), some of these runtime guards may become statically evaluable — possibly as a separate analysis layer, or as an extension of the extraction pipeline itself.
102102

103-
Project configuration, Python environment discovery, module resolution, and Python spec extraction live in `crates/djls-project/`. `Project` is a Salsa input holding the project root, interpreter path, Django settings module, resolver-owned `SearchPaths`, and manual tag-spec configuration. `djls-project` also owns the `TemplateLibraries` type that holds derived template tag library knowledge from Django settings, source files, and installed packages. Imperative refresh functions there synchronize external project state into Salsa inputs; tracked semantic queries then fuse those observed facts into validation data and other semantic facts.
103+
Project configuration, Python environment discovery, module resolution, and source-derived Django facts live in `crates/djls-project/`. `Project` is a Salsa input holding the project root, interpreter path, Django settings module, resolver-owned `SearchPaths`, and manual tag-spec configuration. `djls-project` also owns the `TemplateLibraries` type that holds derived template tag library knowledge from Django settings, source files, and installed packages. Imperative refresh functions there synchronize external project state into Salsa inputs; tracked semantic queries then fuse those observed facts into validation data and other semantic facts.
104104

105105
### `crates/djls-ide`
106106

@@ -158,7 +158,7 @@ Project refresh starts from the configured Django settings module. `djls-project
158158
1. The `Project` input stores the project root, interpreter path, Django settings module, search paths, and manual tag-spec configuration.
159159
2. Template directories come from the static settings projection and are exposed as tracked project queries.
160160
3. Template libraries come from three source-derived places: configured `OPTIONS["libraries"]`, configured/default `OPTIONS["builtins"]`, and `templatetags` packages under installed apps.
161-
4. Symbols for those libraries are collected from Python source with the registration scanner in `djls-project::extraction`; `djls-project::specs` reuses the same registration facts to extract validation rules and model graphs.
161+
4. Symbols for those libraries are collected from Python source with the registration scanner in `djls-project::templates::registrations`; `djls-project::templates::{tags,filters}` reuse the same registration facts to extract validation rules and filter arities, while `djls-project::models` extracts Django model graphs.
162162
5. Search-path roots for installed packages remain high-durability source roots, so package files are reread when refresh detects that external data changed.
163163

164164
Startup is now configuration read plus project refresh. There is no embedded inspector zipapp, no `django.setup()`, and no template-library disk cache in the server path.

CONTEXT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Source-based **Django Discovery** that derives **Project Facts** from source fil
3535
_Avoid_: introspection, runtime analysis, import-time analysis
3636

3737
**Project/Semantic Boundary**:
38-
Observed source facts and lookup facts — including Python source facts and **Template Resolution** — belong to `djls-project`; project meaning — fusion, validity, availability, diagnostics, and template-domain relationships — belongs to `djls-semantic`. Only `djls-project` parses Python source.
38+
Observed source facts and lookup facts — including Python source facts and **Template Resolution** — belong to `djls-project`; project meaning — fusion, validity, availability, diagnostics, and template-domain relationships — belongs to `djls-semantic`. Only `djls-project` parses Python source. Inside `djls-project`, code is organized by Django domain (`settings`, `templates`, `models`) rather than by generic fact/extraction buckets.
3939
_Avoid_: classifying ownership by whether a type's name sounds semantic
4040

4141
**Project Introspection**:

crates/djls-project/src/extraction.rs

Lines changed: 0 additions & 24 deletions
This file was deleted.

crates/djls-project/src/lib.rs

Lines changed: 51 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,21 @@
11
mod ast;
22
mod db;
3-
mod environment;
4-
mod extraction;
3+
mod models;
54
mod names;
65
mod parse;
76
mod project;
87
mod python;
98
mod resolve;
109
mod settings;
11-
mod specs;
12-
mod symbols;
1310
mod sync;
1411
mod system;
1512
mod templates;
1613

1714
pub use ast::ExprExt;
1815
pub use db::Db;
19-
pub use environment::InactiveLibraries;
20-
pub use environment::InactiveLibrary;
21-
pub use environment::inactive_template_libraries;
22-
pub use extraction::DjangoSettings;
23-
pub use extraction::InstalledAppsSetting;
24-
pub use extraction::RegistrationInfo;
25-
pub use extraction::RegistrationKind;
26-
pub use extraction::SettingsSource;
27-
pub use extraction::SettingsSourceResolver;
28-
pub use extraction::SettingsStarImport;
29-
pub use extraction::StaticKnowledge;
30-
pub use extraction::TemplateBackend;
31-
pub use extraction::TemplateDirPath;
32-
pub use extraction::TemplateSettings;
33-
pub use extraction::collect_registrations_from_body;
34-
pub use extraction::extract_settings;
16+
pub use models::ModelGraph;
17+
pub use models::compute_model_graph;
18+
pub use models::extract_model_graph;
3519
pub use names::LibraryName;
3620
pub use names::ModulePath;
3721
pub use names::PyModuleName;
@@ -46,54 +30,67 @@ pub use resolve::SearchPaths;
4630
pub use resolve::discover_model_files;
4731
pub use resolve::model_modules;
4832
pub use resolve::templatetag_modules;
33+
pub use settings::DjangoSettings;
34+
pub use settings::InstalledAppsSetting;
35+
pub use settings::SettingsSource;
36+
pub use settings::SettingsSourceResolver;
37+
pub use settings::SettingsStarImport;
38+
pub use settings::StaticKnowledge;
39+
pub use settings::TemplateBackend;
40+
pub use settings::TemplateDirPath;
41+
pub use settings::TemplateSettings;
4942
pub use settings::django_settings;
43+
pub use settings::extract_settings;
5044
pub use settings::settings_module_file;
51-
pub use settings::template_dirs;
52-
pub use settings::template_libraries;
53-
pub use specs::ArgumentCountConstraint;
54-
pub use specs::AsVar;
55-
pub use specs::BlockSpec;
56-
pub use specs::BlockSpecs;
57-
pub use specs::ChoiceAt;
58-
pub use specs::ExtractedDiagnosticConstraint;
59-
pub use specs::ExtractedDiagnosticMessage;
60-
pub use specs::ExtractedMessageArg;
61-
pub use specs::ExtractedMessageTemplate;
62-
pub use specs::FilterArity;
63-
pub use specs::FilterArityMap;
64-
pub use specs::KnownOptions;
65-
pub use specs::ModelGraph;
66-
pub use specs::RequiredKeyword;
67-
pub use specs::SplitPosition;
68-
pub use specs::SymbolKey;
69-
pub use specs::TagArgument;
70-
pub use specs::TagArgumentKind;
71-
pub use specs::TagRule;
72-
pub use specs::TagRuleMap;
73-
pub use specs::TemplateSymbolKind;
74-
pub use specs::compute_model_graph;
75-
pub use specs::extract_block_specs;
76-
pub use specs::extract_filter_arities;
77-
pub use specs::extract_model_graph;
78-
pub use specs::extract_tag_rules;
79-
pub use symbols::InstalledSymbolCandidate;
80-
pub use symbols::InstalledSymbolOrigin;
81-
pub use symbols::SymbolDefinition;
82-
pub use symbols::TemplateLibraries;
83-
pub use symbols::TemplateLibrary;
84-
pub use symbols::TemplateSymbol;
8545
pub use sync::RefreshData;
8646
pub use sync::RefreshQuery;
8747
pub use sync::RefreshQueryResult;
8848
pub use sync::apply_refresh;
49+
pub use templates::ArgumentCountConstraint;
50+
pub use templates::AsVar;
51+
pub use templates::BlockSpec;
52+
pub use templates::BlockSpecs;
53+
pub use templates::ChoiceAt;
54+
pub use templates::ExtractedDiagnosticConstraint;
55+
pub use templates::ExtractedDiagnosticMessage;
56+
pub use templates::ExtractedMessageArg;
57+
pub use templates::ExtractedMessageTemplate;
58+
pub use templates::FilterArity;
59+
pub use templates::FilterArityMap;
8960
pub use templates::FindTemplateResult;
61+
pub use templates::InactiveLibraries;
62+
pub use templates::InactiveLibrary;
63+
pub use templates::InstalledSymbolCandidate;
64+
pub use templates::InstalledSymbolOrigin;
65+
pub use templates::KnownOptions;
9066
pub use templates::ProjectTemplateFile;
9167
pub use templates::ProjectTemplateFiles;
68+
pub use templates::RegistrationInfo;
69+
pub use templates::RegistrationKind;
70+
pub use templates::RequiredKeyword;
71+
pub use templates::SplitPosition;
72+
pub use templates::SymbolDefinition;
73+
pub use templates::SymbolKey;
74+
pub use templates::TagArgument;
75+
pub use templates::TagArgumentKind;
76+
pub use templates::TagRule;
77+
pub use templates::TagRuleMap;
9278
pub use templates::TemplateDoesNotExist;
79+
pub use templates::TemplateLibraries;
80+
pub use templates::TemplateLibrary;
9381
pub use templates::TemplateName;
9482
pub use templates::TemplateOrigin;
9583
pub use templates::TemplateOrigins;
84+
pub use templates::TemplateSymbol;
85+
pub use templates::TemplateSymbolKind;
9686
pub use templates::TriedTemplateSource;
87+
pub use templates::collect_registrations_from_body;
88+
pub use templates::extract_block_specs;
89+
pub use templates::extract_filter_arities;
90+
pub use templates::extract_tag_rules;
9791
pub use templates::find_template;
92+
pub use templates::inactive_template_libraries;
9893
pub use templates::project_template_files;
94+
pub use templates::template_dirs;
95+
pub use templates::template_libraries;
9996
pub use templates::template_origins;
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
pub mod extract;
2-
pub mod graph;
1+
mod extract;
2+
mod graph;
33

44
use djls_source::File;
5+
pub use extract::extract_model_graph;
6+
pub use graph::ModelGraph;
57

68
use crate::db::Db;
9+
use crate::models::extract::extract_model_graph_from_body;
710
use crate::names::ModulePath;
811
use crate::parse::parse_python_module;
912
use crate::project::Project;
1013
use crate::resolve::model_modules;
11-
use crate::specs::models::extract::extract_model_graph_from_body;
12-
use crate::specs::models::graph::ModelGraph;
1314

1415
/// Compute a merged `ModelGraph` from discovered model sources.
1516
#[salsa::tracked(returns(ref))]

crates/djls-project/src/specs/models/extract.rs renamed to crates/djls-project/src/models/extract.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ use rustc_hash::FxHashSet;
88
use crate::ast::ExprExt;
99
use crate::ast::Recurse;
1010
use crate::ast::walk_stmts;
11-
use crate::specs::models::graph::FieldName;
12-
use crate::specs::models::graph::ModelDef;
13-
use crate::specs::models::graph::ModelGraph;
14-
use crate::specs::models::graph::ModelKind;
15-
use crate::specs::models::graph::ModelName;
16-
use crate::specs::models::graph::Relation;
17-
use crate::specs::models::graph::RelationType;
11+
use crate::models::graph::FieldName;
12+
use crate::models::graph::ModelDef;
13+
use crate::models::graph::ModelGraph;
14+
use crate::models::graph::ModelKind;
15+
use crate::models::graph::ModelName;
16+
use crate::models::graph::Relation;
17+
use crate::models::graph::RelationType;
1818

1919
/// Extract a model graph from Python source text.
2020
///
File renamed without changes.

crates/djls-project/src/resolve.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ pub fn templatetag_modules(db: &dyn ProjectDb, project: Project) -> Vec<PythonMo
247247

248248
let mut modules = Vec::new();
249249

250-
for (module_index, module_path) in crate::settings::template_libraries(db, project)
250+
for (module_index, module_path) in crate::templates::template_libraries(db, project)
251251
.registration_modules()
252252
.into_iter()
253253
.enumerate()

0 commit comments

Comments
 (0)