]> git.lizzy.rs Git - rust.git/blob - clippy_dev/src/new_lint.rs
44b2a5383d2113f367c1a6262e351b236ea33e16
[rust.git] / clippy_dev / src / new_lint.rs
1 use crate::clippy_project_root;
2 use std::fs::{File, OpenOptions};
3 use std::io;
4 use std::io::prelude::*;
5 use std::io::ErrorKind;
6 use std::path::Path;
7
8 /// Creates files required to implement and test a new lint and runs `update_lints`.
9 ///
10 /// # Errors
11 ///
12 /// This function errors, if the files couldn't be created
13 pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>) -> Result<(), io::Error> {
14     let pass = pass.expect("`pass` argument is validated by clap");
15     let lint_name = lint_name.expect("`name` argument is validated by clap");
16     let category = category.expect("`category` argument is validated by clap");
17
18     match open_files(lint_name) {
19         Ok((mut test_file, mut lint_file)) => {
20             let (pass_type, pass_lifetimes, pass_import, context_import) = match pass {
21                 "early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
22                 "late" => ("LateLintPass", "<'_, '_>", "use rustc_hir::*;", "LateContext"),
23                 _ => {
24                     unreachable!("`pass_type` should only ever be `early` or `late`!");
25                 },
26             };
27
28             let camel_case_name = to_camel_case(lint_name);
29
30             if let Err(e) = test_file.write_all(get_test_file_contents(lint_name).as_bytes()) {
31                 return Err(io::Error::new(
32                     ErrorKind::Other,
33                     format!("Could not write to test file: {}", e),
34                 ));
35             };
36
37             if let Err(e) = lint_file.write_all(
38                 get_lint_file_contents(
39                     pass_type,
40                     pass_lifetimes,
41                     lint_name,
42                     &camel_case_name,
43                     category,
44                     pass_import,
45                     context_import,
46                 )
47                 .as_bytes(),
48             ) {
49                 return Err(io::Error::new(
50                     ErrorKind::Other,
51                     format!("Could not write to lint file: {}", e),
52                 ));
53             }
54             Ok(())
55         },
56         Err(e) => Err(io::Error::new(
57             ErrorKind::Other,
58             format!("Unable to create lint: {}", e),
59         )),
60     }
61 }
62
63 fn open_files(lint_name: &str) -> Result<(File, File), io::Error> {
64     let project_root = clippy_project_root();
65
66     let test_file_path = project_root.join("tests").join("ui").join(format!("{}.rs", lint_name));
67     let lint_file_path = project_root
68         .join("clippy_lints")
69         .join("src")
70         .join(format!("{}.rs", lint_name));
71
72     if Path::new(&test_file_path).exists() {
73         return Err(io::Error::new(
74             ErrorKind::AlreadyExists,
75             format!("test file {:?} already exists", test_file_path),
76         ));
77     }
78     if Path::new(&lint_file_path).exists() {
79         return Err(io::Error::new(
80             ErrorKind::AlreadyExists,
81             format!("lint file {:?} already exists", lint_file_path),
82         ));
83     }
84
85     let test_file = OpenOptions::new().write(true).create_new(true).open(test_file_path)?;
86     let lint_file = OpenOptions::new().write(true).create_new(true).open(lint_file_path)?;
87
88     Ok((test_file, lint_file))
89 }
90
91 fn to_camel_case(name: &str) -> String {
92     name.split('_')
93         .map(|s| {
94             if s.is_empty() {
95                 String::from("")
96             } else {
97                 [&s[0..1].to_uppercase(), &s[1..]].concat()
98             }
99         })
100         .collect()
101 }
102
103 fn get_test_file_contents(lint_name: &str) -> String {
104     format!(
105         "#![warn(clippy::{})]
106
107 fn main() {{
108     // test code goes here
109 }}
110 ",
111         lint_name
112     )
113 }
114
115 fn get_lint_file_contents(
116     pass_type: &str,
117     pass_lifetimes: &str,
118     lint_name: &str,
119     camel_case_name: &str,
120     category: &str,
121     pass_import: &str,
122     context_import: &str,
123 ) -> String {
124     format!(
125         "use rustc_lint::{{{type}, {context_import}}};
126 use rustc_session::{{declare_lint_pass, declare_tool_lint}};
127 {pass_import}
128
129 declare_clippy_lint! {{
130     /// **What it does:**
131     ///
132     /// **Why is this bad?**
133     ///
134     /// **Known problems:** None.
135     ///
136     /// **Example:**
137     ///
138     /// ```rust
139     /// // example code where clippy issues a warning
140     /// ```
141     /// Use instead:
142     /// ```rust
143     /// // example code which does not raise clippy warning
144     /// ```
145     pub {name_upper},
146     {category},
147     \"default lint description\"
148 }}
149
150 declare_lint_pass!({name_camel} => [{name_upper}]);
151
152 impl {type}{lifetimes} for {name_camel} {{}}
153 ",
154         type=pass_type,
155         lifetimes=pass_lifetimes,
156         name_upper=lint_name.to_uppercase(),
157         name_camel=camel_case_name,
158         category=category,
159         pass_import=pass_import,
160         context_import=context_import
161     )
162 }
163
164 #[test]
165 fn test_camel_case() {
166     let s = "a_lint";
167     let s2 = to_camel_case(s);
168     assert_eq!(s2, "ALint");
169
170     let name = "a_really_long_new_lint";
171     let name2 = to_camel_case(name);
172     assert_eq!(name2, "AReallyLongNewLint");
173
174     let name3 = "lint__name";
175     let name4 = to_camel_case(name3);
176     assert_eq!(name4, "LintName");
177 }