1#![doc = include_str!("../RULES.md")]
3#![doc = include_str!("../DEFINITIONS.md")]
5#![warn(missing_docs)]
20#![warn(rust_2018_idioms)]
21#![warn(rust_2021_compatibility)]
22#![warn(missing_debug_implementations)]
23#![warn(clippy::missing_docs_in_private_items)]
24#![warn(rustdoc::broken_intra_doc_links)]
25
26use std::collections::HashSet;
27use std::sync::LazyLock;
28
29use wdl_analysis::Visitor;
30use wdl_ast::SyntaxKind;
31
32mod config;
33pub(crate) mod fix;
34mod linter;
35pub mod rules;
36mod tags;
37pub(crate) mod util;
38
39pub use config::Config;
40pub use linter::*;
41pub use tags::*;
42pub use util::find_nearest_rule;
43pub use wdl_analysis as analysis;
44pub use wdl_ast as ast;
45
46pub const DEFINITIONS_TEXT: &str = include_str!("../DEFINITIONS.md");
48
49pub static ALL_RULE_IDS: LazyLock<Vec<String>> = LazyLock::new(|| {
51 let mut ids: Vec<String> = rules(&Config::default())
52 .iter()
53 .map(|r| r.id().to_string())
54 .collect();
55 ids.sort();
56 ids
57});
58
59pub static ALL_TAG_NAMES: LazyLock<Vec<String>> = LazyLock::new(|| {
61 let mut tags: HashSet<Tag> = HashSet::new();
62 for rule in rules(&Config::default()) {
63 for tag in rule.tags().iter() {
64 tags.insert(tag);
65 }
66 }
67 let mut tag_names: Vec<String> = tags.into_iter().map(|t| t.to_string()).collect();
68 tag_names.sort();
69 tag_names
70});
71
72pub trait Rule: Visitor {
74 fn id(&self) -> &'static str;
81
82 fn description(&self) -> &'static str;
84
85 fn explanation(&self) -> &'static str;
87
88 fn tags(&self) -> TagSet;
90
91 fn url(&self) -> Option<&'static str> {
93 None
94 }
95
96 fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]>;
100
101 fn related_rules(&self) -> &[&'static str];
107}
108
109pub fn rules(config: &Config) -> Vec<Box<dyn Rule>> {
111 let rules: Vec<Box<dyn Rule>> = vec![
112 Box::<rules::DoubleQuotesRule>::default(),
113 Box::<rules::HereDocCommandsRule>::default(),
114 Box::new(rules::SnakeCaseRule::new(config)),
115 Box::<rules::RuntimeSectionRule>::default(),
116 Box::<rules::ParameterMetaMatchedRule>::default(),
117 Box::<rules::CommandSectionIndentationRule>::default(),
118 Box::<rules::ImportPlacementRule>::default(),
119 Box::<rules::PascalCaseRule>::default(),
120 Box::<rules::MetaSectionsRule>::default(),
121 Box::<rules::InputSortedRule>::default(),
122 Box::<rules::ConsistentNewlinesRule>::default(),
123 Box::<rules::CallInputKeywordRule>::default(),
124 Box::<rules::SectionOrderingRule>::default(),
125 Box::<rules::DeprecatedObjectRule>::default(),
126 Box::<rules::MetaDescriptionRule>::default(),
127 Box::<rules::DeprecatedPlaceholderRule>::default(),
128 Box::new(rules::ExpectedRuntimeKeysRule::new(config)),
129 Box::<rules::DocMetaStringsRule>::default(),
130 Box::<rules::TodoCommentRule>::default(),
131 Box::<rules::MatchingOutputMetaRule<'_>>::default(),
132 Box::<rules::InputNameRule>::default(),
133 Box::<rules::OutputNameRule>::default(),
134 Box::new(rules::DeclarationNameRule::new(config)),
135 Box::<rules::RedundantNone>::default(),
136 Box::<rules::ContainerUriRule>::default(),
137 Box::<rules::RequirementsSectionRule>::default(),
138 Box::<rules::KnownRulesRule>::default(),
139 Box::<rules::ExceptDirectiveValidRule>::default(),
140 Box::<rules::ConciseInputRule>::default(),
141 Box::<rules::ShellCheckRule>::default(),
142 Box::<rules::DescriptionLengthRule>::default(),
143 Box::<rules::DocCommentTabsRule>::default(),
144 Box::<rules::UnusedDocCommentsRule>::default(),
145 ];
146
147 #[cfg(debug_assertions)]
150 {
151 use std::collections::HashSet;
152
153 use convert_case::Case;
154 use convert_case::Casing;
155 let mut lint_set = HashSet::new();
156 let analysis_set: HashSet<&str> =
157 HashSet::from_iter(analysis::rules().iter().map(|r| r.id()));
158 for r in &rules {
159 if r.id().to_case(Case::Pascal) != r.id() {
160 panic!("lint rule id `{id}` is not pascal case", id = r.id());
161 }
162
163 if !lint_set.insert(r.id()) {
164 panic!("duplicate rule id `{id}`", id = r.id());
165 }
166
167 if analysis_set.contains(r.id()) {
168 panic!("rule id `{id}` is in use by wdl-analysis", id = r.id());
169 }
170 let self_id = &r.id();
171 for related_id in r.related_rules() {
172 if related_id == self_id {
173 panic!(
174 "Rule `{self_id}` refers to itself in its related rules. This is not \
175 allowed."
176 );
177 }
178 }
179 }
180 }
181
182 rules
183}