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 error;
|
||||||
mod result;
|
mod result;
|
||||||
mod rule;
|
mod rule;
|
||||||
|
mod rules;
|
||||||
mod severity;
|
mod severity;
|
||||||
|
mod visitor;
|
||||||
use result::LinterResult;
|
|
||||||
|
|
||||||
pub trait Lintable {
|
|
||||||
fn lint(&self) -> LinterResult;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
use severity::LinterSeverity;
|
use severity::LinterSeverity;
|
||||||
|
|
||||||
pub trait LinterRule<T> {
|
pub trait LinterRule {
|
||||||
fn is_valid(node: T) -> bool;
|
fn is_valid(&self) -> bool;
|
||||||
fn message(node: T) -> String;
|
fn message(&self) -> String;
|
||||||
fn description(node: T) -> Option<String>;
|
fn description(&self) -> Option<String>;
|
||||||
fn suggestion(node: T) -> Option<String>;
|
fn suggestion(&self) -> Option<String>;
|
||||||
fn severity(node: T) -> LinterSeverity;
|
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