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