Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add lint warning for newline_eof #18

Merged
merged 1 commit into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,272 changes: 436 additions & 836 deletions Gauntlet.toml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ repository. Note that the information may be out of sync with released packages.
| `mixed_indentation` | `v1::W004` | Style | [`wdl-grammar`][wdl-grammar-lints] |
| `missing_runtime_block` | `v1::W005` | Completeness | [`wdl-grammar`][wdl-grammar-lints] |
| `snake_case` | `v1::W006` | Naming | [`wdl-grammar`][wdl-grammar-lints] |
| `newline_eof` | `v1::W007` | Spacing | [`wdl-grammar`][wdl-grammar-lints] |

[wdl-ast-lints]: https://docs.rs/wdl-ast/latest/wdl_ast/v1/index.html#lint-rules
[wdl-ast-validation]: https://docs.rs/wdl-ast/latest/wdl_ast/v1/index.html#validation-rules
Expand Down
4 changes: 4 additions & 0 deletions wdl-core/src/concern/lint/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub enum Group {
/// Rules associated with the names of WDL elements.
Naming,

/// Rules associated with the whitespace in a document.
claymcleod marked this conversation as resolved.
Show resolved Hide resolved
Spacing,

/// Rules associated with the style of a document.
Style,

Expand All @@ -26,6 +29,7 @@ impl std::fmt::Display for Group {
match self {
Group::Completeness => write!(f, "Completeness"),
Group::Naming => write!(f, "Naming"),
Group::Spacing => write!(f, "Spacing"),
Group::Style => write!(f, "Style"),
Group::Pedantic => write!(f, "Pedantic"),
}
Expand Down
1 change: 1 addition & 0 deletions wdl-grammar/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
@markjschreiber).
* Adds the `snake_case` rule that ensures all tasks, workflows, and variables
are snakecase (#13, contributed by @simojoe).
* Adds the `newline_eof` rule for tasks (#18, contributed by @simojoe).

## 0.2.0 - 12-17-2023

Expand Down
4 changes: 3 additions & 1 deletion wdl-grammar/src/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
//! | `mixed_indentation` | `v1::W004` | Style | [Link](lint::MixedIndentation) |
//! | `missing_runtime_block` | `v1::W005` | Completeness | [Link](lint::MissingRuntimeBlock) |
//! | `snake_case` | `v1::W006` | Naming | [Link](lint::SnakeCase) |
//! | `newline_eof` | `v1::W007` | Spacing | [Link](lint::NewlineEOF) |

use pest::iterators::Pair;
use pest::Parser as _;
Expand Down Expand Up @@ -121,7 +122,8 @@ pub type Result<'a> = wdl_core::parse::Result<Pair<'a, crate::v1::Rule>>;
/// cpu: 1
/// memory: "2GiB"
/// }
/// }"#,
/// }
/// "#,
/// )
/// .unwrap();
///
Expand Down
4 changes: 4 additions & 0 deletions wdl-grammar/src/v1/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ use pest::iterators::Pair;

mod missing_runtime_block;
mod mixed_indentation;
mod newline_eof;
mod no_curly_commands;
mod snake_case;
mod whitespace;

pub use missing_runtime_block::MissingRuntimeBlock;
pub use mixed_indentation::MixedIndentation;
pub use newline_eof::NewlineEOF;
pub use no_curly_commands::NoCurlyCommands;
pub use snake_case::SnakeCase;
pub use whitespace::Whitespace;
Expand All @@ -27,5 +29,7 @@ pub fn rules<'a>() -> Vec<Box<dyn wdl_core::concern::lint::Rule<&'a Pair<'a, cra
Box::new(MissingRuntimeBlock),
// v1::W006
Box::new(SnakeCase),
// v1::W007
Box::new(NewlineEOF),
]
}
163 changes: 163 additions & 0 deletions wdl-grammar/src/v1/lint/newline_eof.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
//! WDL files must end with a newline.

use std::collections::VecDeque;

use nonempty::NonEmpty;
use pest::iterators::Pair;
use wdl_core::concern::code;
use wdl_core::concern::lint;
use wdl_core::concern::lint::Group;
use wdl_core::concern::lint::Rule;
use wdl_core::concern::Code;
use wdl_core::file::Location;
use wdl_core::Version;

use crate::v1;

/// Detects missing newline at the EOF
#[derive(Debug)]
pub struct NewlineEOF;

impl<'a> NewlineEOF {
/// Creates a warning for a file not ending with a newline
fn missing_newline_at_eof(&self, location: Location) -> lint::Warning
where
Self: Rule<&'a Pair<'a, v1::Rule>>,
{
// SAFETY: this error is written so that it will always unwrap.
lint::warning::Builder::default()
.code(self.code())
.level(lint::Level::Low)
.group(self.group())
.push_location(location)
.subject("missing newline at the end of the file")
.body("There should always be a newline at the end of a WDL file.")
.fix("Add a newline at the end of the file.")
.try_build()
.unwrap()
}

/// Creates a warning for a file ending with more than one newline
fn no_empty_line_at_eof(&self, location: Location) -> lint::Warning
where
Self: Rule<&'a Pair<'a, v1::Rule>>,
{
// SAFETY: this error is written so that it will always unwrap.
lint::warning::Builder::default()
.code(self.code())
.level(lint::Level::Low)
.group(self.group())
.push_location(location)
.subject("multiple empty lines at the end of file")
.body("There should only be one newline at the end of a WDL file.")
.fix("Remove all but one empty line at the end of the file.")
.try_build()
.unwrap()
}
}

