Skip to content

Commit

Permalink
feat: adds lint to suggest replacing curly command blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
claymcleod committed Nov 23, 2023
1 parent 59543c3 commit 4ee030f
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 2 deletions.
11 changes: 10 additions & 1 deletion wdl-grammar/src/v1.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
//! WDL 1.x
//! WDL 1.x.
//!
//! ## Linting Rules
//!
//! The following linting rules are supported for WDL 1.x:
//!
//! | Name | Code | Group | Module |
//! |:--------------------|:---------:|:--------:|:-----------------------------:|
//! | `whitespace` | `v1::001` | Style | [Link](lint::Whitespace) |
//! | `no_curly_commands` | `v1::002` | Pedantic | [Link](lint::NoCurlyCommands) |

use pest::Parser as _;

Expand Down
4 changes: 3 additions & 1 deletion wdl-grammar/src/v1/lint.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
//! Lint rules for WDL 1.x.

mod no_curly_commands;
mod whitespace;

pub use no_curly_commands::NoCurlyCommands;
pub use whitespace::Whitespace;

use crate::core::lint::Rule;
use crate::v1;

/// Gets all lint rules available for WDL 1.x.
pub fn rules() -> Vec<Box<dyn Rule<v1::Rule>>> {
vec![Box::new(Whitespace)]
vec![Box::new(Whitespace), Box::new(NoCurlyCommands)]
}
119 changes: 119 additions & 0 deletions wdl-grammar/src/v1/lint/no_curly_commands.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//! Replace curly command blocks with heredoc command blocks.

use std::num::NonZeroUsize;

use pest::iterators::Pairs;

use crate::core::lint;
use crate::core::lint::Group;
use crate::core::lint::Rule;
use crate::core::Code;
use crate::core::Location;
use crate::v1;
use crate::Version;

/// Replace curly command blocks with heredoc command blocks.
///
/// Curly command blocks are no longer considered idiomatic WDL
/// ([link](https://github.com/openwdl/wdl/blob/main/versions/1.1/SPEC.md#command-section)).
/// Idiomatic WDL code uses heredoc command blocks instead.
#[derive(Debug)]
pub struct NoCurlyCommands;

impl NoCurlyCommands {
/// Creates an error corresponding to a line with a trailing tab.
fn no_curly_commands(&self, line_no: NonZeroUsize, col_no: NonZeroUsize) -> lint::Warning
where
Self: Rule<v1::Rule>,
{
// SAFETY: this error is written so that it will always unwrap.
lint::warning::Builder::default()
.code(self.code())
.level(lint::Level::Medium)
.group(lint::Group::Pedantic)
.location(Location::LineCol { line_no, col_no })
.subject("curly command found")
.body(
"Command blocks using curly braces (`{}`) are considered less
idiomatic than heredoc commands.",
)
.fix("Replace the curly command block with a heredoc command block.")
.try_build()
.unwrap()
}
}

impl Rule<v1::Rule> for NoCurlyCommands {
fn code(&self) -> Code {
// SAFETY: this manually crafted to unwrap successfully every time.
Code::try_new(Version::V1, 2).unwrap()
}

fn group(&self) -> lint::Group {
Group::Style
}

fn check(&self, tree: Pairs<'_, v1::Rule>) -> lint::Result {
let mut results = Vec::new();

for node in tree.flatten() {
if node.as_rule() == v1::Rule::command_curly {
let (line, col) = node.line_col();
results.push(self.no_curly_commands(
NonZeroUsize::try_from(line)?,
NonZeroUsize::try_from(col)?,
));
}
}

match results.is_empty() {
true => Ok(None),
false => Ok(Some(results)),
}
}
}

#[cfg(test)]
mod tests {
use pest::Parser as _;

use crate::core::lint::Rule as _;
use crate::v1::parse::Parser;
use crate::v1::Rule;

use super::*;

#[test]
fn it_catches_a_curly_command() -> Result<(), Box<dyn std::error::Error>> {
let tree = Parser::parse(Rule::command_curly, "command {}")?;
let warning = NoCurlyCommands.check(tree)?.unwrap();

assert_eq!(warning.len(), 1);
assert_eq!(
warning.first().unwrap().to_string(),
"[v1::002::Pedantic/Medium] curly command found at 1:1"
);

Ok(())
}

#[test]
fn it_does_not_catch_a_heredoc_command() -> Result<(), Box<dyn std::error::Error>> {
let tree = Parser::parse(Rule::command_heredoc, "command <<<>>>")?;
assert!(NoCurlyCommands.check(tree)?.is_none());

Ok(())
}

#[test]
fn it_unwraps_a_no_curly_commands_error() {
let warning = NoCurlyCommands.no_curly_commands(
NonZeroUsize::try_from(1).unwrap(),
NonZeroUsize::try_from(1).unwrap(),
);
assert_eq!(
warning.to_string(),
"[v1::002::Pedantic/Medium] curly command found at 1:1"
)
}
}

0 comments on commit 4ee030f

Please sign in to comment.