First pass at a linter.

This commit is contained in:
James Harton 2018-09-15 17:21:22 +12:00
parent 2ff1c48753
commit 1641b4f86a
9 changed files with 179 additions and 67 deletions

22
linter/src/ast/atom.rs Normal file
View 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());
}
}

View file

@ -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());
}
}

View file

@ -1 +1 @@
mod atom_should_be_snake_case;
mod atom;

View file

@ -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;

View file

@ -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()
}
}

View 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
View file

@ -0,0 +1,2 @@
pub mod atom_should_be_snake_case;
pub mod type_name_should_be_camel_case;

View 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
View file

@ -0,0 +1,3 @@
pub trait Visitor<'a> {
fn children(&self) -> Vec<Box<Visitor>>;
}