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