Skip to content

Commit 5eb84a9

Browse files
Refactor validation to BlockTree and tagspec semantics only (#266)
1 parent c0255eb commit 5eb84a9

26 files changed

+1424
-621
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/djls-bench/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ edition = "2021"
66
[dependencies]
77
djls-source = { workspace = true }
88
djls-templates = { workspace = true }
9+
djls-semantic = { workspace = true }
910

1011
camino = { workspace = true }
1112
divan = { workspace = true }
@@ -15,5 +16,9 @@ salsa = { workspace = true }
1516
name = "parser"
1617
harness = false
1718

19+
[[bench]]
20+
name = "semantic"
21+
harness = false
22+
1823
[lints]
1924
workspace = true
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use divan::Bencher;
2+
use djls_bench::template_fixtures;
3+
use djls_bench::Db;
4+
use djls_bench::TemplateFixture;
5+
6+
fn main() {
7+
divan::main();
8+
}
9+
10+
#[divan::bench(args = template_fixtures())]
11+
fn build_block_tree(bencher: Bencher, fixture: &TemplateFixture) {
12+
let mut db = Db::new();
13+
let file = db.file_with_contents(fixture.path.clone(), &fixture.source);
14+
15+
let _ = djls_templates::parse_template(&db, file).unwrap();
16+
17+
bencher.bench_local(move || {
18+
if let Some(nodelist) = djls_templates::parse_template(&db, file) {
19+
let tree = djls_semantic::build_block_tree(&db, nodelist);
20+
divan::black_box(tree.roots(&db).len());
21+
}
22+
});
23+
}
24+
25+
#[divan::bench(args = template_fixtures())]
26+
fn build_semantic_forest(bencher: Bencher, fixture: &TemplateFixture) {
27+
let mut db = Db::new();
28+
let file = db.file_with_contents(fixture.path.clone(), &fixture.source);
29+
30+
if let Some(nodelist) = djls_templates::parse_template(&db, file) {
31+
let _ = djls_semantic::build_block_tree(&db, nodelist);
32+
}
33+
34+
bencher.bench_local(move || {
35+
if let Some(nodelist) = djls_templates::parse_template(&db, file) {
36+
let tree = djls_semantic::build_block_tree(&db, nodelist);
37+
let forest = djls_semantic::build_semantic_forest(&db, tree, nodelist);
38+
divan::black_box(forest.roots(&db).len());
39+
}
40+
});
41+
}
42+
43+
#[divan::bench(args = template_fixtures())]
44+
fn validate_template(bencher: Bencher, fixture: &TemplateFixture) {
45+
let mut db = Db::new();
46+
let file = db.file_with_contents(fixture.path.clone(), &fixture.source);
47+
48+
let _ = djls_templates::parse_template(&db, file);
49+
50+
bencher.bench_local(move || {
51+
if let Some(nodelist) = djls_templates::parse_template(&db, file) {
52+
djls_semantic::validate_nodelist(&db, nodelist);
53+
}
54+
});
55+
}
56+
57+
#[divan::bench]
58+
fn validate_all_templates(bencher: Bencher) {
59+
let fixtures = template_fixtures();
60+
let mut db = Db::new();
61+
62+
let files: Vec<_> = fixtures
63+
.iter()
64+
.map(|fixture| {
65+
let file = db.file_with_contents(fixture.path.clone(), &fixture.source);
66+
// Warm up the parse cache
67+
let _ = djls_templates::parse_template(&db, file);
68+
file
69+
})
70+
.collect();
71+
72+
bencher.bench_local(move || {
73+
for file in &files {
74+
if let Some(nodelist) = djls_templates::parse_template(&db, *file) {
75+
djls_semantic::validate_nodelist(&db, nodelist);
76+
}
77+
}
78+
});
79+
}
80+
81+
#[divan::bench(args = template_fixtures())]
82+
fn validate_template_incremental_bench(bencher: Bencher, fixture: &TemplateFixture) {
83+
let mut db = Db::new();
84+
let file = db.file_with_contents(fixture.path.clone(), &fixture.source);
85+
86+
if let Some(nodelist) = djls_templates::parse_template(&db, file) {
87+
djls_semantic::validate_nodelist(&db, nodelist);
88+
}
89+
90+
let original = fixture.source.clone();
91+
let modified = {
92+
let mut text = original.clone();
93+
text.push(' ');
94+
text
95+
};
96+
97+
let mut revision = 1_u64;
98+
let mut use_modified = true;
99+
100+
bencher.bench_local(move || {
101+
let contents = if use_modified { &modified } else { &original };
102+
use_modified = !use_modified;
103+
104+
db.set_file_contents(file, contents, revision);
105+
revision = revision.wrapping_add(1);
106+
107+
if let Some(nodelist) = djls_templates::parse_template(&db, file) {
108+
djls_semantic::validate_nodelist(&db, nodelist);
109+
}
110+
});
111+
}

crates/djls-bench/src/db.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ use std::sync::Arc;
33

44
use camino::Utf8Path;
55
use camino::Utf8PathBuf;
6+
use djls_semantic::django_builtin_specs;
7+
use djls_semantic::Db as SemanticDb;
8+
use djls_semantic::TagIndex;
9+
use djls_semantic::TagSpecs;
610
use djls_source::Db as SourceDb;
711
use djls_source::File;
812
use djls_source::FxDashMap;
@@ -59,3 +63,14 @@ impl SourceDb for Db {
5963

6064
#[salsa::db]
6165
impl TemplateDb for Db {}
66+
67+
#[salsa::db]
68+
impl SemanticDb for Db {
69+
fn tag_specs(&self) -> TagSpecs {
70+
django_builtin_specs()
71+
}
72+
73+
fn tag_index(&self) -> TagIndex<'_> {
74+
TagIndex::from_specs(self)
75+
}
76+
}

crates/djls-ide/src/diagnostics.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ impl DiagnosticError for ValidationError {
6262
| ValidationError::OrphanedTag { span, .. }
6363
| ValidationError::UnmatchedBlockName { span, .. }
6464
| ValidationError::MissingRequiredArguments { span, .. }
65-
| ValidationError::TooManyArguments { span, .. } => Some(span.into()),
65+
| ValidationError::TooManyArguments { span, .. }
66+
| ValidationError::MissingArgument { span, .. }
67+
| ValidationError::InvalidLiteralArgument { span, .. }
68+
| ValidationError::InvalidArgumentChoice { span, .. } => Some(span.into()),
6669
}
6770
}
6871

