wdl_analysis/
lib.rs

1//! Analysis of Workflow Description Language (WDL) documents.
2//!
3//! An analyzer can be used to implement the [Language Server Protocol (LSP)](https://microsoft.github.io/language-server-protocol/).
4//!
5//! # Examples
6//!
7//! ```no_run
8//! use url::Url;
9//! use wdl_analysis::Analyzer;
10//!
11//! #[tokio::main]
12//! async fn main() {
13//!     let analyzer = Analyzer::default();
14//!     // Add a docuement to the analyzer
15//!     analyzer
16//!         .add_document(Url::parse("file:///path/to/file.wdl").unwrap())
17//!         .await
18//!         .unwrap();
19//!     let results = analyzer.analyze(()).await.unwrap();
20//!     // Process the results
21//!     for result in results {
22//!         // Do something
23//!     }
24//! }
25//! ```
26#![doc = include_str!("../RULES.md")]
27#![warn(missing_docs)]
28#![warn(rust_2018_idioms)]
29#![warn(rust_2021_compatibility)]
30#![warn(missing_debug_implementations)]
31#![warn(clippy::missing_docs_in_private_items)]
32#![warn(rustdoc::broken_intra_doc_links)]
33
34use std::collections::HashSet;
35
36use wdl_ast::Direction;
37use wdl_ast::SyntaxKind;
38use wdl_ast::SyntaxNode;
39use wdl_ast::SyntaxToken;
40
41mod analyzer;
42mod config;
43pub mod diagnostics;
44pub mod document;
45pub mod eval;
46mod graph;
47mod queue;
48mod rayon;
49mod rules;
50pub mod stdlib;
51pub mod types;
52mod validation;
53mod visitor;
54
55pub use analyzer::*;
56pub use config::Config;
57pub use config::DiagnosticsConfig;
58pub use document::Document;
59pub use rules::*;
60pub use validation::*;
61pub use visitor::*;
62pub mod handlers;
63
64/// The prefix of `except` comments.
65pub const EXCEPT_COMMENT_PREFIX: &str = "#@ except:";
66
67/// An extension trait for syntax nodes.
68pub trait SyntaxNodeExt {
69    /// Gets an iterator over the `@except` comments for a syntax node.
70    fn except_comments(&self) -> impl Iterator<Item = SyntaxToken> + '_;
71
72    /// Gets the AST node's rule exceptions set.
73    ///
74    /// The set is the comma-delimited list of rule identifiers that follows a
75    /// `#@ except:` comment.
76    fn rule_exceptions(&self) -> HashSet<String>;
77
78    /// Determines if a given rule id is excepted for the syntax node.
79    fn is_rule_excepted(&self, id: &str) -> bool;
80}
81
82impl SyntaxNodeExt for SyntaxNode {
83    fn except_comments(&self) -> impl Iterator<Item = SyntaxToken> + '_ {
84        self.siblings_with_tokens(Direction::Prev)
85            .skip(1)
86            .map_while(|s| {
87                if s.kind() == SyntaxKind::Whitespace || s.kind() == SyntaxKind::Comment {
88                    s.into_token()
89                } else {
90                    None
91                }
92            })
93            .filter(|t| t.kind() == SyntaxKind::Comment)
94    }
95
96    fn rule_exceptions(&self) -> HashSet<String> {
97        let mut set = HashSet::default();
98        for comment in self.except_comments() {
99            if let Some(ids) = comment.text().strip_prefix(EXCEPT_COMMENT_PREFIX) {
100                for id in ids.split(',') {
101                    let id = id.trim();
102                    set.insert(id.to_string());
103                }
104            }
105        }
106
107        set
108    }
109
110    fn is_rule_excepted(&self, id: &str) -> bool {
111        for comment in self.except_comments() {
112            if let Some(ids) = comment.text().strip_prefix(EXCEPT_COMMENT_PREFIX) {
113                if ids.split(',').any(|i| i.trim() == id) {
114                    return true;
115                }
116            }
117        }
118
119        false
120    }
121}