1#![doc = include_str!("../RULES.md")]
3#![warn(missing_docs)]
26#![warn(rust_2018_idioms)]
27#![warn(rust_2021_compatibility)]
28#![warn(missing_debug_implementations)]
29#![warn(clippy::missing_docs_in_private_items)]
30#![warn(rustdoc::broken_intra_doc_links)]
31
32use wdl_ast::Diagnostics;
33use wdl_ast::SyntaxKind;
34use wdl_ast::Visitor;
35
36pub(crate) mod fix;
37pub mod rules;
38mod tags;
39pub(crate) mod util;
40mod visitor;
41
42pub use tags::*;
43pub use visitor::*;
44pub use wdl_ast as ast;
45
46pub const RESERVED_RULE_IDS: &[&str] = &[
48 "UnusedImport",
49 "UnusedInput",
50 "UnusedDeclaration",
51 "UnusedCall",
52 "UnnecessaryFunctionCall",
53];
54
55pub trait Rule: Visitor<State = Diagnostics> {
57 fn id(&self) -> &'static str;
64
65 fn description(&self) -> &'static str;
67
68 fn explanation(&self) -> &'static str;
70
71 fn tags(&self) -> TagSet;
73
74 fn url(&self) -> Option<&'static str> {
76 None
77 }
78
79 fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]>;
83}
84
85pub fn rules() -> Vec<Box<dyn Rule>> {
87 let rules: Vec<Box<dyn Rule>> = vec![
88 Box::<rules::DoubleQuotesRule>::default(),
89 Box::<rules::NoCurlyCommandsRule>::default(),
90 Box::<rules::SnakeCaseRule>::default(),
91 Box::<rules::MissingRuntimeRule>::default(),
92 Box::<rules::EndingNewlineRule>::default(),
93 Box::<rules::PreambleFormattingRule>::default(),
94 Box::<rules::MatchingParameterMetaRule>::default(),
95 Box::<rules::WhitespaceRule>::default(),
96 Box::<rules::CommandSectionMixedIndentationRule>::default(),
97 Box::<rules::ImportPlacementRule>::default(),
98 Box::<rules::PascalCaseRule>::default(),
99 Box::<rules::ImportWhitespaceRule>::default(),
100 Box::<rules::MissingMetasRule>::default(),
101 Box::<rules::MissingOutputRule>::default(),
102 Box::<rules::ImportSortRule>::default(),
103 Box::<rules::InputNotSortedRule>::default(),
104 Box::<rules::LineWidthRule>::default(),
105 Box::<rules::InconsistentNewlinesRule>::default(),
106 Box::<rules::CallInputSpacingRule>::default(),
107 Box::<rules::SectionOrderingRule>::default(),
108 Box::<rules::DeprecatedObjectRule>::default(),
109 Box::<rules::DescriptionMissingRule>::default(),
110 Box::<rules::DeprecatedPlaceholderOptionRule>::default(),
111 Box::<rules::RuntimeSectionKeysRule>::default(),
112 Box::<rules::TodoRule>::default(),
113 Box::<rules::NonmatchingOutputRule<'_>>::default(),
114 Box::<rules::CommentWhitespaceRule>::default(),
115 Box::<rules::TrailingCommaRule>::default(),
116 Box::<rules::BlankLinesBetweenElementsRule>::default(),
117 Box::<rules::KeyValuePairsRule>::default(),
118 Box::<rules::ExpressionSpacingRule>::default(),
119 Box::<rules::DisallowedInputNameRule>::default(),
120 Box::<rules::DisallowedOutputNameRule>::default(),
121 Box::<rules::ContainerValue>::default(),
122 Box::<rules::MissingRequirementsRule>::default(),
123 Box::<rules::UnknownRule>::default(),
124 Box::<rules::MisplacedLintDirectiveRule>::default(),
125 Box::<rules::VersionFormattingRule>::default(),
126 Box::<rules::PreambleCommentAfterVersionRule>::default(),
127 Box::<rules::MalformedLintDirectiveRule>::default(),
128 Box::<rules::RedundantInputAssignment>::default(),
129 ];
130
131 #[cfg(debug_assertions)]
133 {
134 use convert_case::Case;
135 use convert_case::Casing;
136 let mut set = std::collections::HashSet::new();
137 for r in rules.iter() {
138 if r.id().to_case(Case::Pascal) != r.id() {
139 panic!("lint rule id `{id}` is not pascal case", id = r.id());
140 }
141
142 if !set.insert(r.id()) {
143 panic!("duplicate rule id `{id}`", id = r.id());
144 }
145
146 if RESERVED_RULE_IDS.contains(&r.id()) {
147 panic!("rule id `{id}` is reserved", id = r.id());
148 }
149 }
150 }
151
152 rules
153}
154
155pub fn optional_rules() -> Vec<Box<dyn Rule>> {
157 let opt_rules: Vec<Box<dyn Rule>> = vec![Box::<rules::ShellCheckRule>::default()];
158
159 #[cfg(debug_assertions)]
161 {
162 use convert_case::Case;
163 use convert_case::Casing;
164
165 use crate::rules;
166 let mut set: std::collections::HashSet<&str> =
167 std::collections::HashSet::from_iter(rules().iter().map(|r| r.id()));
168 for r in opt_rules.iter() {
169 if r.id().to_case(Case::Pascal) != r.id() {
170 panic!("lint rule id `{id}` is not pascal case", id = r.id());
171 }
172
173 if !set.insert(r.id()) {
174 panic!("duplicate rule id `{id}`", id = r.id());
175 }
176
177 if RESERVED_RULE_IDS.contains(&r.id()) {
178 panic!("rule id `{id}` is reserved", id = r.id());
179 }
180 }
181 }
182
183 opt_rules
184}