@@ -72,8 +75,11 @@ impl DiagnosticError for ValidationError {
7275
ValidationError::UnbalancedStructure { .. } => "S101",
7376
ValidationError::OrphanedTag { .. } => "S102",
7477
ValidationError::UnmatchedBlockName { .. } => "S103",
75-
ValidationError::MissingRequiredArguments { .. } => "S104",
78+
ValidationError::MissingRequiredArguments { .. }
79+
| ValidationError::MissingArgument { .. } => "S104",
7680
ValidationError::TooManyArguments { .. } => "S105",
81+
ValidationError::InvalidLiteralArgument { .. } => "S106",
82+
ValidationError::InvalidArgumentChoice { .. } => "S107",
7783
}
7884
}
7985
}

crates/djls-semantic/src/blocks.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,22 @@ mod builder;
22
mod grammar;
33
mod snapshot;
44
mod tree;
5+
6+
use builder::BlockTreeBuilder;
7+
pub use grammar::TagIndex;
8+
pub(crate) use tree::BlockId;
9+
pub(crate) use tree::BlockNode;
10+
pub(crate) use tree::BlockTree;
11+
pub(crate) use tree::BranchKind;
12+
13+
use crate::db::Db;
14+
use crate::traits::SemanticModel;
15+
16+
#[salsa::tracked]
17+
pub fn build_block_tree<'db>(
18+
db: &'db dyn Db,
19+
nodelist: djls_templates::NodeList<'db>,
20+
) -> BlockTree<'db> {
21+
let builder = BlockTreeBuilder::new(db, db.tag_index());
22+
builder.model(db, nodelist)
23+
}

0 commit comments

Comments
 (0)