1#![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
34mod analyzer;
35pub mod diagnostics;
36pub mod document;
37pub mod eval;
38mod graph;
39mod queue;
40mod rayon;
41mod rules;
42pub mod stdlib;
43pub mod types;
44mod validation;
45mod visitor;
46
47use std::collections::HashSet;
48
49pub use analyzer::*;
50pub use rules::*;
51pub use validation::*;
52pub use visitor::*;
53use wdl_ast::Direction;
54use wdl_ast::SyntaxKind;
55use wdl_ast::SyntaxNode;
56use wdl_ast::SyntaxToken;
57
58pub const EXCEPT_COMMENT_PREFIX: &str = "#@ except:";
60
61pub trait SyntaxNodeExt {
63 fn except_comments(&self) -> impl Iterator<Item = SyntaxToken> + '_;
65
66 fn rule_exceptions(&self) -> HashSet<String>;
71
72 fn is_rule_excepted(&self, id: &str) -> bool;
74}
75
76impl SyntaxNodeExt for SyntaxNode {
77 fn except_comments(&self) -> impl Iterator<Item = SyntaxToken> + '_ {
78 self.siblings_with_tokens(Direction::Prev)
79 .skip(1)
80 .map_while(|s| {
81 if s.kind() == SyntaxKind::Whitespace || s.kind() == SyntaxKind::Comment {
82 s.into_token()
83 } else {
84 None
85 }
86 })
87 .filter(|t| t.kind() == SyntaxKind::Comment)
88 }
89
90 fn rule_exceptions(&self) -> HashSet<String> {
91 let mut set = HashSet::default();
92 for comment in self.except_comments() {
93 if let Some(ids) = comment.text().strip_prefix(EXCEPT_COMMENT_PREFIX) {
94 for id in ids.split(',') {
95 let id = id.trim();
96 set.insert(id.to_string());
97 }
98 }
99 }
100
101 set
102 }
103
104 fn is_rule_excepted(&self, id: &str) -> bool {
105 for comment in self.except_comments() {
106 if let Some(ids) = comment.text().strip_prefix(EXCEPT_COMMENT_PREFIX) {
107 if ids.split(',').any(|i| i.trim() == id) {
108 return true;
109 }
110 }
111 }
112
113 false
114 }
115}