diff options
-rw-r--r-- | crates/hir_ty/src/diagnostics/decl_check.rs | 126 |
1 files changed, 96 insertions, 30 deletions
diff --git a/crates/hir_ty/src/diagnostics/decl_check.rs b/crates/hir_ty/src/diagnostics/decl_check.rs index f179c62b7..4b3e2fa8f 100644 --- a/crates/hir_ty/src/diagnostics/decl_check.rs +++ b/crates/hir_ty/src/diagnostics/decl_check.rs | |||
@@ -16,7 +16,7 @@ use hir_def::{ | |||
16 | adt::VariantData, | 16 | adt::VariantData, |
17 | expr::{Pat, PatId}, | 17 | expr::{Pat, PatId}, |
18 | src::HasSource, | 18 | src::HasSource, |
19 | AdtId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId, | 19 | AdtId, AttrDefId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId, |
20 | }; | 20 | }; |
21 | use hir_expand::{ | 21 | use hir_expand::{ |
22 | diagnostics::DiagnosticSink, | 22 | diagnostics::DiagnosticSink, |
@@ -32,6 +32,12 @@ use crate::{ | |||
32 | diagnostics::{decl_check::case_conv::*, CaseType, IncorrectCase}, | 32 | diagnostics::{decl_check::case_conv::*, CaseType, IncorrectCase}, |
33 | }; | 33 | }; |
34 | 34 | ||
35 | mod allow { | ||
36 | pub(super) const NON_SNAKE_CASE: &str = "non_snake_case"; | ||
37 | pub(super) const NON_UPPER_CASE_GLOBAL: &str = "non_upper_case_globals"; | ||
38 | pub(super) const NON_CAMEL_CASE_TYPES: &str = "non_camel_case_types"; | ||
39 | } | ||
40 | |||
35 | pub(super) struct DeclValidator<'a, 'b: 'a> { | 41 | pub(super) struct DeclValidator<'a, 'b: 'a> { |
36 | owner: ModuleDefId, | 42 | owner: ModuleDefId, |
37 | sink: &'a mut DiagnosticSink<'b>, | 43 | sink: &'a mut DiagnosticSink<'b>, |
@@ -72,11 +78,29 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
72 | } | 78 | } |
73 | } | 79 | } |
74 | 80 | ||
81 | /// Checks whether not following the convention is allowed for this item. | ||
82 | /// | ||
83 | /// Currently this method doesn't check parent attributes. | ||
84 | fn allowed(&self, db: &dyn HirDatabase, id: AttrDefId, allow_name: &str) -> bool { | ||
85 | db.attrs(id).by_key("allow").tt_values().any(|tt| tt.to_string().contains(allow_name)) | ||
86 | } | ||
87 | |||
75 | fn validate_func(&mut self, db: &dyn HirDatabase, func: FunctionId) { | 88 | fn validate_func(&mut self, db: &dyn HirDatabase, func: FunctionId) { |
76 | let data = db.function_data(func); | 89 | let data = db.function_data(func); |
77 | let body = db.body(func.into()); | 90 | let body = db.body(func.into()); |
78 | 91 | ||
79 | // 1. Check the function name. | 92 | // Recursively validate inner scope items, such as static variables and constants. |
93 | for (item_id, _) in body.item_scope.values() { | ||
94 | let mut validator = DeclValidator::new(item_id, self.sink); | ||
95 | validator.validate_item(db); | ||
96 | } | ||
97 | |||
98 | // Check whether non-snake case identifiers are allowed for this function. | ||
99 | if self.allowed(db, func.into(), allow::NON_SNAKE_CASE) { | ||
100 | return; | ||
101 | } | ||
102 | |||
103 | // Check the function name. | ||
80 | let function_name = data.name.to_string(); | 104 | let function_name = data.name.to_string(); |
81 | let fn_name_replacement = if let Some(new_name) = to_lower_snake_case(&function_name) { | 105 | let fn_name_replacement = if let Some(new_name) = to_lower_snake_case(&function_name) { |
82 | let replacement = Replacement { | 106 | let replacement = Replacement { |
@@ -89,7 +113,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
89 | None | 113 | None |
90 | }; | 114 | }; |
91 | 115 | ||
92 | // 2. Check the param names. | 116 | // Check the param names. |
93 | let mut fn_param_replacements = Vec::new(); | 117 | let mut fn_param_replacements = Vec::new(); |
94 | 118 | ||
95 | for pat_id in body.params.iter().cloned() { | 119 | for pat_id in body.params.iter().cloned() { |
@@ -111,7 +135,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
111 | } | 135 | } |
112 | } | 136 | } |
113 | 137 | ||
114 | // 3. Check the patterns inside the function body. | 138 | // Check the patterns inside the function body. |
115 | let mut pats_replacements = Vec::new(); | 139 | let mut pats_replacements = Vec::new(); |
116 | 140 | ||
117 | for (pat_idx, pat) in body.pats.iter() { | 141 | for (pat_idx, pat) in body.pats.iter() { |
@@ -136,7 +160,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
136 | } | 160 | } |
137 | } | 161 | } |
138 | 162 | ||
139 | // 4. If there is at least one element to spawn a warning on, go to the source map and generate a warning. | 163 | // If there is at least one element to spawn a warning on, go to the source map and generate a warning. |
140 | self.create_incorrect_case_diagnostic_for_func( | 164 | self.create_incorrect_case_diagnostic_for_func( |
141 | func, | 165 | func, |
142 | db, | 166 | db, |
@@ -144,12 +168,6 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
144 | fn_param_replacements, | 168 | fn_param_replacements, |
145 | ); | 169 | ); |
146 | self.create_incorrect_case_diagnostic_for_variables(func, db, pats_replacements); | 170 | self.create_incorrect_case_diagnostic_for_variables(func, db, pats_replacements); |
147 | |||
148 | // 5. Recursively validate inner scope items, such as static variables and constants. | ||
149 | for (item_id, _) in body.item_scope.values() { | ||
150 | let mut validator = DeclValidator::new(item_id, self.sink); | ||
151 | validator.validate_item(db); | ||
152 | } | ||
153 | } | 171 | } |
154 | 172 | ||
155 | /// Given the information about incorrect names in the function declaration, looks up into the source code | 173 | /// Given the information about incorrect names in the function declaration, looks up into the source code |
@@ -169,7 +187,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
169 | let fn_loc = func.lookup(db.upcast()); | 187 | let fn_loc = func.lookup(db.upcast()); |
170 | let fn_src = fn_loc.source(db.upcast()); | 188 | let fn_src = fn_loc.source(db.upcast()); |
171 | 189 | ||
172 | // 1. Diagnostic for function name. | 190 | // Diagnostic for function name. |
173 | if let Some(replacement) = fn_name_replacement { | 191 | if let Some(replacement) = fn_name_replacement { |
174 | let ast_ptr = match fn_src.value.name() { | 192 | let ast_ptr = match fn_src.value.name() { |
175 | Some(name) => name, | 193 | Some(name) => name, |
@@ -196,7 +214,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
196 | self.sink.push(diagnostic); | 214 | self.sink.push(diagnostic); |
197 | } | 215 | } |
198 | 216 | ||
199 | // 2. Diagnostics for function params. | 217 | // Diagnostics for function params. |
200 | let fn_params_list = match fn_src.value.param_list() { | 218 | let fn_params_list = match fn_src.value.param_list() { |
201 | Some(params) => params, | 219 | Some(params) => params, |
202 | None => { | 220 | None => { |
@@ -312,7 +330,11 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
312 | fn validate_struct(&mut self, db: &dyn HirDatabase, struct_id: StructId) { | 330 | fn validate_struct(&mut self, db: &dyn HirDatabase, struct_id: StructId) { |
313 | let data = db.struct_data(struct_id); | 331 | let data = db.struct_data(struct_id); |
314 | 332 | ||
315 | // 1. Check the structure name. | 333 | let non_camel_case_allowed = |
334 | self.allowed(db, struct_id.into(), allow::NON_CAMEL_CASE_TYPES); | ||
335 | let non_snake_case_allowed = self.allowed(db, struct_id.into(), allow::NON_SNAKE_CASE); | ||
336 | |||
337 | // Check the structure name. | ||
316 | let struct_name = data.name.to_string(); | 338 | let struct_name = data.name.to_string(); |
317 | let struct_name_replacement = if let Some(new_name) = to_camel_case(&struct_name) { | 339 | let struct_name_replacement = if let Some(new_name) = to_camel_case(&struct_name) { |
318 | let replacement = Replacement { | 340 | let replacement = Replacement { |
@@ -320,29 +342,35 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
320 | suggested_text: new_name, | 342 | suggested_text: new_name, |
321 | expected_case: CaseType::UpperCamelCase, | 343 | expected_case: CaseType::UpperCamelCase, |
322 | }; | 344 | }; |
323 | Some(replacement) | 345 | if non_camel_case_allowed { |
346 | None | ||
347 | } else { | ||
348 | Some(replacement) | ||
349 | } | ||
324 | } else { | 350 | } else { |
325 | None | 351 | None |
326 | }; | 352 | }; |
327 | 353 | ||
328 | // 2. Check the field names. | 354 | // Check the field names. |
329 | let mut struct_fields_replacements = Vec::new(); | 355 | let mut struct_fields_replacements = Vec::new(); |
330 | 356 | ||
331 | if let VariantData::Record(fields) = data.variant_data.as_ref() { | 357 | if !non_snake_case_allowed { |
332 | for (_, field) in fields.iter() { | 358 | if let VariantData::Record(fields) = data.variant_data.as_ref() { |
333 | let field_name = field.name.to_string(); | 359 | for (_, field) in fields.iter() { |
334 | if let Some(new_name) = to_lower_snake_case(&field_name) { | 360 | let field_name = field.name.to_string(); |
335 | let replacement = Replacement { | 361 | if let Some(new_name) = to_lower_snake_case(&field_name) { |
336 | current_name: field.name.clone(), | 362 | let replacement = Replacement { |
337 | suggested_text: new_name, | 363 | current_name: field.name.clone(), |
338 | expected_case: CaseType::LowerSnakeCase, | 364 | suggested_text: new_name, |
339 | }; | 365 | expected_case: CaseType::LowerSnakeCase, |
340 | struct_fields_replacements.push(replacement); | 366 | }; |
367 | struct_fields_replacements.push(replacement); | ||
368 | } | ||
341 | } | 369 | } |
342 | } | 370 | } |
343 | } | 371 | } |
344 | 372 | ||
345 | // 3. If there is at least one element to spawn a warning on, go to the source map and generate a warning. | 373 | // If there is at least one element to spawn a warning on, go to the source map and generate a warning. |
346 | self.create_incorrect_case_diagnostic_for_struct( | 374 | self.create_incorrect_case_diagnostic_for_struct( |
347 | struct_id, | 375 | struct_id, |
348 | db, | 376 | db, |
@@ -442,7 +470,12 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
442 | fn validate_enum(&mut self, db: &dyn HirDatabase, enum_id: EnumId) { | 470 | fn validate_enum(&mut self, db: &dyn HirDatabase, enum_id: EnumId) { |
443 | let data = db.enum_data(enum_id); | 471 | let data = db.enum_data(enum_id); |
444 | 472 | ||
445 | // 1. Check the enum name. | 473 | // Check whether non-camel case names are allowed for this enum. |
474 | if self.allowed(db, enum_id.into(), allow::NON_CAMEL_CASE_TYPES) { | ||
475 | return; | ||
476 | } | ||
477 | |||
478 | // Check the enum name. | ||
446 | let enum_name = data.name.to_string(); | 479 | let enum_name = data.name.to_string(); |
447 | let enum_name_replacement = if let Some(new_name) = to_camel_case(&enum_name) { | 480 | let enum_name_replacement = if let Some(new_name) = to_camel_case(&enum_name) { |
448 | let replacement = Replacement { | 481 | let replacement = Replacement { |
@@ -455,7 +488,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
455 | None | 488 | None |
456 | }; | 489 | }; |
457 | 490 | ||
458 | // 2. Check the field names. | 491 | // Check the field names. |
459 | let mut enum_fields_replacements = Vec::new(); | 492 | let mut enum_fields_replacements = Vec::new(); |
460 | 493 | ||
461 | for (_, variant) in data.variants.iter() { | 494 | for (_, variant) in data.variants.iter() { |
@@ -470,7 +503,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
470 | } | 503 | } |
471 | } | 504 | } |
472 | 505 | ||
473 | // 3. If there is at least one element to spawn a warning on, go to the source map and generate a warning. | 506 | // If there is at least one element to spawn a warning on, go to the source map and generate a warning. |
474 | self.create_incorrect_case_diagnostic_for_enum( | 507 | self.create_incorrect_case_diagnostic_for_enum( |
475 | enum_id, | 508 | enum_id, |
476 | db, | 509 | db, |
@@ -572,6 +605,10 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
572 | fn validate_const(&mut self, db: &dyn HirDatabase, const_id: ConstId) { | 605 | fn validate_const(&mut self, db: &dyn HirDatabase, const_id: ConstId) { |
573 | let data = db.const_data(const_id); | 606 | let data = db.const_data(const_id); |
574 | 607 | ||
608 | if self.allowed(db, const_id.into(), allow::NON_UPPER_CASE_GLOBAL) { | ||
609 | return; | ||
610 | } | ||
611 | |||
575 | let name = match &data.name { | 612 | let name = match &data.name { |
576 | Some(name) => name, | 613 | Some(name) => name, |
577 | None => return, | 614 | None => return, |
@@ -612,6 +649,10 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
612 | fn validate_static(&mut self, db: &dyn HirDatabase, static_id: StaticId) { | 649 | fn validate_static(&mut self, db: &dyn HirDatabase, static_id: StaticId) { |
613 | let data = db.static_data(static_id); | 650 | let data = db.static_data(static_id); |
614 | 651 | ||
652 | if self.allowed(db, static_id.into(), allow::NON_UPPER_CASE_GLOBAL) { | ||
653 | return; | ||
654 | } | ||
655 | |||
615 | let name = match &data.name { | 656 | let name = match &data.name { |
616 | Some(name) => name, | 657 | Some(name) => name, |
617 | None => return, | 658 | None => return, |
@@ -854,4 +895,29 @@ fn main() { | |||
854 | "#, | 895 | "#, |
855 | ); | 896 | ); |
856 | } | 897 | } |
898 | |||
899 | #[test] | ||
900 | fn allow_attributes() { | ||
901 | check_diagnostics( | ||
902 | r#" | ||
903 | #[allow(non_snake_case)] | ||
904 | fn NonSnakeCaseName(SOME_VAR: u8) -> u8{ | ||
905 | let OtherVar = SOME_VAR + 1; | ||
906 | OtherVar | ||
907 | } | ||
908 | |||
909 | #[allow(non_snake_case, non_camel_case_types)] | ||
910 | pub struct some_type { | ||
911 | SOME_FIELD: u8, | ||
912 | SomeField: u16, | ||
913 | } | ||
914 | |||
915 | #[allow(non_upper_case_globals)] | ||
916 | pub const some_const: u8 = 10; | ||
917 | |||
918 | #[allow(non_upper_case_globals)] | ||
919 | pub static SomeStatic: u8 = 10; | ||
920 | "#, | ||
921 | ); | ||
922 | } | ||
857 | } | 923 | } |