Skip to content

Commit

Permalink
Lint against PreEscaped("<!DOCTYPE html>")
Browse files Browse the repository at this point in the history
See #66
  • Loading branch information
lambda-fairy committed Dec 27, 2016
1 parent 98d0402 commit f991eba
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 0 deletions.
4 changes: 4 additions & 0 deletions maud_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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);
}
42 changes: 42 additions & 0 deletions maud_macros/src/lints/doctype_html.rs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}}
}
}
14 changes: 14 additions & 0 deletions maud_macros/src/lints/mod.rs
Original file line number Diff line number Diff line change
@@ -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,
]);
}
92 changes: 92 additions & 0 deletions maud_macros/src/lints/util.rs
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit f991eba

Please sign in to comment.