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