|
5 | 5 | * LICENSE file in the root directory of this source tree. |
6 | 6 | */ |
7 | 7 |
|
8 | | -use lsp_types::CodeLens; |
9 | | -use lsp_types::Command; |
10 | 8 | use lsp_types::Location; |
11 | 9 | use lsp_types::Range; |
12 | 10 | use lsp_types::Uri; |
13 | 11 | use lsp_types::VersionedTextDocumentIdentifier; |
14 | | -use pyrefly_build::handle::Handle; |
15 | | -use pyrefly_python::ast::Ast; |
16 | | -use ruff_python_ast::Expr; |
17 | | -use ruff_python_ast::ExprAttribute; |
18 | | -use ruff_python_ast::Stmt; |
19 | | -use ruff_python_ast::StmtClassDef; |
20 | | -use ruff_text_size::TextRange; |
21 | 12 | use serde::Deserialize; |
22 | 13 | use serde::Serialize; |
23 | 14 | use serde_json::Value; |
24 | 15 |
|
25 | | -use crate::state::state::Transaction; |
26 | 16 |
|
27 | 17 | /// A document batch for override lenses. Ranges use the specified editor version. |
28 | 18 | #[derive(Debug, Deserialize, Serialize)] |
@@ -69,187 +59,3 @@ impl lsp_types::Request for OverrideCodeLensRequest { |
69 | 59 | const MESSAGE_DIRECTION: lsp_types::MessageDirection = |
70 | 60 | lsp_types::MessageDirection::ClientToServer; |
71 | 61 | } |
72 | | - |
73 | | -#[derive(Clone, Copy, Debug, Eq, PartialEq)] |
74 | | -pub enum CodeLensKind { |
75 | | - Run, |
76 | | - Test, |
77 | | -} |
78 | | - |
79 | | -#[derive(Clone, Debug)] |
80 | | -pub struct CodeLensEntry { |
81 | | - pub range: TextRange, |
82 | | - pub kind: CodeLensKind, |
83 | | - pub test_name: Option<String>, |
84 | | - pub class_name: Option<String>, |
85 | | - pub is_unittest: bool, |
86 | | -} |
87 | | - |
88 | | -pub fn runnable_lsp_code_lens( |
89 | | - uri: &Uri, |
90 | | - range: Range, |
91 | | - entry: CodeLensEntry, |
92 | | - cwd: Option<&str>, |
93 | | -) -> CodeLens { |
94 | | - let (title, command, arguments) = match entry.kind { |
95 | | - CodeLensKind::Run => { |
96 | | - let mut args = serde_json::Map::new(); |
97 | | - args.insert("uri".to_owned(), serde_json::json!(uri.to_string())); |
98 | | - if let Some(cwd) = cwd { |
99 | | - args.insert("cwd".to_owned(), serde_json::json!(cwd)); |
100 | | - } |
101 | | - ("Run", "pyrefly.runMain", Some(vec![Value::Object(args)])) |
102 | | - } |
103 | | - CodeLensKind::Test => { |
104 | | - let mut args = serde_json::Map::new(); |
105 | | - args.insert("uri".to_owned(), serde_json::json!(uri.to_string())); |
106 | | - if let Some(cwd) = cwd { |
107 | | - args.insert("cwd".to_owned(), serde_json::json!(cwd)); |
108 | | - } |
109 | | - args.insert( |
110 | | - "position".to_owned(), |
111 | | - serde_json::json!({ |
112 | | - "line": range.start.line, |
113 | | - "character": range.start.character, |
114 | | - }), |
115 | | - ); |
116 | | - if let Some(test_name) = entry.test_name { |
117 | | - args.insert("testName".to_owned(), serde_json::json!(test_name)); |
118 | | - } |
119 | | - if let Some(class_name) = entry.class_name { |
120 | | - args.insert("className".to_owned(), serde_json::json!(class_name)); |
121 | | - } |
122 | | - args.insert( |
123 | | - "isUnittest".to_owned(), |
124 | | - serde_json::json!(entry.is_unittest), |
125 | | - ); |
126 | | - ("Test", "pyrefly.runTest", Some(vec![Value::Object(args)])) |
127 | | - } |
128 | | - }; |
129 | | - |
130 | | - CodeLens { |
131 | | - range, |
132 | | - command: Some(Command { |
133 | | - title: title.to_owned(), |
134 | | - command: command.to_owned(), |
135 | | - arguments, |
136 | | - tooltip: None, |
137 | | - }), |
138 | | - data: None, |
139 | | - } |
140 | | -} |
141 | | - |
142 | | -impl<'a> Transaction<'a> { |
143 | | - pub fn runnable_code_lens_entries( |
144 | | - &self, |
145 | | - handle: &Handle, |
146 | | - uri: &Uri, |
147 | | - runnable_code_lens: bool, |
148 | | - ) -> Option<Vec<CodeLensEntry>> { |
149 | | - if !runnable_code_lens || uri.path().ends_with(".pyi") || uri.path().ends_with(".ipynb") { |
150 | | - return Some(Vec::new()); |
151 | | - } |
152 | | - let ast = self.get_ast(handle)?; |
153 | | - let mut entries = Vec::new(); |
154 | | - collect_module_entries(&ast.body, &mut entries); |
155 | | - Some(entries) |
156 | | - } |
157 | | -} |
158 | | - |
159 | | -fn collect_module_entries(stmts: &[Stmt], entries: &mut Vec<CodeLensEntry>) { |
160 | | - for stmt in stmts { |
161 | | - match stmt { |
162 | | - Stmt::FunctionDef(func) => { |
163 | | - maybe_push_test(entries, func.name.as_str(), func.name.range, None, false); |
164 | | - } |
165 | | - Stmt::ClassDef(class_def) => { |
166 | | - let is_unittest = is_unittest_class(class_def); |
167 | | - if is_test_class(class_def, is_unittest) { |
168 | | - entries.push(CodeLensEntry { |
169 | | - range: class_def.name.range, |
170 | | - kind: CodeLensKind::Test, |
171 | | - test_name: None, |
172 | | - class_name: Some(class_def.name.as_str().to_owned()), |
173 | | - is_unittest, |
174 | | - }); |
175 | | - } |
176 | | - collect_class_entries( |
177 | | - &class_def.body, |
178 | | - entries, |
179 | | - class_def.name.as_str(), |
180 | | - is_unittest, |
181 | | - ); |
182 | | - } |
183 | | - Stmt::If(stmt_if) if Ast::is_main_guard(&stmt_if.test) => { |
184 | | - entries.push(CodeLensEntry { |
185 | | - range: stmt_if.range, |
186 | | - kind: CodeLensKind::Run, |
187 | | - test_name: None, |
188 | | - class_name: None, |
189 | | - is_unittest: false, |
190 | | - }); |
191 | | - } |
192 | | - _ => {} |
193 | | - } |
194 | | - } |
195 | | -} |
196 | | - |
197 | | -fn collect_class_entries( |
198 | | - stmts: &[Stmt], |
199 | | - entries: &mut Vec<CodeLensEntry>, |
200 | | - class_name: &str, |
201 | | - is_unittest: bool, |
202 | | -) { |
203 | | - for stmt in stmts { |
204 | | - if let Stmt::FunctionDef(func) = stmt { |
205 | | - maybe_push_test( |
206 | | - entries, |
207 | | - func.name.as_str(), |
208 | | - func.name.range, |
209 | | - Some(class_name.to_owned()), |
210 | | - is_unittest, |
211 | | - ); |
212 | | - } |
213 | | - } |
214 | | -} |
215 | | - |
216 | | -fn maybe_push_test( |
217 | | - entries: &mut Vec<CodeLensEntry>, |
218 | | - name: &str, |
219 | | - range: TextRange, |
220 | | - class_name: Option<String>, |
221 | | - is_unittest: bool, |
222 | | -) { |
223 | | - if is_test_name(name) { |
224 | | - entries.push(CodeLensEntry { |
225 | | - range, |
226 | | - kind: CodeLensKind::Test, |
227 | | - test_name: Some(name.to_owned()), |
228 | | - class_name, |
229 | | - is_unittest, |
230 | | - }); |
231 | | - } |
232 | | -} |
233 | | - |
234 | | -fn is_test_name(name: &str) -> bool { |
235 | | - name.starts_with("test_") |
236 | | -} |
237 | | - |
238 | | -fn is_test_class(class_def: &StmtClassDef, is_unittest: bool) -> bool { |
239 | | - if class_def.name.as_str().starts_with("Test") { |
240 | | - return true; |
241 | | - } |
242 | | - is_unittest |
243 | | -} |
244 | | - |
245 | | -fn is_unittest_class(class_def: &StmtClassDef) -> bool { |
246 | | - class_def.bases().iter().any(is_unittest_base) |
247 | | -} |
248 | | - |
249 | | -fn is_unittest_base(base: &Expr) -> bool { |
250 | | - match base { |
251 | | - Expr::Name(name) => name.id.as_str().ends_with("TestCase"), |
252 | | - Expr::Attribute(ExprAttribute { attr, .. }) => attr.id.as_str().ends_with("TestCase"), |
253 | | - _ => false, |
254 | | - } |
255 | | -} |
0 commit comments