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 wdl_analysis::Visitor;
27use wdl_ast::SyntaxKind;
28
29pub(crate) mod fix;
30mod linter;
31pub mod rules;
32mod tags;
33pub(crate) mod util;
34
35pub use linter::*;
36pub use tags::*;
37pub use util::find_nearest_rule;
38pub use wdl_analysis as analysis;
39pub use wdl_ast as ast;
40
41pub const DEFINITIONS_TEXT: &str = include_str!("../DEFINITIONS.md");
43
44pub trait Rule: Visitor {
46    fn id(&self) -> &'static str;
53
54    fn description(&self) -> &'static str;
56
57    fn explanation(&self) -> &'static str;
59
60    fn tags(&self) -> TagSet;
62
63    fn url(&self) -> Option<&'static str> {
65        None
66    }
67
68    fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]>;
72
73    fn related_rules(&self) -> &[&'static str];
79}
80
81pub fn rules() -> Vec<Box<dyn Rule>> {
83    let rules: Vec<Box<dyn Rule>> = vec![
84        Box::<rules::DoubleQuotesRule>::default(),
85        Box::<rules::HereDocCommandsRule>::default(),
86        Box::<rules::SnakeCaseRule>::default(),
87        Box::<rules::RuntimeSectionRule>::default(),
88        Box::<rules::EndingNewlineRule>::default(),
89        Box::<rules::PreambleFormattedRule>::default(),
90        Box::<rules::ParameterMetaMatchedRule>::default(),
91        Box::<rules::WhitespaceRule>::default(),
92        Box::<rules::CommandSectionIndentationRule>::default(),
93        Box::<rules::ImportPlacementRule>::default(),
94        Box::<rules::PascalCaseRule>::default(),
95        Box::<rules::ImportWhitespaceRule>::default(),
96        Box::<rules::MetaSectionsRule>::default(),
97        Box::<rules::ImportSortedRule>::default(),
98        Box::<rules::InputSortedRule>::default(),
99        Box::<rules::LineWidthRule>::default(),
100        Box::<rules::ConsistentNewlinesRule>::default(),
101        Box::<rules::CallInputSpacingRule>::default(),
102        Box::<rules::CallInputKeywordRule>::default(),
103        Box::<rules::SectionOrderingRule>::default(),
104        Box::<rules::DeprecatedObjectRule>::default(),
105        Box::<rules::MetaDescriptionRule>::default(),
106        Box::<rules::DeprecatedPlaceholderRule>::default(),
107        Box::<rules::ExpectedRuntimeKeysRule>::default(),
108        Box::<rules::TodoCommentRule>::default(),
109        Box::<rules::MatchingOutputMetaRule<'_>>::default(),
110        Box::<rules::CommentWhitespaceRule>::default(),
111        Box::<rules::TrailingCommaRule>::default(),
112        Box::<rules::ElementSpacingRule>::default(),
113        Box::<rules::MetaKeyValueFormattingRule>::default(),
114        Box::<rules::ExpressionSpacingRule>::default(),
115        Box::<rules::InputNameRule>::default(),
116        Box::<rules::OutputNameRule>::default(),
117        Box::<rules::DeclarationNameRule>::default(),
118        Box::<rules::RedundantNone>::default(),
119        Box::<rules::ContainerUriRule>::default(),
120        Box::<rules::RequirementsSectionRule>::default(),
121        Box::<rules::KnownRulesRule>::default(),
122        Box::<rules::LintDirectiveValidRule>::default(),
123        Box::<rules::VersionStatementFormattedRule>::default(),
124        Box::<rules::PreambleCommentPlacementRule>::default(),
125        Box::<rules::LintDirectiveFormattedRule>::default(),
126        Box::<rules::ConciseInputRule>::default(),
127        Box::<rules::ShellCheckRule>::default(),
128        Box::<rules::DescriptionLengthRule>::default(),
129    ];
130
131    #[cfg(debug_assertions)]
134    {
135        use std::collections::HashSet;
136
137        use convert_case::Case;
138        use convert_case::Casing;
139        let mut lint_set = HashSet::new();
140        let analysis_set: HashSet<&str> =
141            HashSet::from_iter(analysis::rules().iter().map(|r| r.id()));
142        for r in &rules {
143            if r.id().to_case(Case::Pascal) != r.id() {
144                panic!("lint rule id `{id}` is not pascal case", id = r.id());
145            }
146
147            if !lint_set.insert(r.id()) {
148                panic!("duplicate rule id `{id}`", id = r.id());
149            }
150
151            if analysis_set.contains(r.id()) {
152                panic!("rule id `{id}` is in use by wdl-analysis", id = r.id());
153            }
154            let self_id = &r.id();
155            for related_id in r.related_rules() {
156                if related_id == self_id {
157                    panic!(
158                        "Rule `{self_id}` refers to itself in its related rules. This is not \
159                         allowed."
160                    );
161                }
162            }
163        }
164    }
165
166    rules
167}