Skip to content
This repository was archived by the owner on Sep 24, 2025. It is now read-only.

Commit 0995bb0

Browse files
authored
feat: add configurable unrecognized version fallback behavior (#475)
Implements the `wdl` side of #464; a new `sprocket` release will still be required to fully close out that issue. The primary change here is a move of the recognized version enforcement from the initial parse in `wdl-grammar` to the code in `wdl-analysis` that consumes the parse. The behavior upon finding an unrecognized version is now also configurable, with a configurable-severity diagnostic being emitted in case the fallback is used. Other notable changes include: - `Analyzer` now takes a general-purpose `Config` struct which contains the previous `DiagnosticsConfig` struct as a field. - `wdl-ast` interfaces now include a version fallback option in order to return an `Ast` variant consistent with the analyzer's configured fallback option. - `wdl-lsp`'s configuration is now hardcoded to fall back to the default WDL version (1.2 as of this change) with warning-level severity for the associated diagnostic. Additional configurability is planned in #517. - The `wdl-analysis` test suite now looks for `config.toml` in test definition directories and runs the test under that configuration if found. This is a fairly crude mechanism at this point—suitable for testing, but `sprocket`'s config files are the better option for user-facing config. - New tests have been added that exercise the fallback behavior with and without configured diagnostics.
1 parent 439b9dd commit 0995bb0

File tree

51 files changed

+921
-434
lines changed

Some content is hidden

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

51 files changed

+921
-434
lines changed

gauntlet/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use report::Status;
4040
use report::UnmatchedStatus;
4141
pub use repository::Repository;
4242
use wdl::analysis::Analyzer;
43+
use wdl::analysis::Config as AnalysisConfig;
4344
use wdl::analysis::DiagnosticsConfig;
4445
use wdl::analysis::Validator;
4546
use wdl::analysis::rules;
@@ -200,7 +201,9 @@ pub async fn gauntlet(args: Args) -> Result<()> {
200201

201202
let analyzer = Analyzer::new_with_validator(
202203
// Don't bother duplicating analysis warnings for arena mode
203-
DiagnosticsConfig::new(if args.arena { Vec::new() } else { rules() }),
204+
AnalysisConfig::default().with_diagnostics_config(DiagnosticsConfig::new(
205+
if args.arena { Vec::new() } else { rules() },
206+
)),
204207
move |_: (), _, _, _| async move {},
205208
move || {
206209
let mut validator = if args.arena {

wdl-analysis/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
* Added support for struct members, struct literals and call inputs in `goto_definition` ([#491](https://github.com/stjude-rust-labs/wdl/pull/491)).
1313
* Added `find references` support for WDL Language Server ([#484](https://github.com/stjude-rust-labs/wdl/pull/484)).
1414
* Added `goto_definition` support for WDL Language Server ([#468](https://github.com/stjude-rust-labs/wdl/pull/468)).
15+
* Added a `fallback_version` configuration option ([#475](https://github.com/stjude-rust-labs/wdl/pull/475)).
16+
17+
#### Changed
18+
19+
* `Analyzer` now takes a general-purpose `Config` argument, which contains the previous `DiagnosticsConfig` argument ([#475](https://github.com/stjude-rust-labs/wdl/pull/475)).
20+
* Non-error diagnostics during parsing no longer prevent `wdl-analysis` from analyzing documents ([#475](https://github.com/stjude-rust-labs/wdl/pull/475)).
1521

1622
#### Fixed
1723

wdl-analysis/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ rayon = { workspace = true }
2626
regex = { workspace = true }
2727
reqwest = { workspace = true }
2828
rowan = { workspace = true }
29+
serde = { workspace = true, features = ["rc"] }
2930
tokio = { workspace = true }
3031
tracing = { workspace = true }
3132
url = { workspace = true }
@@ -38,6 +39,8 @@ codespan-reporting = { workspace = true }
3839
colored = { workspace = true }
3940
pretty_assertions = { workspace = true }
4041
tempfile = { workspace = true }
42+
toml = { workspace = true }
43+
tracing-subscriber = { workspace = true }
4144

4245
[features]
4346
default = []

wdl-analysis/src/analyzer.rs

Lines changed: 14 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use std::future::Future;
66
use std::mem::ManuallyDrop;
77
use std::ops::Range;
88
use std::path::Path;
9-
use std::path::PathBuf;
109
use std::path::absolute;
1110
use std::sync::Arc;
1211
use std::thread::JoinHandle;
@@ -29,16 +28,8 @@ use tokio::sync::mpsc;
2928
use tokio::sync::oneshot;
3029
use url::Url;
3130
use walkdir::WalkDir;
32-
use wdl_ast::Severity;
33-
use wdl_ast::SyntaxNode;
34-
35-
use crate::Rule;
36-
use crate::SyntaxNodeExt;
37-
use crate::UNNECESSARY_FUNCTION_CALL;
38-
use crate::UNUSED_CALL_RULE_ID;
39-
use crate::UNUSED_DECL_RULE_ID;
40-
use crate::UNUSED_IMPORT_RULE_ID;
41-
use crate::UNUSED_INPUT_RULE_ID;
31+
32+
use crate::config::Config;
4233
use crate::document::Document;
4334
use crate::graph::DocumentGraphNode;
4435
use crate::graph::ParseState;
@@ -53,7 +44,6 @@ use crate::queue::NotifyIncrementalChangeRequest;
5344
use crate::queue::RemoveRequest;
5445
use crate::queue::Request;
5546
use crate::rayon::RayonHandle;
56-
use crate::rules;
5747

5848
/// Represents the kind of analysis progress being reported.
5949
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -296,138 +286,6 @@ pub struct IncrementalChange {
296286
pub edits: Vec<SourceEdit>,
297287
}
298288

299-
/// Configuration for analysis diagnostics.
300-
///
301-
/// Only the analysis diagnostics that aren't inherently treated as errors are
302-
/// represented here.
303-
///
304-
/// These diagnostics default to a warning severity.
305-
#[derive(Debug, Clone, Copy)]
306-
pub struct DiagnosticsConfig {
307-
/// The severity for the "unused import" diagnostic.
308-
///
309-
/// A value of `None` disables the diagnostic.
310-
pub unused_import: Option<Severity>,
311-
/// The severity for the "unused input" diagnostic.
312-
///
313-
/// A value of `None` disables the diagnostic.
314-
pub unused_input: Option<Severity>,
315-
/// The severity for the "unused declaration" diagnostic.
316-
///
317-
/// A value of `None` disables the diagnostic.
318-
pub unused_declaration: Option<Severity>,
319-
/// The severity for the "unused call" diagnostic.
320-
///
321-
/// A value of `None` disables the diagnostic.
322-
pub unused_call: Option<Severity>,
323-
/// The severity for the "unnecessary function call" diagnostic.
324-
///
325-
/// A value of `None` disables the diagnostic.
326-
pub unnecessary_function_call: Option<Severity>,
327-
}
328-
329-
impl Default for DiagnosticsConfig {
330-
fn default() -> Self {
331-
let mut unused_import = None;
332-
let mut unused_input = None;
333-
let mut unused_declaration = None;
334-
let mut unused_call = None;
335-
let mut unnecessary_function_call = None;
336-
337-
for rule in rules() {
338-
let rule = rule.as_ref();
339-
match rule.id() {
340-
UNUSED_IMPORT_RULE_ID => unused_import = Some(rule.severity()),
341-
UNUSED_INPUT_RULE_ID => unused_input = Some(rule.severity()),
342-
UNUSED_DECL_RULE_ID => unused_declaration = Some(rule.severity()),
343-
UNUSED_CALL_RULE_ID => unused_call = Some(rule.severity()),
344-
UNNECESSARY_FUNCTION_CALL => unnecessary_function_call = Some(rule.severity()),
345-
_ => {
346-
unreachable!("unknown rule ID: {}", rule.id());
347-
}
348-
}
349-
}
350-
351-
Self {
352-
unused_import,
353-
unused_input,
354-
unused_declaration,
355-
unused_call,
356-
unnecessary_function_call,
357-
}
358-
}
359-
}
360-
361-
impl DiagnosticsConfig {
362-
/// Creates a new diagnostics configuration from a rule set.
363-
pub fn new<T: AsRef<dyn Rule>>(rules: impl IntoIterator<Item = T>) -> Self {
364-
let mut unused_import = None;
365-
let mut unused_input = None;
366-
let mut unused_declaration = None;
367-
let mut unused_call = None;
368-
let mut unnecessary_function_call = None;
369-
370-
for rule in rules {
371-
let rule = rule.as_ref();
372-
match rule.id() {
373-
UNUSED_IMPORT_RULE_ID => unused_import = Some(rule.severity()),
374-
UNUSED_INPUT_RULE_ID => unused_input = Some(rule.severity()),
375-
UNUSED_DECL_RULE_ID => unused_declaration = Some(rule.severity()),
376-
UNUSED_CALL_RULE_ID => unused_call = Some(rule.severity()),
377-
UNNECESSARY_FUNCTION_CALL => unnecessary_function_call = Some(rule.severity()),
378-
_ => {}
379-
}
380-
}
381-
382-
Self {
383-
unused_import,
384-
unused_input,
385-
unused_declaration,
386-
unused_call,
387-
unnecessary_function_call,
388-
}
389-
}
390-
391-
/// Gets the excepted set of diagnostics based on any `#@ except` comments
392-
/// that precede the given syntax node.
393-
pub fn excepted_for_node(mut self, node: &SyntaxNode) -> Self {
394-
let exceptions = node.rule_exceptions();
395-
396-
if exceptions.contains(UNUSED_IMPORT_RULE_ID) {
397-
self.unused_import = None;
398-
}
399-
400-
if exceptions.contains(UNUSED_INPUT_RULE_ID) {
401-
self.unused_input = None;
402-
}
403-
404-
if exceptions.contains(UNUSED_DECL_RULE_ID) {
405-
self.unused_declaration = None;
406-
}
407-
408-
if exceptions.contains(UNUSED_CALL_RULE_ID) {
409-
self.unused_call = None;
410-
}
411-
412-
if exceptions.contains(UNNECESSARY_FUNCTION_CALL) {
413-
self.unnecessary_function_call = None;
414-
}
415-
416-
self
417-
}
418-
419-
/// Excepts all of the diagnostics.
420-
pub fn except_all() -> Self {
421-
Self {
422-
unused_import: None,
423-
unused_input: None,
424-
unused_declaration: None,
425-
unused_call: None,
426-
unnecessary_function_call: None,
427-
}
428-
}
429-
}
430-
431289
/// Represents a Workflow Description Language (WDL) document analyzer.
432290
///
433291
/// By default, analysis parses documents, performs validation checks, resolves
@@ -453,23 +311,22 @@ impl<Context> Analyzer<Context>
453311
where
454312
Context: Send + Clone + 'static,
455313
{
456-
/// Constructs a new analyzer with the given diagnostics config.
314+
/// Constructs a new analyzer with the given config.
457315
///
458316
/// The provided progress callback will be invoked during analysis.
459317
///
460318
/// The analyzer will use a default validator for validation.
461319
///
462320
/// The analyzer must be constructed from the context of a Tokio runtime.
463-
pub fn new<Progress, Return>(config: DiagnosticsConfig, progress: Progress) -> Self
321+
pub fn new<Progress, Return>(config: Config, progress: Progress) -> Self
464322
where
465323
Progress: Fn(Context, ProgressKind, usize, usize) -> Return + Send + 'static,
466324
Return: Future<Output = ()>,
467325
{
468326
Self::new_with_validator(config, progress, crate::Validator::default)
469327
}
470328

471-
/// Constructs a new analyzer with the given diagnostics config and
472-
/// validator function.
329+
/// Constructs a new analyzer with the given config and validator function.
473330
///
474331
/// The provided progress callback will be invoked during analysis.
475332
///
@@ -478,7 +335,7 @@ where
478335
///
479336
/// The analyzer must be constructed from the context of a Tokio runtime.
480337
pub fn new_with_validator<Progress, Return, Validator>(
481-
config: DiagnosticsConfig,
338+
config: Config,
482339
progress: Progress,
483340
validator: Validator,
484341
) -> Self
@@ -529,7 +386,8 @@ where
529386
///
530387
/// Returns an error if there was a problem discovering documents for the
531388
/// specified path.
532-
pub async fn add_directory(&self, path: PathBuf) -> Result<()> {
389+
pub async fn add_directory(&self, path: impl AsRef<Path>) -> Result<()> {
390+
let path = path.as_ref().to_path_buf();
533391
// Start by searching for documents
534392
let documents = RayonHandle::spawn(move || -> Result<IndexSet<Url>> {
535393
let mut documents = IndexSet::new();
@@ -788,7 +646,7 @@ where
788646

789647
impl Default for Analyzer<()> {
790648
fn default() -> Self {
791-
Self::new(DiagnosticsConfig::default(), |_, _, _, _| async {})
649+
Self::new(Default::default(), |_, _, _, _| async {})
792650
}
793651
}
794652

@@ -817,11 +675,10 @@ mod test {
817675
use wdl_ast::Severity;
818676

819677
use super::*;
820-
use crate::rules;
821678

822679
#[tokio::test]
823680
async fn it_returns_empty_results() {
824-
let analyzer = Analyzer::new(DiagnosticsConfig::new(rules()), |_: (), _, _, _| async {});
681+
let analyzer = Analyzer::default();
825682
let results = analyzer.analyze(()).await.unwrap();
826683
assert!(results.is_empty());
827684
}
@@ -845,7 +702,7 @@ workflow test {
845702
.expect("failed to create test file");
846703

847704
// Analyze the file and check the resulting diagnostic
848-
let analyzer = Analyzer::new(DiagnosticsConfig::new(rules()), |_: (), _, _, _| async {});
705+
let analyzer = Analyzer::default();
849706
analyzer
850707
.add_document(path_to_uri(&path).expect("should convert to URI"))
851708
.await
@@ -900,7 +757,7 @@ workflow test {
900757
.expect("failed to create test file");
901758

902759
// Analyze the file and check the resulting diagnostic
903-
let analyzer = Analyzer::new(DiagnosticsConfig::new(rules()), |_: (), _, _, _| async {});
760+
let analyzer = Analyzer::default();
904761
analyzer
905762
.add_document(path_to_uri(&path).expect("should convert to URI"))
906763
.await
@@ -972,7 +829,7 @@ workflow test {
972829
.expect("failed to create test file");
973830

974831
// Analyze the file and check the resulting diagnostic
975-
let analyzer = Analyzer::new(DiagnosticsConfig::new(rules()), |_: (), _, _, _| async {});
832+
let analyzer = Analyzer::default();
976833
analyzer
977834
.add_document(path_to_uri(&path).expect("should convert to URI"))
978835
.await
@@ -1051,7 +908,7 @@ workflow test {
1051908
.expect("failed to create test file");
1052909

1053910
// Add all three documents to the analyzer
1054-
let analyzer = Analyzer::new(DiagnosticsConfig::new(rules()), |_: (), _, _, _| async {});
911+
let analyzer = Analyzer::default();
1055912
analyzer
1056913
.add_directory(dir.path().to_path_buf())
1057914
.await

0 commit comments

Comments
 (0)