1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#![allow(clippy::type_complexity)]

use rustc_driver::Callbacks;
use rustc_errors::FatalError;
use rustc_interface::interface::Config;
use rustc_lint::LintStore;
use rustc_session::config::ErrorOutputType;
use rustc_session::EarlyDiagCtxt;
use rustc_span::Symbol;

use std::sync::Arc;

struct Lints {
    callback: Arc<Box<dyn Fn(&mut LintStore) + Send + Sync + 'static>>,
    /// If one of these files is modified, the linter needs to be re-run.
    tracked_files: Arc<Vec<String>>,
}

impl Callbacks for Lints {
    fn config(&mut self, config: &mut Config) {
        // Should always be `None` but just in case...
        let previous = config.register_lints.take();

        let tracked_files = Arc::clone(&self.tracked_files);
        config.psess_created = Some(Box::new(move |parse_sess| {
            // In here, we insert the files that, if modified, will tell cargo that the command
            // needs to be re-run.
            if tracked_files.is_empty() {
                return;
            }
            let file_depinfo = parse_sess.file_depinfo.get_mut();
            for tracked_file in tracked_files.iter() {
                file_depinfo.insert(Symbol::intern(tracked_file));
            }
        }));
        let callback = Arc::clone(&self.callback);
        config.register_lints = Some(Box::new(move |sess, lint_store| {
            if let Some(previous) = &previous {
                (previous)(sess, lint_store);
            }
            (*callback)(lint_store);
        }));
    }
}

/// If you want to create a linter, this the function you want to use.
///
/// * `args` is what is provided to the compiler.
/// * `tracked_files` is the files which will trigger a re-compilation if they are modified.
/// * `callback` is called when everything is setup. This is where you will register your lints
///   before they are run by rustc directly. It provides a mutable reference to the [`LintStore`]
///   type.
///
/// Take a look at the `examples/lint.rs` file if you want an example on how to create lints.
///
/// **VERY IMPORTANT TO NOTE**: if you want to run this code on a crate with dependencies, you'll
/// need to pass the according options so that `rustc` knows where to look for them. otherwise it
/// will simply fail to compile and the `callback` won't be called. A good example of the list
/// of the expected arguments can be seen when you run `cargo build -v`.
///
/// Take a look at [`cargo_integration`](crate::cargo_integration) and at
/// [rustc-tools-example](https://github.com/GuillaumeGomez/rustc-tools-example) to see how to
/// write a cargo integration.
///
/// [`LintStore`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html
pub fn with_lints<F: Fn(&mut LintStore) + Send + Sync + 'static>(
    args: &[String],
    tracked_files: Vec<String>,
    callback: F,
) -> Result<(), FatalError> {
    with_lints_and_error_output(args, tracked_files, ErrorOutputType::default(), callback)
}

/// If you want to create a linter, this the function you want to use.
///
/// * `args` is what is provided to the compiler.
/// * `tracked_files` is the files which will trigger a re-compilation if they are modified.
/// * `error_output` is the equivalent of `--output-format`, it is quite useful if you want
///    to run `rustfix` on the warnings/errors generated by your linter.
/// * `callback` is called when everything is setup. This is where you will register your lints
///   before they are run by rustc directly. It provides a mutable reference to the [`LintStore`]
///   type.
///
/// Take a look at the `examples/lint.rs` file if you want an example on how to create lints.
///
/// **VERY IMPORTANT TO NOTE**: if you want to run this code on a crate with dependencies, you'll
/// need to pass the according options so that `rustc` knows where to look for them. otherwise it
/// will simply fail to compile and the `callback` won't be called. A good example of the list
/// of the expected arguments can be seen when you run `cargo build -v`.
///
/// Take a look at [`cargo_integration`](crate::cargo_integration) and at
/// [rustc-tools-example](https://github.com/GuillaumeGomez/rustc-tools-example) to see how to
/// write a cargo integration.
///
/// [`LintStore`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LintStore.html
pub fn with_lints_and_error_output<F: Fn(&mut LintStore) + Send + Sync + 'static>(
    args: &[String],
    tracked_files: Vec<String>,
    error_output: ErrorOutputType,
    callback: F,
) -> Result<(), FatalError> {
    let handler = EarlyDiagCtxt::new(error_output);
    rustc_driver::init_rustc_env_logger(&handler);
    if rustc_driver::catch_fatal_errors(move || {
        rustc_driver::RunCompiler::new(
            args,
            &mut Lints {
                callback: Arc::new(Box::new(callback)),
                tracked_files: Arc::new(tracked_files),
            },
        )
        .run()
        .map(|_| ())
    })
    .is_err()
    {
        Err(FatalError)
    } else {
        Ok(())
    }
}