First pass at a linter.
This commit is contained in:
parent
2ff1c48753
commit
1641b4f86a
9 changed files with 179 additions and 67 deletions
22
linter/src/ast/atom.rs
Normal file
22
linter/src/ast/atom.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use huia_parser::Atom;
|
||||
use rule::LinterRule;
|
||||
use rules::atom_should_be_snake_case::AtomShouldBeSnakeCase;
|
||||
use visitor::Visitor;
|
||||
|
||||
impl<'a> Visitor<'a> for Atom<'a> {
|
||||
fn children(&self) -> Vec<Box<Visitor>> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use huia_parser::Parse;
|
||||
|
||||
#[test]
|
||||
fn has_no_children() {
|
||||
let atom = Atom::parse(":MartyMcFly").unwrap();
|
||||
assert!(atom.children().is_empty());
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
use heck::SnakeCase;
|
||||
use huia_parser::ast::Atom;
|
||||
use rule::LinterRule;
|
||||
use severity::LinterSeverity;
|
||||
|
||||
pub struct AtomShouldBeSnakeCase();
|
||||
|
||||
impl<'a> LinterRule<Atom<'a>> for AtomShouldBeSnakeCase {
|
||||
fn is_valid(atom: Atom<'a>) -> bool {
|
||||
atom.name().to_snake_case() == atom.name()
|
||||
}
|
||||
|
||||
fn message(_atom: Atom<'a>) -> String {
|
||||
"Atom should be snake case".to_string()
|
||||
}
|
||||
|
||||
fn description(_atom: Atom<'a>) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn suggestion(atom: Atom<'a>) -> Option<String> {
|
||||
let original = atom.name();
|
||||
let replacement = atom.name().to_snake_case();
|
||||
let message = format!("Replace \":{}\" with \":{}\"", original, replacement);
|
||||
Some(message)
|
||||
}
|
||||
|
||||
fn severity(_atom: Atom<'a>) -> LinterSeverity {
|
||||
LinterSeverity::Warn
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use huia_parser::grammar::{parse_str, Rule};
|
||||
|
||||
#[test]
|
||||
fn camel_case_atom() {
|
||||
let pair = parse_str(":martyMcFly", Rule::atom);
|
||||
let atom = Atom::from(pair);
|
||||
assert!(!AtomShouldBeSnakeCase::is_valid(atom.clone()));
|
||||
assert_eq!(
|
||||
AtomShouldBeSnakeCase::message(atom.clone()),
|
||||
"Atom should be snake case"
|
||||
);
|
||||
assert!(AtomShouldBeSnakeCase::description(atom.clone()).is_none());
|
||||
assert_eq!(
|
||||
AtomShouldBeSnakeCase::suggestion(atom.clone()).unwrap(),
|
||||
"Replace \":martyMcFly\" with \":marty_mc_fly\""
|
||||
);
|
||||
assert!(AtomShouldBeSnakeCase::severity(atom).is_warn());
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
mod atom_should_be_snake_case;
|
||||
mod atom;
|
||||
|
|
|
@ -5,10 +5,6 @@ mod ast;
|
|||
mod error;
|
||||
mod result;
|
||||
mod rule;
|
||||
mod rules;
|
||||
mod severity;
|
||||
|
||||
use result::LinterResult;
|
||||
|
||||
pub trait Lintable {
|
||||
fn lint(&self) -> LinterResult;
|
||||
}
|
||||
mod visitor;
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
use severity::LinterSeverity;
|
||||
|
||||
pub trait LinterRule<T> {
|
||||
fn is_valid(node: T) -> bool;
|
||||
fn message(node: T) -> String;
|
||||
fn description(node: T) -> Option<String>;
|
||||
fn suggestion(node: T) -> Option<String>;
|
||||
fn severity(node: T) -> LinterSeverity;
|
||||
pub trait LinterRule {
|
||||
fn is_valid(&self) -> bool;
|
||||
fn message(&self) -> String;
|
||||
fn description(&self) -> Option<String>;
|
||||
fn suggestion(&self) -> Option<String>;
|
||||
fn severity(&self) -> LinterSeverity;
|
||||
|
||||
fn is_invalid(&self) -> bool {
|
||||
!self.is_valid()
|
||||
}
|
||||
}
|
||||
|
|
75
linter/src/rules/atom_should_be_snake_case.rs
Normal file
75
linter/src/rules/atom_should_be_snake_case.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
use heck::SnakeCase;
|
||||
use huia_parser::Atom;
|
||||
use rule::LinterRule;
|
||||
use severity::LinterSeverity;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AtomShouldBeSnakeCase<'a>(&'a Atom<'a>);
|
||||
|
||||
impl<'a> LinterRule for AtomShouldBeSnakeCase<'a> {
|
||||
fn is_valid(&self) -> bool {
|
||||
self.0.name().to_snake_case() == self.0.name()
|
||||
}
|
||||
|
||||
fn message(&self) -> String {
|
||||
"Atom should be snake_case".to_string()
|
||||
}
|
||||
|
||||
fn description(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn suggestion(&self) -> Option<String> {
|
||||
let original = self.0.name();
|
||||
let replacement = original.to_snake_case();
|
||||
let message = format!("Replace \":{}\" with \":{}\"", original, replacement);
|
||||
Some(message)
|
||||
}
|
||||
|
||||
fn severity(&self) -> LinterSeverity {
|
||||
LinterSeverity::Warn
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Atom<'a>> for AtomShouldBeSnakeCase<'a> {
|
||||
fn from(atom: &'a Atom<'a>) -> Self {
|
||||
AtomShouldBeSnakeCase(atom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use huia_parser::Parse;
|
||||
|
||||
#[test]
|
||||
fn camel_case_atom() {
|
||||
let atom = Atom::parse(":MartyMcFly").unwrap();
|
||||
let rule = AtomShouldBeSnakeCase::from(&atom);
|
||||
assert!(!rule.is_valid());
|
||||
assert_eq!(rule.message(), "Atom should be snake_case");
|
||||
assert_eq!(
|
||||
rule.suggestion().unwrap(),
|
||||
"Replace \":MartyMcFly\" with \":marty_mc_fly\""
|
||||
);
|
||||
assert!(rule.severity().is_warn());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snake_case_atom() {
|
||||
let atom = Atom::parse(":marty_mcfly").unwrap();
|
||||
let rule = AtomShouldBeSnakeCase::from(&atom);
|
||||
assert!(rule.is_valid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn screaming_snake_case_atom() {
|
||||
let atom = Atom::parse(":MARTY_MCFLY").unwrap();
|
||||
let rule = AtomShouldBeSnakeCase::from(&atom);
|
||||
assert!(!rule.is_valid());
|
||||
assert_eq!(
|
||||
rule.suggestion().unwrap(),
|
||||
"Replace \":MARTY_MCFLY\" with \":marty_mcfly\""
|
||||
);
|
||||
}
|
||||
}
|
2
linter/src/rules/mod.rs
Normal file
2
linter/src/rules/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod atom_should_be_snake_case;
|
||||
pub mod type_name_should_be_camel_case;
|
64
linter/src/rules/type_name_should_be_camel_case.rs
Normal file
64
linter/src/rules/type_name_should_be_camel_case.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use heck::CamelCase;
|
||||
use huia_parser::TypeName;
|
||||
use rule::LinterRule;
|
||||
use severity::LinterSeverity;
|
||||
|
||||
pub struct TypeNameShouldBeCamelCase<'a>(&'a TypeName<'a>);
|
||||
|
||||
impl<'a> LinterRule for TypeNameShouldBeCamelCase<'a> {
|
||||
fn is_valid(&self) -> bool {
|
||||
self.0.name().to_camel_case() == self.0.name()
|
||||
}
|
||||
|
||||
fn message(&self) -> String {
|
||||
"Type name should be CamelCase".to_string()
|
||||
}
|
||||
|
||||
fn description(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn suggestion(&self) -> Option<String> {
|
||||
let original = self.0.name();
|
||||
let replacement = original.to_camel_case();
|
||||
let message = format!("Replace \"{}\" with \"{}\"", original, replacement);
|
||||
Some(message)
|
||||
}
|
||||
|
||||
fn severity(&self) -> LinterSeverity {
|
||||
LinterSeverity::Warn
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a TypeName<'a>> for TypeNameShouldBeCamelCase<'a> {
|
||||
fn from(type_name: &'a TypeName<'a>) -> Self {
|
||||
TypeNameShouldBeCamelCase(type_name)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use huia_parser::Parse;
|
||||
|
||||
#[test]
|
||||
fn screaming_snake_case() {
|
||||
let type_name = TypeName::parse("MARTY_MC_FLY").unwrap();
|
||||
let rule = TypeNameShouldBeCamelCase::from(&type_name);
|
||||
assert!(!rule.is_valid());
|
||||
assert_eq!(rule.message(), "Type name should be CamelCase");
|
||||
assert_eq!(
|
||||
rule.suggestion().unwrap(),
|
||||
"Replace \"MARTY_MC_FLY\" with \"MartyMcFly\""
|
||||
);
|
||||
assert!(rule.severity().is_warn());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn camel_case_type_name() {
|
||||
let type_name = TypeName::parse("MartyMcFly").unwrap();
|
||||
let rule = TypeNameShouldBeCamelCase::from(&type_name);
|
||||
assert!(rule.is_valid());
|
||||
}
|
||||
|
||||
}
|
3
linter/src/visitor.rs
Normal file
3
linter/src/visitor.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub trait Visitor<'a> {
|
||||
fn children(&self) -> Vec<Box<Visitor>>;
|
||||
}
|
Reference in a new issue