mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-12-23 08:47:53 +00:00
Add configuration option to disable individual diagnostics (#347)
This commit is contained in:
parent
f36fb51b55
commit
cf60dd63dc
13 changed files with 532 additions and 4 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -85,4 +85,8 @@ impl SemanticDb for Db {
|
|||
fn template_dirs(&self) -> Option<Vec<Utf8PathBuf>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn diagnostics_config(&self) -> djls_conf::DiagnosticsConfig {
|
||||
djls_conf::DiagnosticsConfig::default()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
261
crates/djls-conf/src/diagnostics.rs
Normal file
261
crates/djls-conf/src/diagnostics.rs
Normal file
|
|
@ -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<String, DiagnosticSeverity>,
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String>,
|
||||
#[serde(default)]
|
||||
tagspecs: Vec<TagSpecDef>,
|
||||
#[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 {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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<lsp_types::Diagnostic> {
|
||||
let mut diagnostics = Vec::new();
|
||||
|
||||
let config = db.diagnostics_config();
|
||||
|
||||
let template_errors =
|
||||
djls_templates::parse_template::accumulated::<TemplateErrorAccumulator>(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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<lsp_types::DiagnosticSeverity>;
|
||||
}
|
||||
|
||||
impl DiagnosticSeverityExt for DiagnosticSeverity {
|
||||
fn to_lsp_severity(self) -> Option<lsp_types::DiagnosticSeverity> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -247,6 +247,10 @@ mod tests {
|
|||
fn template_dirs(&self) -> Option<Vec<Utf8PathBuf>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn diagnostics_config(&self) -> djls_conf::DiagnosticsConfig {
|
||||
djls_conf::DiagnosticsConfig::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -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<Vec<Utf8PathBuf>>;
|
||||
|
||||
/// Get the diagnostics configuration
|
||||
fn diagnostics_config(&self) -> DiagnosticsConfig;
|
||||
}
|
||||
|
||||
#[salsa::accumulator]
|
||||
|
|
|
|||
|
|
@ -287,6 +287,10 @@ mod tests {
|
|||
fn template_dirs(&self) -> Option<Vec<Utf8PathBuf>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn diagnostics_config(&self) -> djls_conf::DiagnosticsConfig {
|
||||
djls_conf::DiagnosticsConfig::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -199,6 +199,10 @@ impl SemanticDb for DjangoDatabase {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn diagnostics_config(&self) -> djls_conf::DiagnosticsConfig {
|
||||
self.settings().diagnostics().clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue