├── .gitignore ├── Makefile ├── README.md ├── Cargo.toml ├── src ├── lint.rs ├── lib.rs └── parse.rs └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.rs.bk 4 | src/main.rs 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | FOO-BAR := 123 2 | 3 | foo-bar: 4 | @echo $(FOO-BAR) 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mint 2 | 3 | > a makefile linter 4 | 5 | Doug Tangren (softprops) 2016 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mint" 3 | version = "0.1.0" 4 | authors = ["softprops "] 5 | description = "a make file linter" 6 | 7 | [dependencies] 8 | regex = "0.1" 9 | -------------------------------------------------------------------------------- /src/lint.rs: -------------------------------------------------------------------------------- 1 | use super::Makefile; 2 | 3 | pub trait Lint { 4 | fn name(&self) -> String; 5 | fn apply(&self, makefile: &Makefile) -> Option; 6 | } 7 | 8 | pub struct HasTarget { 9 | target: String 10 | } 11 | 12 | impl HasTarget { 13 | pub fn new(target: String) -> HasTarget { 14 | HasTarget { target: target } 15 | } 16 | } 17 | 18 | impl Lint for HasTarget { 19 | fn name(&self) -> String { 20 | "has target".to_owned() 21 | } 22 | 23 | fn apply(&self, makefile: &Makefile) -> Option { 24 | if let None = makefile.rules.iter().find(|r|r.target == self.target) { 25 | Some(format!("missing target {}", self.target)) 26 | } else { 27 | None 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Doug Tangren 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate regex; 2 | 3 | 4 | mod lint; 5 | pub use lint::*; 6 | 7 | mod parse; 8 | pub use parse::*; 9 | 10 | #[derive(Default, Debug)] 11 | pub struct Makefile { 12 | rules: Vec, 13 | vars: Vec, 14 | } 15 | 16 | impl Makefile { 17 | fn new() -> Makefile { 18 | Makefile { ..Default::default() } 19 | } 20 | 21 | fn add_rule(&mut self, rule: Rule) { 22 | self.rules.push(rule); 23 | } 24 | 25 | fn add_var(&mut self, var: Var) { 26 | self.vars.push(var); 27 | } 28 | 29 | fn rule_line(&mut self, line: String) { 30 | if let Some(mut last) = self.rules.pop() { 31 | last.body.push(line); 32 | self.rules.push(last); 33 | } 34 | } 35 | } 36 | 37 | #[derive(Debug, Default)] 38 | pub struct Rule { 39 | target: String, 40 | dependencies: Vec, 41 | line: usize, 42 | body: Vec, 43 | } 44 | 45 | #[derive(Debug)] 46 | pub enum Var { 47 | Eq(String, String, usize), 48 | ColonEq(String, String, usize), 49 | DoubleColonEq(String, String, usize), 50 | PlusEq(String, String, usize), 51 | QuestionEq(String, String, usize), 52 | Special(String, String, usize) 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | #[test] 58 | fn it_works() {} 59 | } 60 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use super::{Makefile, Var, Rule}; 2 | 3 | use regex::Regex; 4 | 5 | pub fn parse(file: &str) -> Makefile { 6 | // rule line with deps 7 | // https://www.gnu.org/software/make/manual/html_node/Rule-Syntax.html#Rule-Syntax 8 | let rule = Regex::new("^([a-zA-Z-_]+):(.*)?").unwrap(); 9 | // rule body lines, intended by tabs 10 | let rule_body = Regex::new("^\t+(.*)").unwrap(); 11 | 12 | // http://www.gnu.org/software/make/manual/make.html#Reading-Makefiles 13 | // variables come in the forms =, :=, ::=, +=, and ?= 14 | let var = Regex::new("^([a-zA-Z-_]+)\\s*(=|:=|::=|\\+=|\\?=)(.*)").unwrap(); 15 | 16 | // special .-prefixed vars 17 | let special_var = Regex::new("^\\.([a-zA-Z-_]+):(.*)").unwrap(); 18 | 19 | let mut makefile = Makefile::new(); 20 | for (i, line) in file.lines().filter(|l| !l.is_empty() && !l.starts_with('#')).enumerate() { 21 | if let Some(rule) = rule.captures(line) { 22 | if let Some(target) = rule.at(1) { 23 | let deps = rule.at(2) 24 | .map(|d| { 25 | d.trim() 26 | .split(" ") 27 | .filter(|d| !d.is_empty()) 28 | .map(|d| d.to_owned()) 29 | .collect::>() 30 | }) 31 | .unwrap_or(vec![]); 32 | makefile.add_rule(Rule { 33 | target: target.to_owned(), 34 | dependencies: deps, 35 | line: i, 36 | ..Default::default() 37 | }) 38 | } 39 | } else if let Some(body) = rule_body.captures(line) { 40 | if let Some(bod) = body.at(1) { 41 | makefile.rule_line(bod.trim().to_owned()); 42 | } 43 | } else if let Some(var) = var.captures(line) { 44 | if let (Some(k), Some(b), Some(v)) = (var.at(1), var.at(2), var.at(3)) { 45 | let (key, value) = (k.to_owned(), v.trim().to_owned()); 46 | let v = match b { 47 | "=" => Var::Eq(key, value, i), 48 | ":=" => Var::ColonEq(key, value, i), 49 | "::=" => Var::DoubleColonEq(key, value, i), 50 | "+=" => Var::PlusEq(key, value, i), 51 | "?=" => Var::QuestionEq(key, value, i), 52 | _ => unreachable!() 53 | }; 54 | makefile.add_var(v); 55 | } 56 | } else if let Some(var) = special_var.captures(line) { 57 | if let (Some(k), Some(v)) = (var.at(1), var.at(2)) { 58 | let v = Var::Special(k.to_owned(), v.trim().to_owned(), i); 59 | makefile.add_var(v); 60 | } 61 | } else { 62 | println!("unparsed line! '{}''", line) 63 | } 64 | } 65 | makefile 66 | } 67 | --------------------------------------------------------------------------------