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::sync::LazyLock;
27
28use dyn_clone::DynClone;
29use strum::VariantArray;
30use wdl_analysis::Example;
31use wdl_analysis::Visitor;
32use wdl_ast::SyntaxKind;
33
34pub mod baseline;
35mod config;
36pub(crate) mod fix;
37mod linter;
38pub mod rules;
39mod tags;
40pub(crate) mod util;
41
42pub use baseline::Baseline;
43pub use baseline::BaselineEntry;
44pub use baseline::BaselineMatcher;
45pub use config::Config;
46#[doc(hidden)]
47pub use config::ConfigField;
48pub use linter::*;
49pub use tags::*;
50pub use wdl_analysis as analysis;
51pub use wdl_ast as ast;
52
53pub const DEFINITIONS_TEXT: &str = include_str!("../DEFINITIONS.md");
55
56pub static ALL_RULE_IDS: LazyLock<Vec<String>> = LazyLock::new(|| {
58 let mut ids: Vec<String> = rules(&Config::default())
59 .iter()
60 .map(|r| r.id().to_string())
61 .collect();
62 ids.sort();
63 ids
64});
65
66pub static ALL_TAG_NAMES: LazyLock<Vec<String>> =
68 LazyLock::new(|| ALL_TAGS.iter().map(|t| t.to_string()).collect());
69
70pub static ALL_TAGS: LazyLock<Vec<Tag>> = LazyLock::new(|| {
72 let mut tags: Vec<Tag> = Tag::VARIANTS.to_vec();
73 tags.sort_by_cached_key(Tag::to_string);
74 tags
75});
76
77pub trait Rule: Visitor + DynClone {
79 fn id(&self) -> &'static str;
86
87 fn description(&self) -> &'static str;
89
90 fn explanation(&self) -> &'static str;
92
93 fn examples(&self) -> &'static [Example];
95
96 fn tags(&self) -> TagSet;
98
99 fn url(&self) -> Option<&'static str> {
101 None
102 }
103
104 fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]>;
108
109 fn related_rules(&self) -> &'static [&'static str];
115}
116
117dyn_clone::clone_trait_object!(Rule);
118
119pub fn rules(config: &Config) -> Vec<Box<dyn Rule + Send + Sync>> {
121 let rules: Vec<Box<dyn Rule + Send + Sync>> = vec![
122 Box::<rules::DoubleQuotesRule>::default(),
123 Box::<rules::HereDocCommandsRule>::default(),
124 Box::new(rules::SnakeCaseRule::new(config)),
125 Box::<rules::RuntimeSectionRule>::default(),
126 Box::<rules::ParameterMetaMatchedRule>::default(),
127 Box::<rules::CommandSectionIndentationRule>::default(),
128 Box::<rules::ImportPlacementRule>::default(),
129 Box::<rules::PascalCaseRule>::default(),
130 Box::<rules::MetaSectionsRule>::default(),
131 Box::<rules::CallInputKeywordRule>::default(),
132 Box::<rules::SectionOrderingRule>::default(),
133 Box::<rules::DeprecatedObjectRule>::default(),
134 Box::<rules::MetaDescriptionRule>::default(),
135 Box::<rules::DeprecatedPlaceholderRule>::default(),
136 Box::new(rules::ExpectedRuntimeKeysRule::new(config)),
137 Box::<rules::EmptyDocCommentRule>::default(),
138 Box::<rules::DocMetaStringsRule>::default(),
139 Box::<rules::TodoCommentRule>::default(),
140 Box::<rules::MatchingOutputMetaRule<'_>>::default(),
141 Box::<rules::InputNameRule>::default(),
142 Box::<rules::OutputNameRule>::default(),
143 Box::new(rules::DeclarationNameRule::new(config)),
144 Box::<rules::RedundantNone>::default(),
145 Box::<rules::HostPathLiteralsRule>::default(),
146 Box::<rules::ContainerUriRule>::default(),
147 Box::<rules::RequirementsSectionRule>::default(),
148 Box::<rules::ExceptDirectiveValidRule>::default(),
149 Box::<rules::ParameterDescriptionRule>::default(),
150 Box::<rules::ConciseInputRule>::default(),
151 Box::<rules::ShellCheckRule>::default(),
152 Box::<rules::DescriptionLengthRule>::default(),
153 Box::<rules::DocCommentTabsRule>::default(),
154 Box::<rules::UnusedDocCommentsRule>::default(),
155 Box::<rules::DenyGlobStar>::default(),
156 Box::<rules::EmptyOutputs>::default(),
157 Box::new(rules::BashSetSyntax::new(config)),
158 Box::<rules::InlineInstall>::default(),
159 ];
160
161 #[cfg(debug_assertions)]
164 {
165 use std::collections::HashSet;
166
167 use convert_case::Case;
168 use convert_case::Casing;
169 let mut lint_set = HashSet::new();
170 let analysis_set: HashSet<&str> =
171 HashSet::from_iter(analysis::rules().iter().map(|r| r.id()));
172 for r in &rules {
173 if r.id().to_case(Case::Pascal) != r.id() {
174 panic!("lint rule id `{id}` is not pascal case", id = r.id());
175 }
176
177 if !lint_set.insert(r.id()) {
178 panic!("duplicate rule id `{id}`", id = r.id());
179 }
180
181 if analysis_set.contains(r.id()) {
182 panic!("rule id `{id}` is in use by wdl-analysis", id = r.id());
183 }
184 let self_id = &r.id();
185 for related_id in r.related_rules() {
186 if related_id == self_id {
187 panic!(
188 "Rule `{self_id}` refers to itself in its related rules. This is not \
189 allowed."
190 );
191 }
192 }
193 }
194 }
195
196 rules
197}