1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! Lint rules for Workflow Description Language (WDL) documents.
#![doc = include_str!("../RULES.md")]
//! # Examples
//!
//! An example of parsing a WDL document and linting it:
//!
//! ```rust
//! # let source = "version 1.1\nworkflow test {}";
//! use wdl_lint::ast::Document;
//! use wdl_lint::ast::Validator;
//! use wdl_lint::LintVisitor;
//!
//! match Document::parse(source).into_result() {
//!     Ok(document) => {
//!         let mut validator = Validator::default();
//!         validator.add_visitor(LintVisitor::default());
//!         match validator.validate(&document) {
//!             Ok(_) => {
//!                 // The document was valid WDL and passed all lints
//!             }
//!             Err(diagnostics) => {
//!                 // Handle the failure to validate
//!             }
//!         }
//!     }
//!     Err(diagnostics) => {
//!         // Handle the failure to parse
//!     }
//! }
//! ```

#![warn(missing_docs)]
#![warn(rust_2018_idioms)]
#![warn(rust_2021_compatibility)]
#![warn(missing_debug_implementations)]
#![warn(clippy::missing_docs_in_private_items)]
#![warn(rustdoc::broken_intra_doc_links)]

use wdl_ast::Diagnostics;
use wdl_ast::Visitor;

pub mod rules;
mod tags;
pub(crate) mod util;
mod visitor;

pub use tags::*;
pub use visitor::*;
pub use wdl_ast as ast;

/// A trait implemented by lint rules.
pub trait Rule: Visitor<State = Diagnostics> {
    /// The unique identifier for the lint rule.
    ///
    /// The identifier is required to be pascal case.
    ///
    /// This is what will show up in style guides and is the identifier by which
    /// a lint rule is disabled.
    fn id(&self) -> &'static str;

    /// A short, single sentence description of the lint rule.
    fn description(&self) -> &'static str;

    /// Get the long-form explanation of the lint rule.
    fn explanation(&self) -> &'static str;

    /// Get the tags of the lint rule.
    fn tags(&self) -> TagSet;

    /// Gets the optional URL of the lint rule.
    fn url(&self) -> Option<&'static str> {
        None
    }
}

/// Gets the default rule set.
pub fn rules() -> Vec<Box<dyn Rule>> {
    let rules: Vec<Box<dyn Rule>> = vec![
        Box::new(rules::DoubleQuotesRule),
        Box::new(rules::NoCurlyCommandsRule),
        Box::new(rules::SnakeCaseRule::default()),
        Box::new(rules::MissingRuntimeRule),
        Box::new(rules::EndingNewlineRule),
        Box::new(rules::PreambleWhitespaceRule::default()),
        Box::new(rules::PreambleCommentsRule::default()),
        Box::new(rules::MatchingParameterMetaRule),
        Box::new(rules::WhitespaceRule::default()),
        Box::new(rules::CommandSectionMixedIndentationRule),
        Box::new(rules::ImportPlacementRule::default()),
        Box::new(rules::PascalCaseRule),
        Box::new(rules::ImportWhitespaceRule),
        Box::new(rules::MissingMetasRule),
        Box::new(rules::MissingOutputRule),
        Box::new(rules::ImportSortRule),
        Box::new(rules::InputNotSortedRule),
        Box::new(rules::LineWidthRule::default()),
        Box::new(rules::InconsistentNewlinesRule::default()),
    ];

    // Ensure all the rule ids are unique and pascal case
    #[cfg(debug_assertions)]
    {
        use convert_case::Case;
        use convert_case::Casing;
        let mut set = std::collections::HashSet::new();
        for r in rules.iter() {
            if r.id().to_case(Case::Pascal) != r.id() {
                panic!("lint rule id `{id}` is not pascal case", id = r.id());
            }

            if !set.insert(r.id()) {
                panic!("duplicate rule id `{id}`", id = r.id());
            }
        }
    }

    rules
}