1pub mod util;
4
5use std::collections::HashMap;
6use std::sync::LazyLock;
7
8use wdl_ast::Severity;
9use wdl_grammar::SyntaxKind;
10
11use crate::RuleMap;
12
13pub static ALL_RULE_IDS: LazyLock<Vec<String>> = LazyLock::new(|| {
15 let mut ids: Vec<String> = rules().iter().map(|r| r.id().to_string()).collect();
16 ids.sort();
17 ids
18});
19
20pub(crate) static RULE_MAP: LazyLock<RuleMap> = LazyLock::new(|| {
22 let rules = rules();
23 let mut map = HashMap::with_capacity(rules.len());
24 for rule in rules {
25 map.insert(String::from(rule.id()), rule.exceptable_nodes());
26 }
27 map
28});
29
30#[derive(Copy, Clone, Debug)]
32pub struct LabeledSnippet {
33 pub label: Option<&'static str>,
35 pub snippet: &'static str,
37}
38
39#[derive(Copy, Clone, Debug)]
41pub struct Example {
42 pub negative: LabeledSnippet,
44 pub revised: Option<LabeledSnippet>,
46}
47
48pub trait Rule: Send + Sync {
50 fn id(&self) -> &'static str;
55
56 fn description(&self) -> &'static str;
58
59 fn explanation(&self) -> &'static str;
61
62 fn examples(&self) -> &'static [Example];
64
65 fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]>;
69
70 fn deny(&mut self);
74
75 fn severity(&self) -> Severity;
77}
78
79pub fn rules() -> Vec<Box<dyn Rule>> {
81 let rules: Vec<Box<dyn Rule>> = vec![
82 Box::<UnusedImportRule>::default(),
83 Box::<UnusedInputRule>::default(),
84 Box::<UnusedDeclarationRule>::default(),
85 Box::<UnusedCallRule>::default(),
86 Box::<UnnecessaryFunctionCall>::default(),
87 Box::<UsingFallbackVersion>::default(),
88 Box::<MisleadingDeclarationOrderRule>::default(),
89 Box::<MeaninglessLintDirective>::default(),
90 Box::<KnownRulesRule>::default(),
91 Box::<ExceptDirectiveValidRule>::default(),
92 Box::<CommandSectionIndentationRule>::default(),
93 Box::<DeprecatedObjectRule>::default(),
94 Box::<DeprecatedPlaceholderRule>::default(),
95 Box::<DeprecatedRuntimeSectionRule>::default(),
96 ];
97
98 #[cfg(debug_assertions)]
100 {
101 use convert_case::Case;
102 use convert_case::Casing;
103 let mut set = std::collections::HashSet::new();
104 for r in rules.iter() {
105 if r.id().to_case(Case::Pascal) != r.id() {
106 panic!("analysis rule id `{id}` is not pascal case", id = r.id());
107 }
108
109 if !set.insert(r.id()) {
110 panic!("duplicate rule id `{id}`", id = r.id());
111 }
112 }
113 }
114
115 rules
116}
117
118#[derive(Debug, Clone, Copy)]
120pub struct UnusedImportRule(Severity);
121
122impl UnusedImportRule {
123 pub const EXCEPTABLE_NODES: Option<&'static [SyntaxKind]> = Some(&[
125 SyntaxKind::VersionStatementNode,
126 SyntaxKind::ImportStatementNode,
127 ]);
128 pub const ID: &'static str = "UnusedImport";
130
131 pub fn new() -> Self {
133 Self(Severity::Warning)
134 }
135}
136
137impl Default for UnusedImportRule {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143impl Rule for UnusedImportRule {
144 fn id(&self) -> &'static str {
145 Self::ID
146 }
147
148 fn description(&self) -> &'static str {
149 "Ensures that import namespaces are used in the importing document."
150 }
151
152 fn explanation(&self) -> &'static str {
153 "Imported WDL documents should be used in the document that imports them. Unused imports \
154 impact parsing and evaluation performance."
155 }
156
157 fn examples(&self) -> &'static [Example] {
158 &[Example {
159 negative: LabeledSnippet {
160 label: None,
161 snippet: r#"version 1.3
162
163import "bar.wdl"
164import "foo.wdl" as used
165
166workflow example {
167 call used.test
168}
169"#,
170 },
171 revised: Some(LabeledSnippet {
172 label: Some("Consider removing the import entirely"),
173 snippet: r#"version 1.3
174
175import "foo.wdl" as used
176
177workflow example {
178 call used.test
179}
180"#,
181 }),
182 }]
183 }
184
185 fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]> {
186 Self::EXCEPTABLE_NODES
187 }
188
189 fn deny(&mut self) {
190 self.0 = Severity::Error;
191 }
192
193 fn severity(&self) -> Severity {
194 self.0
195 }
196}
197
198#[derive(Debug, Clone, Copy)]
200pub struct UnusedInputRule(Severity);
201
202impl UnusedInputRule {
203 pub const EXCEPTABLE_NODES: Option<&'static [SyntaxKind]> = Some(&[
205 SyntaxKind::VersionStatementNode,
206 SyntaxKind::WorkflowDefinitionNode,
207 SyntaxKind::TaskDefinitionNode,
208 SyntaxKind::BoundDeclNode,
209 SyntaxKind::UnboundDeclNode,
210 ]);
211 pub const ID: &str = "UnusedInput";
213
214 pub fn new() -> Self {
216 Self(Severity::Warning)
217 }
218}
219
220impl Default for UnusedInputRule {
221 fn default() -> Self {
222 Self::new()
223 }
224}
225
226impl Rule for UnusedInputRule {
227 fn id(&self) -> &'static str {
228 Self::ID
229 }
230
231 fn description(&self) -> &'static str {
232 "Ensures that task or workspace inputs are used within the declaring task or workspace."
233 }
234
235 fn explanation(&self) -> &'static str {
236 "Unused inputs degrade evaluation performance and reduce the clarity of the code. Unused \
237 file inputs in tasks can also cause unnecessary file localizations."
238 }
239
240 fn examples(&self) -> &'static [Example] {
241 &[Example {
242 negative: LabeledSnippet {
243 label: None,
244 snippet: r#"version 1.2
245
246workflow example {
247 input {
248 String unused
249 }
250}
251"#,
252 },
253 revised: Some(LabeledSnippet {
254 label: Some("Consider removing the input entirely"),
255 snippet: r#"version 1.2
256
257workflow example {
258 input {
259 }
260}
261"#,
262 }),
263 }]
264 }
265
266 fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]> {
267 Self::EXCEPTABLE_NODES
268 }
269
270 fn deny(&mut self) {
271 self.0 = Severity::Error;
272 }
273
274 fn severity(&self) -> Severity {
275 self.0
276 }
277}
278
279#[derive(Debug, Clone, Copy)]
281pub struct UnusedDeclarationRule(Severity);
282
283impl UnusedDeclarationRule {
284 pub const EXCEPTABLE_NODES: Option<&'static [SyntaxKind]> = Some(&[
286 SyntaxKind::VersionStatementNode,
287 SyntaxKind::WorkflowDefinitionNode,
288 SyntaxKind::TaskDefinitionNode,
289 SyntaxKind::BoundDeclNode,
290 SyntaxKind::UnboundDeclNode,
291 ]);
292 pub const ID: &str = "UnusedDeclaration";
294
295 pub fn new() -> Self {
297 Self(Severity::Warning)
298 }
299}
300
301impl Default for UnusedDeclarationRule {
302 fn default() -> Self {
303 Self::new()
304 }
305}
306
307impl Rule for UnusedDeclarationRule {
308 fn id(&self) -> &'static str {
309 Self::ID
310 }
311
312 fn description(&self) -> &'static str {
313 "Ensures that private declarations in tasks or workspaces are used within the declaring \
314 task or workspace."
315 }
316
317 fn explanation(&self) -> &'static str {
318 "Unused private declarations degrade evaluation performance and reduce the clarity of the \
319 code."
320 }
321
322 fn examples(&self) -> &'static [Example] {
323 &[Example {
324 negative: LabeledSnippet {
325 label: None,
326 snippet: r#"version 1.2
327
328workflow example {
329 String unused = "this will produce a warning"
330}
331"#,
332 },
333 revised: Some(LabeledSnippet {
334 label: Some("Consider removing the declaration entirely"),
335 snippet: r#"version 1.2
336
337workflow example {
338}
339"#,
340 }),
341 }]
342 }
343
344 fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]> {
345 Self::EXCEPTABLE_NODES
346 }
347
348 fn deny(&mut self) {
349 self.0 = Severity::Error;
350 }
351
352 fn severity(&self) -> Severity {
353 self.0
354 }
355}
356
357#[derive(Debug, Clone, Copy)]
359pub struct UnusedCallRule(Severity);
360
361impl UnusedCallRule {
362 pub const EXCEPTABLE_NODES: Option<&'static [SyntaxKind]> = Some(&[
364 SyntaxKind::VersionStatementNode,
365 SyntaxKind::WorkflowDefinitionNode,
366 SyntaxKind::CallStatementNode,
367 ]);
368 pub const ID: &str = "UnusedCall";
370
371 pub fn new() -> Self {
373 Self(Severity::Warning)
374 }
375}
376
377impl Default for UnusedCallRule {
378 fn default() -> Self {
379 Self::new()
380 }
381}
382
383impl Rule for UnusedCallRule {
384 fn id(&self) -> &'static str {
385 Self::ID
386 }
387
388 fn description(&self) -> &'static str {
389 "Ensures that outputs of a call statement are used in the declaring workflow."
390 }
391
392 fn explanation(&self) -> &'static str {
393 "Unused calls may cause unnecessary consumption of compute resources."
394 }
395
396 fn examples(&self) -> &'static [Example] {
397 &[Example {
398 negative: LabeledSnippet {
399 label: None,
400 snippet: r#"version 1.2
401
402workflow example {
403 # The output of `do_work` is never used
404 call do_work
405}
406
407task do_work {
408 command <<<
409 >>>
410
411 output {
412 Int x = 0
413 }
414}
415"#,
416 },
417 revised: Some(LabeledSnippet {
418 label: Some("Consider removing the call entirely"),
419 snippet: r#"version 1.2
420
421workflow example {
422}
423
424task do_work {
425 command <<<
426 >>>
427
428 output {
429 Int x = 0
430 }
431}
432"#,
433 }),
434 }]
435 }
436
437 fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]> {
438 Self::EXCEPTABLE_NODES
439 }
440
441 fn deny(&mut self) {
442 self.0 = Severity::Error;
443 }
444
445 fn severity(&self) -> Severity {
446 self.0
447 }
448}
449
450#[derive(Debug, Clone, Copy)]
452pub struct UnnecessaryFunctionCall(Severity);
453
454impl UnnecessaryFunctionCall {
455 pub const EXCEPTABLE_NODES: Option<&'static [SyntaxKind]> = Some(&[
457 SyntaxKind::VersionStatementNode,
458 SyntaxKind::WorkflowDefinitionNode,
459 SyntaxKind::TaskDefinitionNode,
460 SyntaxKind::BoundDeclNode,
461 ]);
462 pub const ID: &str = "UnnecessaryFunctionCall";
464
465 pub fn new() -> Self {
467 Self(Severity::Warning)
468 }
469}
470
471impl Default for UnnecessaryFunctionCall {
472 fn default() -> Self {
473 Self::new()
474 }
475}
476
477impl Rule for UnnecessaryFunctionCall {
478 fn id(&self) -> &'static str {
479 Self::ID
480 }
481
482 fn description(&self) -> &'static str {
483 "Ensures that function calls are necessary."
484 }
485
486 fn explanation(&self) -> &'static str {
487 "Unnecessary function calls may impact evaluation performance."
488 }
489
490 fn examples(&self) -> &'static [Example] {
491 &[Example {
492 negative: LabeledSnippet {
493 label: None,
494 snippet: r#"version 1.2
495
496workflow example {
497 # Calls to `defined` on values that are statically
498 # known to be non-None are unnecessary.
499 Boolean exists = defined("hello")
500}
501"#,
502 },
503 revised: None,
504 }]
505 }
506
507 fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]> {
508 Self::EXCEPTABLE_NODES
509 }
510
511 fn deny(&mut self) {
512 self.0 = Severity::Error;
513 }
514
515 fn severity(&self) -> Severity {
516 self.0
517 }
518}
519
520#[derive(Debug, Clone, Copy)]
522pub struct UsingFallbackVersion(Severity);
523
524impl UsingFallbackVersion {
525 pub const EXCEPTABLE_NODES: Option<&'static [SyntaxKind]> =
527 Some(&[SyntaxKind::VersionStatementNode]);
528 pub const ID: &str = "UsingFallbackVersion";
530
531 pub fn new() -> Self {
533 Self(Severity::Warning)
534 }
535}
536
537impl Default for UsingFallbackVersion {
538 fn default() -> Self {
539 Self::new()
540 }
541}
542
543impl Rule for UsingFallbackVersion {
544 fn id(&self) -> &'static str {
545 Self::ID
546 }
547
548 fn description(&self) -> &'static str {
549 "Warns if interpretation of a document with an unsupported version falls back to a default."
550 }
551
552 fn explanation(&self) -> &'static str {
553 "A document with an unsupported version may have unpredictable behavior if interpreted as \
554 a different version."
555 }
556
557 fn examples(&self) -> &'static [Example] {
558 &[Example {
559 negative: LabeledSnippet {
560 label: None,
561 snippet: r#"# Not a valid version. If a fallback version is configured,
562# the document will be interpreted as that version.
563version development
564
565workflow example {
566}
567"#,
568 },
569 revised: None,
570 }]
571 }
572
573 fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]> {
574 Self::EXCEPTABLE_NODES
575 }
576
577 fn deny(&mut self) {
578 self.0 = Severity::Error;
579 }
580
581 fn severity(&self) -> Severity {
582 self.0
583 }
584}
585
586#[derive(Debug, Clone, Copy)]
588pub struct MeaninglessLintDirective(Severity);
589
590impl MeaninglessLintDirective {
591 pub const EXCEPTABLE_NODES: Option<&'static [SyntaxKind]> = None;
593 pub const ID: &str = "MeaninglessLintDirective";
595
596 pub fn new() -> Self {
598 Self(Severity::Note)
599 }
600}
601
602impl Default for MeaninglessLintDirective {
603 fn default() -> Self {
604 Self::new()
605 }
606}
607
608impl Rule for MeaninglessLintDirective {
609 fn id(&self) -> &'static str {
610 Self::ID
611 }
612
613 fn description(&self) -> &'static str {
614 "Warns if an `#@ except:` comment doesn't actually suppress a lint."
615 }
616
617 fn explanation(&self) -> &'static str {
618 "Unused `#@ except:` comments are likely leftovers of refactoring or debugging, and can \
619 reduce the clarity of the code. It is best to remove them."
620 }
621
622 fn examples(&self) -> &'static [Example] {
623 &[Example {
624 negative: LabeledSnippet {
625 label: None,
626 snippet: r#"version 1.3
627
628task do_work {
629 command <<<
630 echo "Lots of hard work!"
631 >>>
632
633 output {
634 String result = read_string(stdout())
635 }
636}
637
638# We except `UnusedCall` unnecessarily.
639workflow calculate {
640 #@ except: UnusedCall
641 call do_work
642
643 output {
644 # We're using the result here!
645 String result = do_work.result
646 }
647}
648"#,
649 },
650 revised: Some(LabeledSnippet {
651 label: Some("Consider removing the unused exception"),
652 snippet: r#"version 1.3
653
654task do_work {
655 command <<<
656 echo "Lots of hard work!"
657 >>>
658
659 output {
660 String result = read_string(stdout())
661 }
662}
663
664workflow calculate {
665 call do_work
666
667 output {
668 String result = do_work.result
669 }
670}
671"#,
672 }),
673 }]
674 }
675
676 fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]> {
677 Self::EXCEPTABLE_NODES
678 }
679
680 fn deny(&mut self) {
681 self.0 = Severity::Error;
682 }
683
684 fn severity(&self) -> Severity {
685 self.0
686 }
687}
688
689#[derive(Debug, Clone, Copy)]
691pub struct MisleadingDeclarationOrderRule(Severity);
692
693impl MisleadingDeclarationOrderRule {
694 pub const EXCEPTABLE_NODES: Option<&'static [SyntaxKind]> =
696 Some(&[SyntaxKind::TaskDefinitionNode, SyntaxKind::BoundDeclNode]);
697 pub const ID: &str = "MisleadingDeclarationOrder";
699
700 pub fn new() -> Self {
702 Self(Severity::Warning)
703 }
704}
705
706impl Default for MisleadingDeclarationOrderRule {
707 fn default() -> Self {
708 Self::new()
709 }
710}
711
712impl Rule for MisleadingDeclarationOrderRule {
713 fn id(&self) -> &'static str {
714 Self::ID
715 }
716
717 fn description(&self) -> &'static str {
718 "Warns when a variable declaration is placed after a `command` block."
719 }
720
721 fn explanation(&self) -> &'static str {
722 "WDL tasks are evaluated based on their dependency graph, not top-to-bottom. Variable \
723 declarations that appear after `command` sections are visually misleading, as they will \
724 still be evaluated _before_ the command is executed."
725 }
726
727 fn examples(&self) -> &'static [Example] {
728 &[Example {
729 negative: LabeledSnippet {
730 label: None,
731 snippet: r#"version 1.2
732
733task greet {
734 String greeting = "Hello"
735
736 command <<<
737 echo "~{greeting}, ~{name}!"
738 >>>
739
740 String name = "World"
741}
742"#,
743 },
744 revised: Some(LabeledSnippet {
745 label: None,
746 snippet: r#"version 1.2
747
748task greet {
749 String greeting = "Hello"
750 String name = "World"
751
752 command <<<
753 echo "~{greeting}, ~{name}!"
754 >>>
755}
756"#,
757 }),
758 }]
759 }
760
761 fn exceptable_nodes(&self) -> Option<&'static [SyntaxKind]> {
762 Self::EXCEPTABLE_NODES
763 }
764
765 fn deny(&mut self) {
766 self.0 = Severity::Error;
767 }
768
769 fn severity(&self) -> Severity {
770 self.0
771 }
772}
773
774#[derive(Debug, Clone, Copy)]
776pub struct KnownRulesRule(Severity);
777
778impl KnownRulesRule {
779 pub const EXCEPTABLE_NODES: Option<&'static [SyntaxKind]> =
781 Some(&[SyntaxKind::VersionStatementNode]);
782 pub const ID: &str = "KnownRules";
784
785 pub fn new() -> Self {
787 Self(Severity::Note)
788 }
789}
790
791impl Default for KnownRulesRule {
792 fn default() -> Self {
793 Self::new()
794 }
795}
796
797impl Rule for KnownRulesRule {
798 fn id(&self) -> &'static str {
799 Self::ID
800 }
801
802 fn description(&self) -> &'static str {
803 "Ensures only known rules are used in `except` directives."
804 }
805
806 fn explanation(&self) -> &'static str {
807 "When writing WDL, `except` directives are used to suppress certain rules. If a rule is \
808 unknown, nothing will be suppressed. This rule flags unknown rules as they are often \
809 mistakes."
810 }
811
812 fn examples(&self) -> &'static [Example] {
813 &[Example {
814 negative: LabeledSnippet {
815 label: None,
816 snippet: r#"#@ except: LintThatDoesNotExist
817version 1.2
818
819workflow example {
820}
821"#,
822 },
823 revised: Some(LabeledSnippet {
824 label: None,
825 snippet: r#"version 1.2
826
827workflow example {
828}
829"#,
830 }),
831 }]
832 }
833
834 fn exceptable_nodes(&self) -> Option<&'static [wdl_ast::SyntaxKind]> {
835 Self::EXCEPTABLE_NODES
836 }
837
838 fn deny(&mut self) {
839 self.0 = Severity::Error;
840 }
841
842 fn severity(&self) -> Severity {
843 self.0
844 }
845}
846
847#[derive(Debug, Clone, Copy)]
849pub struct ExceptDirectiveValidRule(Severity);
850
851impl ExceptDirectiveValidRule {
852 pub const EXCEPTABLE_NODES: Option<&'static [SyntaxKind]> =
854 Some(&[SyntaxKind::VersionStatementNode]);
855 pub const ID: &str = "ExceptDirectiveValid";
857
858 pub fn new() -> Self {
860 Self(Severity::Note)
861 }
862}
863
864impl Default for ExceptDirectiveValidRule {
865 fn default() -> Self {
866 Self::new()
867 }
868}
869
870impl Rule for ExceptDirectiveValidRule {
871 fn id(&self) -> &'static str {
872 Self::ID
873 }
874
875 fn description(&self) -> &'static str {
876 "Ensures `except` directives are placed correctly to have the intended effect."
877 }
878
879 fn explanation(&self) -> &'static str {
880 "When writing WDL, `except` directives are used to suppress certain rules. If an `except` \
881 directive is misplaced, it will have no effect. This rule flags misplaced `except` \
882 directives to ensure they are in the correct location."
883 }
884
885 fn examples(&self) -> &'static [Example] {
886 &[Example {
887 negative: LabeledSnippet {
888 label: None,
889 snippet: r#"version 1.3
890
891# UsingFallbackVersion exceptions aren't valid
892# in this context
893#@ except: UsingFallbackVersion
894workflow example {
895}
896"#,
897 },
898 revised: Some(LabeledSnippet {
899 label: None,
900 snippet: r#"#@ except: UsingFallbackVersion
901version 1.3
902
903workflow example {
904}
905"#,
906 }),
907 }]
908 }
909
910 fn exceptable_nodes(&self) -> Option<&'static [wdl_ast::SyntaxKind]> {
911 Self::EXCEPTABLE_NODES
912 }
913
914 fn deny(&mut self) {
915 self.0 = Severity::Error;
916 }
917
918 fn severity(&self) -> Severity {
919 self.0
920 }
921}
922
923#[derive(Debug, Clone, Copy)]
925pub struct CommandSectionIndentationRule(Severity);
926
927impl CommandSectionIndentationRule {
928 pub const EXCEPTABLE_NODES: Option<&'static [SyntaxKind]> = Some(&[
930 SyntaxKind::VersionStatementNode,
931 SyntaxKind::CommandSectionNode,
932 ]);
933 pub const ID: &str = "CommandSectionIndentation";
935
936 pub fn new() -> Self {
938 Self(Severity::Warning)
939 }
940}
941
942impl Default for CommandSectionIndentationRule {
943 fn default() -> Self {
944 Self::new()
945 }
946}
947
948impl Rule for CommandSectionIndentationRule {
949 fn id(&self) -> &'static str {
950 Self::ID
951 }
952
953 fn description(&self) -> &'static str {
954 "Ensures consistent indentation (no mixed spaces/tabs) within command sections."
955 }
956
957 fn explanation(&self) -> &'static str {
958 "Mixing indentation (tab and space) characters within the command line causes leading \
959 whitespace stripping to be skipped. Commands may be whitespace sensitive, and skipping \
960 the whitespace stripping step may cause unexpected behavior."
961 }
962
963 fn examples(&self) -> &'static [Example] {
964 &[Example {
965 negative: LabeledSnippet {
966 label: None,
967 snippet: r#"version 1.3
968
969task say_greetings {
970 input {
971 String name
972 }
973
974 command <<<
975 # this line is prefixed with tabs
976 echo "Hello, ~{name}!"
977 # this line is prefixed with spaces
978 echo "Goodbye, ~{name}!"
979 >>>
980}
981"#,
982 },
983 revised: Some(LabeledSnippet {
984 label: None,
985 snippet: r#"version 1.3
986
987task say_greetings {
988 input {
989 String name
990 }
991
992 command <<<
993 # this line is prefixed with spaces
994 echo "Hello, ~{name}!"
995 # this line is prefixed with spaces
996 echo "Goodbye, ~{name}!"
997 >>>
998}
999"#,
1000 }),
1001 }]
1002 }
1003
1004 fn exceptable_nodes(&self) -> Option<&'static [wdl_ast::SyntaxKind]> {
1005 Self::EXCEPTABLE_NODES
1006 }
1007
1008 fn deny(&mut self) {
1009 self.0 = Severity::Error;
1010 }
1011
1012 fn severity(&self) -> Severity {
1013 self.0
1014 }
1015}
1016
1017#[derive(Debug, Clone, Copy)]
1019pub struct DeprecatedObjectRule(Severity);
1020
1021impl DeprecatedObjectRule {
1022 pub const EXCEPTABLE_NODES: Option<&'static [SyntaxKind]> = Some(&[
1024 SyntaxKind::VersionStatementNode,
1025 SyntaxKind::TaskDefinitionNode,
1026 SyntaxKind::WorkflowDefinitionNode,
1027 SyntaxKind::BoundDeclNode,
1028 SyntaxKind::UnboundDeclNode,
1029 ]);
1030 pub const ID: &str = "DeprecatedObject";
1032
1033 pub fn new() -> Self {
1035 Self(Severity::Warning)
1036 }
1037}
1038
1039impl Default for DeprecatedObjectRule {
1040 fn default() -> Self {
1041 Self::new()
1042 }
1043}
1044
1045impl Rule for DeprecatedObjectRule {
1046 fn id(&self) -> &'static str {
1047 Self::ID
1048 }
1049
1050 fn description(&self) -> &'static str {
1051 "Ensures that the deprecated `Object` types are not used."
1052 }
1053
1054 fn explanation(&self) -> &'static str {
1055 "WDL `Object` types are officially deprecated and will be removed in the next major WDL release.
1056
1057`Object`s existed prior to better containers, such as `Map`s and `Struct`s, being \
1058introduced into the language. Unfortunately, though these better alternatives did exist at \
1059the time of the v1.0 release, the type was not removed. It was later decided \
1060that `Object`s overlapped with `Map`s and `Struct`s in functionality, and the type was marked for removal.
1061
1062See this issue for more details: <https://github.com/openwdl/wdl/pull/228>."
1063 }
1064
1065 fn examples(&self) -> &'static [Example] {
1066 &[Example {
1067 negative: LabeledSnippet {
1068 label: None,
1069 snippet: r#"version 1.2
1070
1071workflow example {
1072 Object person = object {
1073 name: "Jimmy",
1074 age: 55,
1075 }
1076}
1077"#,
1078 },
1079 revised: Some(LabeledSnippet {
1080 label: Some("Consider switching to a `Struct` or `Map`"),
1081 snippet: r#"version 1.2
1082
1083struct Person {
1084 String name
1085 Int age
1086}
1087
1088workflow example {
1089 Person person = Person {
1090 name: "Jimmy",
1091 age: 55,
1092 }
1093}
1094"#,
1095 }),
1096 }]
1097 }
1098
1099 fn exceptable_nodes(&self) -> Option<&'static [wdl_ast::SyntaxKind]> {
1100 Self::EXCEPTABLE_NODES
1101 }
1102
1103 fn deny(&mut self) {
1104 self.0 = Severity::Error;
1105 }
1106
1107 fn severity(&self) -> Severity {
1108 self.0
1109 }
1110}
1111
1112#[derive(Debug, Clone, Copy)]
1114pub struct DeprecatedPlaceholderRule(Severity);
1115
1116impl DeprecatedPlaceholderRule {
1117 pub const EXCEPTABLE_NODES: Option<&'static [SyntaxKind]> = Some(&[
1119 SyntaxKind::VersionStatementNode,
1120 SyntaxKind::TaskDefinitionNode,
1121 SyntaxKind::WorkflowDefinitionNode,
1122 SyntaxKind::PlaceholderNode,
1123 ]);
1124 pub const ID: &str = "DeprecatedPlaceholder";
1126
1127 pub fn new() -> Self {
1129 Self(Severity::Warning)
1130 }
1131}
1132
1133impl Default for DeprecatedPlaceholderRule {
1134 fn default() -> Self {
1135 Self::new()
1136 }
1137}
1138
1139impl Rule for DeprecatedPlaceholderRule {
1140 fn id(&self) -> &'static str {
1141 Self::ID
1142 }
1143
1144 fn description(&self) -> &'static str {
1145 "Ensures that deprecated expression placeholder options are not used."
1146 }
1147
1148 fn explanation(&self) -> &'static str {
1149 "Expression placeholder options were deprecated in WDL v1.1 and will be removed in the \
1150 next major WDL version.
1151
1152 - `sep` placeholder options should be replaced by the `sep()` standard library function.
1153 - `true/false` placeholder options should be replaced with `if`/`else` statements.
1154 - `default` placeholder options should be replaced by the `select_first()` standard \
1155 library function.
1156 - `${}` interpolation placeholders should be replaced by `~{}` interpolation placeholders.
1157
1158
1159This rule only evaluates for WDL V1 documents with a version of v1.1 or later, as this was the \
1160 version where the deprecation was introduced."
1161 }
1162
1163 fn examples(&self) -> &'static [Example] {
1164 &[Example {
1165 negative: LabeledSnippet {
1166 label: None,
1167 snippet: r#"version 1.2
1168
1169workflow example {
1170 Array[String] names = [
1171 "James",
1172 "Jimmy",
1173 "John",
1174 ]
1175 String names_separated = "~{sep="," names}"
1176 String names_interpolated = "${names_separated}"
1177}
1178"#,
1179 },
1180 revised: Some(LabeledSnippet {
1181 label: None,
1182 snippet: r#"version 1.2
1183
1184workflow example {
1185 Array[String] names = [
1186 "James",
1187 "Jimmy",
1188 "John",
1189 ]
1190 String names_separated = "~{sep(",", names)}"
1191 String names_interpolated = "~{names_separated}"
1192}
1193"#,
1194 }),
1195 }]
1196 }
1197
1198 fn exceptable_nodes(&self) -> Option<&'static [wdl_ast::SyntaxKind]> {
1199 Self::EXCEPTABLE_NODES
1200 }
1201
1202 fn deny(&mut self) {
1203 self.0 = Severity::Error;
1204 }
1205
1206 fn severity(&self) -> Severity {
1207 self.0
1208 }
1209}
1210
1211#[derive(Debug, Clone, Copy)]
1213pub struct DeprecatedRuntimeSectionRule(Severity);
1214
1215impl DeprecatedRuntimeSectionRule {
1216 pub const EXCEPTABLE_NODES: Option<&'static [SyntaxKind]> = Some(&[
1218 SyntaxKind::VersionStatementNode,
1219 SyntaxKind::TaskDefinitionNode,
1220 SyntaxKind::RuntimeSectionNode,
1221 ]);
1222 pub const ID: &str = "DeprecatedRuntimeSection";
1224
1225 pub fn new() -> Self {
1227 Self(Severity::Warning)
1228 }
1229}
1230
1231impl Default for DeprecatedRuntimeSectionRule {
1232 fn default() -> Self {
1233 Self::new()
1234 }
1235}
1236
1237impl Rule for DeprecatedRuntimeSectionRule {
1238 fn id(&self) -> &'static str {
1239 Self::ID
1240 }
1241
1242 fn description(&self) -> &'static str {
1243 "Detects deprecated `runtime` sections."
1244 }
1245
1246 fn explanation(&self) -> &'static str {
1247 "The `runtime` section is deprecated in WDL v1.2 and later. Replace it with a \
1248 `requirements` section."
1249 }
1250
1251 fn examples(&self) -> &'static [Example] {
1252 &[Example {
1253 negative: LabeledSnippet {
1254 label: None,
1255 snippet: r#"version 1.2
1256
1257task say_hello {
1258 input {
1259 String name
1260 }
1261
1262 command <<<
1263 echo "Hello, ~{name}!"
1264 >>>
1265
1266 runtime {
1267 container: "ubuntu:latest"
1268 }
1269}
1270"#,
1271 },
1272 revised: Some(LabeledSnippet {
1273 label: None,
1274 snippet: r#"version 1.2
1275
1276task say_hello {
1277 input {
1278 String name
1279 }
1280
1281 command <<<
1282 echo "Hello, ~{name}!"
1283 >>>
1284
1285 requirements {
1286 container: "ubuntu:latest"
1287 }
1288}
1289"#,
1290 }),
1291 }]
1292 }
1293
1294 fn exceptable_nodes(&self) -> Option<&'static [wdl_ast::SyntaxKind]> {
1295 Self::EXCEPTABLE_NODES
1296 }
1297
1298 fn deny(&mut self) {
1299 self.0 = Severity::Error;
1300 }
1301
1302 fn severity(&self) -> Severity {
1303 self.0
1304 }
1305}