diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d4f40f3..51958910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project attempts to adhere to [Semantic Versioning](https://semver.org/ ### Added +- Added `diagnostics.severity` configuration option for configuring diagnostic severity levels - Added `pythonpath` configuration option for specifying additional Python import paths - Added documentation for VS Code extension - Added documentation for Zed extension diff --git a/crates/djls-bench/Cargo.toml b/crates/djls-bench/Cargo.toml index f3c59535..4ce07d22 100644 --- a/crates/djls-bench/Cargo.toml +++ b/crates/djls-bench/Cargo.toml @@ -4,6 +4,7 @@ version = "0.0.0" edition = "2021" [dependencies] +djls-conf = { workspace = true } djls-source = { workspace = true } djls-templates = { workspace = true } djls-semantic = { workspace = true } diff --git a/crates/djls-bench/src/db.rs b/crates/djls-bench/src/db.rs index bc00230e..7ed0d7bb 100644 --- a/crates/djls-bench/src/db.rs +++ b/crates/djls-bench/src/db.rs @@ -85,4 +85,8 @@ impl SemanticDb for Db { fn template_dirs(&self) -> Option> { None } + + fn diagnostics_config(&self) -> djls_conf::DiagnosticsConfig { + djls_conf::DiagnosticsConfig::default() + } } diff --git a/crates/djls-conf/src/diagnostics.rs b/crates/djls-conf/src/diagnostics.rs new file mode 100644 index 00000000..46682c09 --- /dev/null +++ b/crates/djls-conf/src/diagnostics.rs @@ -0,0 +1,261 @@ +use std::collections::HashMap; + +use serde::Deserialize; + +/// Diagnostic severity level for LSP diagnostics. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum DiagnosticSeverity { + Off, + Error, + Warning, + Info, + Hint, +} + +/// Configuration for diagnostic severity levels. +/// +/// All diagnostics are enabled by default at "error" severity. +/// Configure severity per diagnostic code or prefix pattern. +/// Specific codes override prefix patterns. +/// +/// Example configuration: +/// ```toml +/// [tool.djls.diagnostics.severity] +/// # Individual codes +/// S101 = "warning" +/// S102 = "off" +/// +/// # Prefixes for bulk configuration +/// "T" = "off" # Disable all template errors +/// T100 = "hint" # But show parser errors as hints (specific overrides prefix) +/// ``` +#[derive(Debug, Clone, PartialEq, Deserialize, Default)] +pub struct DiagnosticsConfig { + /// Map of diagnostic codes/prefixes to severity levels. + /// Supports: + /// - Specific codes: "S100", "T100" + /// - Prefixes: "S" (all S-series), "T" (all T-series), "S1" (S100-S199) + /// - More specific patterns override less specific ones + #[serde(default)] + severity: HashMap, +} + +impl DiagnosticsConfig { + /// Get the severity level for a diagnostic code. + /// + /// Resolution order (most specific wins): + /// 1. Exact match (e.g., "S100") + /// 2. Longest prefix match (e.g., "S1" over "S") + /// 3. Default: Error + #[must_use] + pub fn get_severity(&self, code: &str) -> DiagnosticSeverity { + // First, check for exact match + if let Some(&severity) = self.severity.get(code) { + return severity; + } + + // Then, find the longest matching prefix + let mut best_match: Option<(&str, DiagnosticSeverity)> = None; + + for (pattern, &severity) in &self.severity { + if code.starts_with(pattern) { + match best_match { + None => best_match = Some((pattern, severity)), + Some((existing_pattern, _)) => { + // Longer patterns are more specific + if pattern.len() > existing_pattern.len() { + best_match = Some((pattern, severity)); + } + } + } + } + } + + best_match.map_or(DiagnosticSeverity::Error, |(_, severity)| severity) + } + + /// Check if a diagnostic should be shown (severity is not Off). + #[must_use] + pub fn is_enabled(&self, code: &str) -> bool { + self.get_severity(code) != DiagnosticSeverity::Off + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_severity_default() { + let config = DiagnosticsConfig::default(); + assert_eq!(config.get_severity("S100"), DiagnosticSeverity::Error); + assert_eq!(config.get_severity("T100"), DiagnosticSeverity::Error); + } + + #[test] + fn test_get_severity_exact_match() { + let mut severity = HashMap::new(); + severity.insert("S100".to_string(), DiagnosticSeverity::Warning); + severity.insert("S101".to_string(), DiagnosticSeverity::Off); + + let config = DiagnosticsConfig { severity }; + + assert_eq!(config.get_severity("S100"), DiagnosticSeverity::Warning); + assert_eq!(config.get_severity("S101"), DiagnosticSeverity::Off); + assert_eq!(config.get_severity("S102"), DiagnosticSeverity::Error); + } + + #[test] + fn test_get_severity_prefix_match() { + let mut severity = HashMap::new(); + severity.insert("S".to_string(), DiagnosticSeverity::Warning); + severity.insert("T".to_string(), DiagnosticSeverity::Off); + + let config = DiagnosticsConfig { severity }; + + assert_eq!(config.get_severity("S100"), DiagnosticSeverity::Warning); + assert_eq!(config.get_severity("S101"), DiagnosticSeverity::Warning); + assert_eq!(config.get_severity("T100"), DiagnosticSeverity::Off); + assert_eq!(config.get_severity("T900"), DiagnosticSeverity::Off); + } + + #[test] + fn test_get_severity_longest_prefix_wins() { + let mut severity = HashMap::new(); + severity.insert("S".to_string(), DiagnosticSeverity::Warning); + severity.insert("S1".to_string(), DiagnosticSeverity::Off); + severity.insert("S10".to_string(), DiagnosticSeverity::Hint); + + let config = DiagnosticsConfig { severity }; + + // S10 is most specific for S100 + assert_eq!(config.get_severity("S100"), DiagnosticSeverity::Hint); + assert_eq!(config.get_severity("S101"), DiagnosticSeverity::Hint); + // S1 is most specific for S110 + assert_eq!(config.get_severity("S110"), DiagnosticSeverity::Off); + assert_eq!(config.get_severity("S199"), DiagnosticSeverity::Off); + // S is most specific for S200 + assert_eq!(config.get_severity("S200"), DiagnosticSeverity::Warning); + } + + #[test] + fn test_get_severity_exact_overrides_prefix() { + let mut severity = HashMap::new(); + severity.insert("S".to_string(), DiagnosticSeverity::Warning); + severity.insert("S1".to_string(), DiagnosticSeverity::Off); + severity.insert("S100".to_string(), DiagnosticSeverity::Error); + + let config = DiagnosticsConfig { severity }; + + // Exact match wins + assert_eq!(config.get_severity("S100"), DiagnosticSeverity::Error); + // S1 prefix for other S1xx codes + assert_eq!(config.get_severity("S101"), DiagnosticSeverity::Off); + // S prefix for S2xx codes + assert_eq!(config.get_severity("S200"), DiagnosticSeverity::Warning); + } + + #[test] + fn test_is_enabled_default() { + let config = DiagnosticsConfig::default(); + assert!(config.is_enabled("S100")); + assert!(config.is_enabled("T100")); + } + + #[test] + fn test_is_enabled_with_off() { + let mut severity = HashMap::new(); + severity.insert("S100".to_string(), DiagnosticSeverity::Off); + + let config = DiagnosticsConfig { severity }; + + assert!(!config.is_enabled("S100")); + assert!(config.is_enabled("S101")); + } + + #[test] + fn test_is_enabled_with_prefix_off() { + let mut severity = HashMap::new(); + severity.insert("T".to_string(), DiagnosticSeverity::Off); + + let config = DiagnosticsConfig { severity }; + + assert!(!config.is_enabled("T100")); + assert!(!config.is_enabled("T900")); + assert!(config.is_enabled("S100")); + } + + #[test] + fn test_is_enabled_prefix_off_with_specific_override() { + let mut severity = HashMap::new(); + severity.insert("T".to_string(), DiagnosticSeverity::Off); + severity.insert("T100".to_string(), DiagnosticSeverity::Hint); + + let config = DiagnosticsConfig { severity }; + + // T100 has specific override, so it's enabled + assert!(config.is_enabled("T100")); + // Other T codes are off + assert!(!config.is_enabled("T900")); + assert!(!config.is_enabled("T901")); + } + + #[test] + fn test_deserialize_diagnostics_config() { + let toml = r#" + [severity] + S100 = "off" + S101 = "warning" + S102 = "hint" + "T" = "off" + T100 = "info" + "#; + + let config: DiagnosticsConfig = toml::from_str(toml).unwrap(); + assert_eq!(config.get_severity("S100"), DiagnosticSeverity::Off); + assert_eq!(config.get_severity("S101"), DiagnosticSeverity::Warning); + assert_eq!(config.get_severity("S102"), DiagnosticSeverity::Hint); + // T prefix applies to T900 + assert_eq!(config.get_severity("T900"), DiagnosticSeverity::Off); + // T100 has specific override + assert_eq!(config.get_severity("T100"), DiagnosticSeverity::Info); + } + + #[test] + fn test_complex_scenario() { + let mut severity = HashMap::new(); + // Disable all template errors + severity.insert("T".to_string(), DiagnosticSeverity::Off); + // But show parser errors as hints + severity.insert("T100".to_string(), DiagnosticSeverity::Hint); + // Make all semantic errors warnings + severity.insert("S".to_string(), DiagnosticSeverity::Warning); + // Except S100 which is completely off + severity.insert("S100".to_string(), DiagnosticSeverity::Off); + // And S10x (S100-S109) should be info + severity.insert("S10".to_string(), DiagnosticSeverity::Info); + + let config = DiagnosticsConfig { severity }; + + // S100 is exact match - off + assert_eq!(config.get_severity("S100"), DiagnosticSeverity::Off); + assert!(!config.is_enabled("S100")); + + // S101 matches S10 prefix - info + assert_eq!(config.get_severity("S101"), DiagnosticSeverity::Info); + assert!(config.is_enabled("S101")); + + // S200 matches S prefix - warning + assert_eq!(config.get_severity("S200"), DiagnosticSeverity::Warning); + assert!(config.is_enabled("S200")); + + // T100 has exact match - hint + assert_eq!(config.get_severity("T100"), DiagnosticSeverity::Hint); + assert!(config.is_enabled("T100")); + + // T900 matches T prefix - off + assert_eq!(config.get_severity("T900"), DiagnosticSeverity::Off); + assert!(!config.is_enabled("T900")); + } +} diff --git a/crates/djls-conf/src/lib.rs b/crates/djls-conf/src/lib.rs index 11573707..1eb41be3 100644 --- a/crates/djls-conf/src/lib.rs +++ b/crates/djls-conf/src/lib.rs @@ -1,3 +1,4 @@ +pub mod diagnostics; pub mod tagspecs; use std::fs; @@ -14,6 +15,8 @@ use directories::ProjectDirs; use serde::Deserialize; use thiserror::Error; +pub use crate::diagnostics::DiagnosticSeverity; +pub use crate::diagnostics::DiagnosticsConfig; pub use crate::tagspecs::ArgTypeDef; pub use crate::tagspecs::EndTagDef; pub use crate::tagspecs::IntermediateTagDef; @@ -65,6 +68,8 @@ pub struct Settings { pythonpath: Vec, #[serde(default)] tagspecs: Vec, + #[serde(default)] + diagnostics: DiagnosticsConfig, } impl Settings { @@ -86,6 +91,10 @@ impl Settings { if !overrides.tagspecs.is_empty() { settings.tagspecs = overrides.tagspecs; } + // For diagnostics, override if the config is non-default + if overrides.diagnostics != DiagnosticsConfig::default() { + settings.diagnostics = overrides.diagnostics; + } } Ok(settings) @@ -158,6 +167,11 @@ impl Settings { pub fn tagspecs(&self) -> &[TagSpecDef] { &self.tagspecs } + + #[must_use] + pub fn diagnostics(&self) -> &DiagnosticsConfig { + &self.diagnostics + } } #[cfg(test)] @@ -184,6 +198,7 @@ mod tests { django_settings_module: None, pythonpath: vec![], tagspecs: vec![], + diagnostics: DiagnosticsConfig::default(), } ); } @@ -267,6 +282,42 @@ mod tests { } ); } + + #[test] + fn test_load_diagnostics_config() { + let dir = tempdir().unwrap(); + fs::write( + dir.path().join("djls.toml"), + r#" +[diagnostics.severity] +S100 = "off" +S101 = "warning" +"T" = "off" +T100 = "hint" +"#, + ) + .unwrap(); + let settings = Settings::new(Utf8Path::from_path(dir.path()).unwrap(), None).unwrap(); + // Test via public API + assert_eq!( + settings.diagnostics.get_severity("S100"), + DiagnosticSeverity::Off + ); + assert_eq!( + settings.diagnostics.get_severity("S101"), + DiagnosticSeverity::Warning + ); + // T prefix applies to T900 + assert_eq!( + settings.diagnostics.get_severity("T900"), + DiagnosticSeverity::Off + ); + // T100 has specific override + assert_eq!( + settings.diagnostics.get_severity("T100"), + DiagnosticSeverity::Hint + ); + } } mod priority { diff --git a/crates/djls-ide/Cargo.toml b/crates/djls-ide/Cargo.toml index 0521c527..38c7ea86 100644 --- a/crates/djls-ide/Cargo.toml +++ b/crates/djls-ide/Cargo.toml @@ -4,6 +4,7 @@ version = "0.0.0" edition = "2021" [dependencies] +djls-conf = { workspace = true } djls-project = { workspace = true } djls-semantic = { workspace = true } djls-source = { workspace = true } diff --git a/crates/djls-ide/src/diagnostics.rs b/crates/djls-ide/src/diagnostics.rs index 3916e87b..62e3d014 100644 --- a/crates/djls-ide/src/diagnostics.rs +++ b/crates/djls-ide/src/diagnostics.rs @@ -6,6 +6,7 @@ use djls_templates::TemplateError; use djls_templates::TemplateErrorAccumulator; use tower_lsp_server::lsp_types; +use crate::ext::DiagnosticSeverityExt; use crate::ext::SpanExt; trait DiagnosticError: std::fmt::Display { @@ -88,6 +89,9 @@ impl DiagnosticError for ValidationError { /// parsing and validation. The caller must provide the parsed `NodeList` (or `None` /// if parsing failed), making it explicit that parsing should have already occurred. /// +/// Diagnostics are filtered based on the configuration settings (`select` and `ignore`), +/// and severity levels can be overridden per diagnostic code. +/// /// # Parameters /// - `db`: The Salsa database /// - `file`: The source file (needed to retrieve accumulated template errors) @@ -95,7 +99,7 @@ impl DiagnosticError for ValidationError { /// /// # Returns /// A vector of LSP diagnostics combining both template syntax errors and -/// semantic validation errors. +/// semantic validation errors, filtered by the diagnostics configuration. /// /// # Design /// This API design makes it clear that: @@ -110,13 +114,27 @@ pub fn collect_diagnostics( ) -> Vec { let mut diagnostics = Vec::new(); + let config = db.diagnostics_config(); + let template_errors = djls_templates::parse_template::accumulated::(db, file); let line_index = file.line_index(db); for error_acc in template_errors { - diagnostics.push(error_acc.0.as_diagnostic(line_index)); + let mut diagnostic = error_acc.0.as_diagnostic(line_index); + if let Some(lsp_types::NumberOrString::String(code)) = &diagnostic.code { + let severity = config.get_severity(code); + + // Skip if diagnostic is disabled (severity = off) + if let Some(lsp_severity) = severity.to_lsp_severity() { + diagnostic.severity = Some(lsp_severity); + diagnostics.push(diagnostic); + } + } else { + // No code, use default + diagnostics.push(diagnostic); + } } if let Some(nodelist) = nodelist { @@ -125,9 +143,49 @@ pub fn collect_diagnostics( >(db, nodelist); for error_acc in validation_errors { - diagnostics.push(error_acc.0.as_diagnostic(line_index)); + let mut diagnostic = error_acc.0.as_diagnostic(line_index); + if let Some(lsp_types::NumberOrString::String(code)) = &diagnostic.code { + let severity = config.get_severity(code); + + // Skip if diagnostic is disabled (severity = off) + if let Some(lsp_severity) = severity.to_lsp_severity() { + diagnostic.severity = Some(lsp_severity); + diagnostics.push(diagnostic); + } + } else { + // No code, use default + diagnostics.push(diagnostic); + } } } diagnostics } + +#[cfg(test)] +mod tests { + use djls_conf::DiagnosticSeverity; + + use super::*; + + #[test] + fn test_to_lsp_severity() { + assert_eq!(DiagnosticSeverity::Off.to_lsp_severity(), None); + assert_eq!( + DiagnosticSeverity::Error.to_lsp_severity(), + Some(lsp_types::DiagnosticSeverity::ERROR) + ); + assert_eq!( + DiagnosticSeverity::Warning.to_lsp_severity(), + Some(lsp_types::DiagnosticSeverity::WARNING) + ); + assert_eq!( + DiagnosticSeverity::Info.to_lsp_severity(), + Some(lsp_types::DiagnosticSeverity::INFORMATION) + ); + assert_eq!( + DiagnosticSeverity::Hint.to_lsp_severity(), + Some(lsp_types::DiagnosticSeverity::HINT) + ); + } +} diff --git a/crates/djls-ide/src/ext.rs b/crates/djls-ide/src/ext.rs index 71d8e091..04e213fa 100644 --- a/crates/djls-ide/src/ext.rs +++ b/crates/djls-ide/src/ext.rs @@ -1,5 +1,6 @@ use camino::Utf8Path; use camino::Utf8PathBuf; +use djls_conf::DiagnosticSeverity; use djls_source::LineIndex; use djls_source::Offset; use djls_source::Span; @@ -44,3 +45,19 @@ impl Utf8PathExt for Utf8PathBuf { lsp_types::Uri::from_file_path(self.as_std_path()) } } + +pub(crate) trait DiagnosticSeverityExt { + fn to_lsp_severity(self) -> Option; +} + +impl DiagnosticSeverityExt for DiagnosticSeverity { + fn to_lsp_severity(self) -> Option { + match self { + DiagnosticSeverity::Off => None, + DiagnosticSeverity::Error => Some(lsp_types::DiagnosticSeverity::ERROR), + DiagnosticSeverity::Warning => Some(lsp_types::DiagnosticSeverity::WARNING), + DiagnosticSeverity::Info => Some(lsp_types::DiagnosticSeverity::INFORMATION), + DiagnosticSeverity::Hint => Some(lsp_types::DiagnosticSeverity::HINT), + } + } +} diff --git a/crates/djls-semantic/src/blocks/tree.rs b/crates/djls-semantic/src/blocks/tree.rs index 838c9f0a..eca7662b 100644 --- a/crates/djls-semantic/src/blocks/tree.rs +++ b/crates/djls-semantic/src/blocks/tree.rs @@ -247,6 +247,10 @@ mod tests { fn template_dirs(&self) -> Option> { None } + + fn diagnostics_config(&self) -> djls_conf::DiagnosticsConfig { + djls_conf::DiagnosticsConfig::default() + } } #[test] diff --git a/crates/djls-semantic/src/db.rs b/crates/djls-semantic/src/db.rs index 10807452..653ab106 100644 --- a/crates/djls-semantic/src/db.rs +++ b/crates/djls-semantic/src/db.rs @@ -1,4 +1,5 @@ use camino::Utf8PathBuf; +use djls_conf::DiagnosticsConfig; use djls_templates::Db as TemplateDb; use crate::blocks::TagIndex; @@ -13,6 +14,9 @@ pub trait Db: TemplateDb { fn tag_index(&self) -> TagIndex<'_>; fn template_dirs(&self) -> Option>; + + /// Get the diagnostics configuration + fn diagnostics_config(&self) -> DiagnosticsConfig; } #[salsa::accumulator] diff --git a/crates/djls-semantic/src/semantic/forest.rs b/crates/djls-semantic/src/semantic/forest.rs index 3d08e723..3e578b8c 100644 --- a/crates/djls-semantic/src/semantic/forest.rs +++ b/crates/djls-semantic/src/semantic/forest.rs @@ -287,6 +287,10 @@ mod tests { fn template_dirs(&self) -> Option> { None } + + fn diagnostics_config(&self) -> djls_conf::DiagnosticsConfig { + djls_conf::DiagnosticsConfig::default() + } } #[test] diff --git a/crates/djls-server/src/db.rs b/crates/djls-server/src/db.rs index 06c70a1d..c2b5b501 100644 --- a/crates/djls-server/src/db.rs +++ b/crates/djls-server/src/db.rs @@ -199,6 +199,10 @@ impl SemanticDb for DjangoDatabase { None } } + + fn diagnostics_config(&self) -> djls_conf::DiagnosticsConfig { + self.settings().diagnostics().clone() + } } #[salsa::db] diff --git a/docs/configuration.md b/docs/configuration.md index 7aa7e2d3..c54ce2f2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -55,6 +55,110 @@ Additional directories to add to Python's import search path when the inspector Enable debug logging for troubleshooting language server issues. +### `diagnostics` + +Configure diagnostic severity levels. All diagnostics are enabled by default at "error" severity level. + +**Default:** All diagnostics shown as errors + +#### `diagnostics.severity` + +Map diagnostic codes or prefixes to severity levels. Supports: +- **Exact codes:** `"S100"`, `"T100"` +- **Prefixes:** `"S"` (all S-series), `"T"` (all T-series), `"S1"` (S100-S199), `"T9"` (T900-T999) +- **Resolution:** More specific patterns override less specific (exact > longer prefix > shorter prefix) + +**Available severity levels:** +- `"off"` - Disable diagnostic completely +- `"hint"` - Show as subtle hint +- `"info"` - Show as information +- `"warning"` - Show as warning +- `"error"` - Show as error (default) + +#### Available Diagnostic Codes + +**Template Errors (T-series):** +- `T100` - Parser errors (syntax issues in templates) +- `T900` - IO errors (file read/write issues) +- `T901` - Configuration errors (invalid tagspecs) + +**Semantic Validation Errors (S-series):** +- `S100` - Unclosed tag (missing end tag) +- `S101` - Unbalanced structure (mismatched block tags) +- `S102` - Orphaned tag (intermediate tag without parent) +- `S103` - Unmatched block name (e.g., `{% endblock foo %}` doesn't match `{% block bar %}`) +- `S104` - Missing required arguments +- `S105` - Too many arguments +- `S106` - Invalid literal argument +- `S107` - Invalid argument choice + +#### Examples + +**Disable specific diagnostics:** +```toml +[diagnostics.severity] +S100 = "off" # Don't show unclosed tag errors +T100 = "off" # Don't show parser errors +``` + +**Disable all template errors:** +```toml +[diagnostics.severity] +"T" = "off" # Prefix matches all T-series +``` + +**Disable with specific override:** +```toml +[diagnostics.severity] +"T" = "off" # Disable all template errors +T100 = "hint" # But show parser errors as hints (specific overrides prefix) +``` + +**Make all semantic errors warnings:** +```toml +[diagnostics.severity] +"S" = "warning" # All semantic errors as warnings +``` + +**Complex configuration:** +```toml +[diagnostics.severity] +# Disable all template errors +"T" = "off" + +# But show parser errors as hints +T100 = "hint" + +# Make all semantic errors warnings +"S" = "warning" + +# Except completely disable unclosed tags +S100 = "off" + +# And make S10x (S100-S109) info level +"S10" = "info" +``` + +**Resolution order example:** +```toml +[diagnostics.severity] +"S" = "warning" # Base: all S-series are warnings +"S1" = "info" # Override: S100-S199 are info +S100 = "off" # Override: S100 is off + +# Results: +# S100 → off (exact match) +# S101 → info ("S1" prefix) +# S200 → warning ("S" prefix) +``` + +**When to configure:** + +- Disable false positives: Set problematic diagnostics to `"off"` +- Gradual adoption: Downgrade to `"warning"` or `"hint"` during migration +- Focus attention: Disable entire categories with prefix patterns +- Fine-tune experience: Mix prefix patterns with specific overrides + ### `tagspecs` **Default:** `[]` @@ -80,7 +184,15 @@ Pass configuration through your editor's LSP client using `initializationOptions { "django_settings_module": "myproject.settings", "venv_path": "/path/to/venv", - "pythonpath": ["/path/to/shared/libs"] + "pythonpath": ["/path/to/shared/libs"], + "diagnostics": { + "severity": { + "S100": "off", + "S101": "warning", + "T": "off", + "T100": "hint" + } + } } ``` @@ -97,6 +209,12 @@ If you use `pyproject.toml`, add a `[tool.djls]` section: django_settings_module = "myproject.settings" venv_path = "/path/to/venv" # Optional: only if auto-detection fails pythonpath = ["/path/to/shared/libs"] # Optional: additional import paths + +[tool.djls.diagnostics.severity] +S100 = "off" +S101 = "warning" +"T" = "off" +T100 = "hint" ``` If you prefer a dedicated config file or don't use `pyproject.toml`, you can use `djls.toml` (same settings, no `[tool.djls]` table).