impl<'a> Rule<&'a Pair<'a, v1::Rule>> for NewlineEOF {
fn code(&self) -> Code {
// SAFETY: this manually crafted to unwrap successfully every time.
Code::try_new(code::Kind::Warning, Version::V1, 7).unwrap()
}

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

fn check(&self, tree: &'a Pair<'_, v1::Rule>) -> lint::Result {
let mut warnings: VecDeque<_> = VecDeque::new();

let mut iter = tree.clone().into_inner().rev();

if let (Some(node_eof), Some(node1), Some(node2)) = (iter.next(), iter.next(), iter.next())
{
if node1.as_str() != "\n" {
let location =
Location::try_from(node_eof.as_span()).map_err(lint::Error::Location)?;
warnings.push_back(self.missing_newline_at_eof(location))
}

if node1.as_str() == "\n" && node2.as_str() == "\n" {
let location =
Location::try_from(node1.as_span()).map_err(lint::Error::Location)?;
warnings.push_back(self.no_empty_line_at_eof(location))
}
}

match warnings.pop_front() {
Some(front) => {
let mut results = NonEmpty::new(front);
results.extend(warnings);
Ok(Some(results))
}
None => Ok(None),
}
}
}

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

use super::*;
use crate::v1::parse::Parser;
use crate::v1::Rule;

#[test]
fn it_catches_no_trailing_newline() -> Result<(), Box<dyn std::error::Error>> {
let tree = Parser::parse(
Rule::document,
r#"version 1.0
workflow test {}"#,
)?
.next()
.unwrap();

let warnings = NewlineEOF.check(&tree)?.unwrap();
assert_eq!(warnings.len(), 1);
assert_eq!(
warnings.first().to_string(),
"[v1::W007::Spacing/Low] missing newline at the end of the file (2:17-2:17)"
);
Ok(())
}

#[test]
fn it_catches_an_empty_newline_eof() -> Result<(), Box<dyn std::error::Error>> {
let tree = Parser::parse(
Rule::document,
r#"version 1.0
workflow test {}

"#,
)?
.next()
.unwrap();

let warnings = NewlineEOF.check(&tree)?.unwrap();
assert_eq!(warnings.len(), 1);
assert_eq!(
warnings.first().to_string(),
"[v1::W007::Spacing/Low] multiple empty lines at the end of file (3:1-4:1)"
);
Ok(())
}

#[test]
fn it_ignores_a_correctly_formatted_eof() -> Result<(), Box<dyn std::error::Error>> {
let tree = Parser::parse(
Rule::document,
r#"version 1.0
workflow test {}
"#,
)?
.next()
.unwrap();

assert!(NewlineEOF.check(&tree)?.is_none());
Ok(())
}
}
Loading