From f991ebaa7810ff3f6254baf2350c7185dce23e58 Mon Sep 17 00:00:00 2001 From: Chris Wong <lambda.fairy@gmail.com> Date: Mon, 26 Dec 2016 22:04:45 +1300 Subject: [PATCH] Lint against `PreEscaped("<!DOCTYPE html>")` See #66 --- maud_macros/src/lib.rs | 4 ++ maud_macros/src/lints/doctype_html.rs | 42 ++++++++++++ maud_macros/src/lints/mod.rs | 14 ++++ maud_macros/src/lints/util.rs | 92 +++++++++++++++++++++++++++ 4 files changed, 152 insertions(+) create mode 100644 maud_macros/src/lints/doctype_html.rs create mode 100644 maud_macros/src/lints/mod.rs create mode 100644 maud_macros/src/lints/util.rs diff --git a/maud_macros/src/lib.rs b/maud_macros/src/lib.rs index 25b6789..6db3dc5 100644 --- a/maud_macros/src/lib.rs +++ b/maud_macros/src/lib.rs @@ -3,6 +3,8 @@ #![feature(slice_patterns)] #![feature(rustc_private)] +#[macro_use] +extern crate rustc; extern crate rustc_plugin; extern crate syntax; extern crate maud; @@ -14,6 +16,7 @@ use syntax::ext::base::{DummyResult, ExtCtxt, MacEager, MacResult}; use syntax::print::pprust; use syntax::tokenstream::TokenTree; +mod lints; mod parse; mod render; @@ -41,4 +44,5 @@ fn expand_html_debug<'cx>(cx: &'cx mut ExtCtxt, sp: Span, args: &[TokenTree]) -> pub fn plugin_registrar(reg: &mut Registry) { reg.register_macro("html", expand_html); reg.register_macro("html_debug", expand_html_debug); + lints::register_lints(reg); } diff --git a/maud_macros/src/lints/doctype_html.rs b/maud_macros/src/lints/doctype_html.rs new file mode 100644 index 0000000..1a9ff8a --- /dev/null +++ b/maud_macros/src/lints/doctype_html.rs @@ -0,0 +1,42 @@ +use rustc::hir::{Expr, ExprCall, ExprLit, ExprPath}; +use rustc::lint::{LateContext, LateLintPass, LintArray, LintContext, LintPass}; +use std::ascii::AsciiExt; +use super::util::match_def_path; +use syntax::ast::LitKind; + +declare_lint! { + pub MAUD_DOCTYPE_HTML, + Warn, + "suggest using the maud::DOCTYPE_HTML constant" +} + +pub struct DoctypeHtml; + +impl LintPass for DoctypeHtml { + fn get_lints(&self) -> LintArray { + lint_array![MAUD_DOCTYPE_HTML] + } +} + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for DoctypeHtml { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) { + if_let_chain! {[ + // It's a function call... + let ExprCall(ref path_expr, ref args) = expr.node, + // ... where the argument is a literal "<!doctype html>" + let Some(first_arg) = args.first(), + let ExprLit(ref lit) = first_arg.node, + let LitKind::Str(s, _) = lit.node, + s.as_str().eq_ignore_ascii_case("<!doctype html>"), + ], { + // ... and the callee is `maud::PreEscaped` + if let ExprPath(ref qpath) = path_expr.node { + let def_id = cx.tcx.tables().qpath_def(qpath, path_expr.id).def_id(); + if match_def_path(cx, def_id, &["maud", "PreEscaped", "{{constructor}}"]) { + cx.struct_span_lint(MAUD_DOCTYPE_HTML, expr.span, + "use `maud::DOCTYPE_HTML` instead").emit(); + } + } + }} + } +} diff --git a/maud_macros/src/lints/mod.rs b/maud_macros/src/lints/mod.rs new file mode 100644 index 0000000..c2409d0 --- /dev/null +++ b/maud_macros/src/lints/mod.rs @@ -0,0 +1,14 @@ +use rustc_plugin::Registry; + +#[macro_use] +mod util; + +pub mod doctype_html; + +pub fn register_lints(reg: &mut Registry) { + reg.register_late_lint_pass(Box::new(doctype_html::DoctypeHtml)); + + reg.register_lint_group("maud", vec![ + doctype_html::MAUD_DOCTYPE_HTML, + ]); +} diff --git a/maud_macros/src/lints/util.rs b/maud_macros/src/lints/util.rs new file mode 100644 index 0000000..b05e7e1 --- /dev/null +++ b/maud_macros/src/lints/util.rs @@ -0,0 +1,92 @@ +//! Miscellaneous utilities for writing lints. +//! +//! Most of these are adapted from Clippy. + +use rustc::hir::def_id::DefId; +use rustc::lint::LateContext; +use rustc::ty; +use syntax::symbol::{InternedString, Symbol}; + +/// Produce a nested chain of if-lets and ifs from the patterns: +/// +/// ```rust,ignore +/// if_let_chain! {[ +/// let Some(y) = x, +/// y.len() == 2, +/// let Some(z) = y, +/// ], { +/// block +/// }} +/// ``` +/// +/// becomes +/// +/// ```rust,ignore +/// if let Some(y) = x { +/// if y.len() == 2 { +/// if let Some(z) = y { +/// block +/// } +/// } +/// } +/// ``` +#[macro_export] +macro_rules! if_let_chain { + ([let $pat:pat = $expr:expr, $($tt:tt)+], $block:block) => { + if let $pat = $expr { + if_let_chain!{ [$($tt)+], $block } + } + }; + ([let $pat:pat = $expr:expr], $block:block) => { + if let $pat = $expr { + $block + } + }; + ([let $pat:pat = $expr:expr,], $block:block) => { + if let $pat = $expr { + $block + } + }; + ([$expr:expr, $($tt:tt)+], $block:block) => { + if $expr { + if_let_chain!{ [$($tt)+], $block } + } + }; + ([$expr:expr], $block:block) => { + if $expr { + $block + } + }; + ([$expr:expr,], $block:block) => { + if $expr { + $block + } + }; +} + +/// Check if a `DefId`'s path matches the given absolute type path usage. +/// +/// # Examples +/// ```rust,ignore +/// match_def_path(cx, id, &["core", "option", "Option"]) +/// ``` +pub fn match_def_path(cx: &LateContext, def_id: DefId, path: &[&str]) -> bool { + struct AbsolutePathBuffer { + names: Vec<InternedString>, + } + + impl ty::item_path::ItemPathBuffer for AbsolutePathBuffer { + fn root_mode(&self) -> &ty::item_path::RootMode { + const ABSOLUTE: &'static ty::item_path::RootMode = &ty::item_path::RootMode::Absolute; + ABSOLUTE + } + + fn push(&mut self, text: &str) { + self.names.push(Symbol::intern(text).as_str()); + } + } + + let mut apb = AbsolutePathBuffer { names: vec![] }; + cx.tcx.push_item_path(&mut apb, def_id); + apb.names.len() == path.len() && apb.names.iter().zip(path.iter()).all(|(a, &b)| &**a == b) +}