mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-30 22:01:37 +00:00
Auto merge of #18099 - ChayimFriedman2:diag-only-necessary, r=Veykril
Use more correct handling of lint attributes The previous analysis was top-down, and worked on a single file (expanding macros). The new analysis is bottom-up, starting from the diagnostics and climbing up the syntax and module tree. While this is more efficient (and in fact, efficiency was the motivating reason to work on this), unfortunately the code was already fast enough. But luckily, it also fixes a correctness problem: outline parent modules' attributes were not respected for the previous analysis. Case lints specifically did their own analysis to accommodate that, but it was limited to only them. The new analysis works on all kinds of lints, present and future. It was basically impossible to fix the old analysis without rewriting it because navigating the module hierarchy must come bottom-up, and if we already have a bottom-up analysis (including syntax analysis because modules can be nested in other syntax elements, including macros), it makes sense to use only this kind of analysis. Few other bugs (not fundamental to the previous analysis) are also fixed, e.g. overwriting of lint levels (i.e. `#[allow(lint)] mod foo { #[warn(lint)] mod bar; }`. After this PR is merged I intend to work on an editor command that does workspace-wide diagnostics analysis (that is, `rust-analyzer diagnostics` but from your editor and without having to spawn a new process, which will have to analyze the workspace from scratch). This can be useful to users who do not want to enable check on save because of its overhead, but want to see workspace wide diagnostics from r-a (or to maintainers of rust-analyzer). Closes #18086. Closes #18081. Fixes #18056.
This commit is contained in:
commit
fd243cd0fb
6 changed files with 410 additions and 286 deletions
|
@ -16,15 +16,14 @@ mod case_conv;
|
|||
use std::fmt;
|
||||
|
||||
use hir_def::{
|
||||
data::adt::VariantData, db::DefDatabase, hir::Pat, src::HasSource, AdtId, AttrDefId, ConstId,
|
||||
EnumId, EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup, ModuleDefId, ModuleId,
|
||||
StaticId, StructId, TraitId, TypeAliasId,
|
||||
data::adt::VariantData, db::DefDatabase, hir::Pat, src::HasSource, AdtId, ConstId, EnumId,
|
||||
EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup, ModuleDefId, ModuleId, StaticId,
|
||||
StructId, TraitId, TypeAliasId,
|
||||
};
|
||||
use hir_expand::{
|
||||
name::{AsName, Name},
|
||||
HirFileId, HirFileIdExt, MacroFileIdExt,
|
||||
HirFileId, HirFileIdExt,
|
||||
};
|
||||
use intern::sym;
|
||||
use stdx::{always, never};
|
||||
use syntax::{
|
||||
ast::{self, HasName},
|
||||
|
@ -36,14 +35,6 @@ use crate::db::HirDatabase;
|
|||
|
||||
use self::case_conv::{to_camel_case, to_lower_snake_case, to_upper_snake_case};
|
||||
|
||||
mod allow {
|
||||
pub(super) const BAD_STYLE: &str = "bad_style";
|
||||
pub(super) const NONSTANDARD_STYLE: &str = "nonstandard_style";
|
||||
pub(super) const NON_SNAKE_CASE: &str = "non_snake_case";
|
||||
pub(super) const NON_UPPER_CASE_GLOBAL: &str = "non_upper_case_globals";
|
||||
pub(super) const NON_CAMEL_CASE_TYPES: &str = "non_camel_case_types";
|
||||
}
|
||||
|
||||
pub fn incorrect_case(db: &dyn HirDatabase, owner: ModuleDefId) -> Vec<IncorrectCase> {
|
||||
let _p = tracing::info_span!("incorrect_case").entered();
|
||||
let mut validator = DeclValidator::new(db);
|
||||
|
@ -160,92 +151,7 @@ impl<'a> DeclValidator<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks whether not following the convention is allowed for this item.
|
||||
fn allowed(&self, id: AttrDefId, allow_name: &str, recursing: bool) -> bool {
|
||||
let is_allowed = |def_id| {
|
||||
let attrs = self.db.attrs(def_id);
|
||||
// don't bug the user about directly no_mangle annotated stuff, they can't do anything about it
|
||||
(!recursing && attrs.by_key(&sym::no_mangle).exists())
|
||||
|| attrs.by_key(&sym::allow).tt_values().any(|tt| {
|
||||
let allows = tt.to_string();
|
||||
allows.contains(allow_name)
|
||||
|| allows.contains(allow::BAD_STYLE)
|
||||
|| allows.contains(allow::NONSTANDARD_STYLE)
|
||||
})
|
||||
};
|
||||
let db = self.db.upcast();
|
||||
let file_id_is_derive = || {
|
||||
match id {
|
||||
AttrDefId::ModuleId(m) => {
|
||||
m.def_map(db)[m.local_id].origin.file_id().map(Into::into)
|
||||
}
|
||||
AttrDefId::FunctionId(f) => Some(f.lookup(db).id.file_id()),
|
||||
AttrDefId::StaticId(sid) => Some(sid.lookup(db).id.file_id()),
|
||||
AttrDefId::ConstId(cid) => Some(cid.lookup(db).id.file_id()),
|
||||
AttrDefId::TraitId(tid) => Some(tid.lookup(db).id.file_id()),
|
||||
AttrDefId::TraitAliasId(taid) => Some(taid.lookup(db).id.file_id()),
|
||||
AttrDefId::ImplId(iid) => Some(iid.lookup(db).id.file_id()),
|
||||
AttrDefId::ExternBlockId(id) => Some(id.lookup(db).id.file_id()),
|
||||
AttrDefId::ExternCrateId(id) => Some(id.lookup(db).id.file_id()),
|
||||
AttrDefId::UseId(id) => Some(id.lookup(db).id.file_id()),
|
||||
// These warnings should not explore macro definitions at all
|
||||
AttrDefId::MacroId(_) => None,
|
||||
AttrDefId::AdtId(aid) => match aid {
|
||||
AdtId::StructId(sid) => Some(sid.lookup(db).id.file_id()),
|
||||
AdtId::EnumId(eid) => Some(eid.lookup(db).id.file_id()),
|
||||
// Unions aren't yet supported
|
||||
AdtId::UnionId(_) => None,
|
||||
},
|
||||
AttrDefId::FieldId(_) => None,
|
||||
AttrDefId::EnumVariantId(_) => None,
|
||||
AttrDefId::TypeAliasId(_) => None,
|
||||
AttrDefId::GenericParamId(_) => None,
|
||||
}
|
||||
.map_or(false, |file_id| {
|
||||
matches!(file_id.macro_file(), Some(file_id) if file_id.is_custom_derive(db.upcast()) || file_id.is_builtin_derive(db.upcast()))
|
||||
})
|
||||
};
|
||||
|
||||
let parent = || {
|
||||
match id {
|
||||
AttrDefId::ModuleId(m) => m.containing_module(db).map(|v| v.into()),
|
||||
AttrDefId::FunctionId(f) => Some(f.lookup(db).container.into()),
|
||||
AttrDefId::StaticId(sid) => Some(sid.lookup(db).container.into()),
|
||||
AttrDefId::ConstId(cid) => Some(cid.lookup(db).container.into()),
|
||||
AttrDefId::TraitId(tid) => Some(tid.lookup(db).container.into()),
|
||||
AttrDefId::TraitAliasId(taid) => Some(taid.lookup(db).container.into()),
|
||||
AttrDefId::ImplId(iid) => Some(iid.lookup(db).container.into()),
|
||||
AttrDefId::ExternBlockId(id) => Some(id.lookup(db).container.into()),
|
||||
AttrDefId::ExternCrateId(id) => Some(id.lookup(db).container.into()),
|
||||
AttrDefId::UseId(id) => Some(id.lookup(db).container.into()),
|
||||
// These warnings should not explore macro definitions at all
|
||||
AttrDefId::MacroId(_) => None,
|
||||
AttrDefId::AdtId(aid) => match aid {
|
||||
AdtId::StructId(sid) => Some(sid.lookup(db).container.into()),
|
||||
AdtId::EnumId(eid) => Some(eid.lookup(db).container.into()),
|
||||
// Unions aren't yet supported
|
||||
AdtId::UnionId(_) => None,
|
||||
},
|
||||
AttrDefId::FieldId(_) => None,
|
||||
AttrDefId::EnumVariantId(_) => None,
|
||||
AttrDefId::TypeAliasId(_) => None,
|
||||
AttrDefId::GenericParamId(_) => None,
|
||||
}
|
||||
.is_some_and(|mid| self.allowed(mid, allow_name, true))
|
||||
};
|
||||
is_allowed(id)
|
||||
// FIXME: this is a hack to avoid false positives in derive macros currently
|
||||
|| file_id_is_derive()
|
||||
// go upwards one step or give up
|
||||
|| parent()
|
||||
}
|
||||
|
||||
fn validate_module(&mut self, module_id: ModuleId) {
|
||||
// Check whether non-snake case identifiers are allowed for this module.
|
||||
if self.allowed(module_id.into(), allow::NON_SNAKE_CASE, false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the module name.
|
||||
let Some(module_name) = module_id.name(self.db.upcast()) else { return };
|
||||
let Some(module_name_replacement) =
|
||||
|
@ -270,11 +176,6 @@ impl<'a> DeclValidator<'a> {
|
|||
}
|
||||
|
||||
fn validate_trait(&mut self, trait_id: TraitId) {
|
||||
// Check whether non-snake case identifiers are allowed for this trait.
|
||||
if self.allowed(trait_id.into(), allow::NON_CAMEL_CASE_TYPES, false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the trait name.
|
||||
let data = self.db.trait_data(trait_id);
|
||||
self.create_incorrect_case_diagnostic_for_item_name(
|
||||
|
@ -292,11 +193,6 @@ impl<'a> DeclValidator<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
// Check whether non-snake case identifiers are allowed for this function.
|
||||
if self.allowed(func.into(), allow::NON_SNAKE_CASE, false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the function name.
|
||||
// Skipped if function is an associated item of a trait implementation.
|
||||
if !self.is_trait_impl_container(container) {
|
||||
|
@ -389,17 +285,13 @@ impl<'a> DeclValidator<'a> {
|
|||
|
||||
fn validate_struct(&mut self, struct_id: StructId) {
|
||||
// Check the structure name.
|
||||
let non_camel_case_allowed =
|
||||
self.allowed(struct_id.into(), allow::NON_CAMEL_CASE_TYPES, false);
|
||||
if !non_camel_case_allowed {
|
||||
let data = self.db.struct_data(struct_id);
|
||||
self.create_incorrect_case_diagnostic_for_item_name(
|
||||
struct_id,
|
||||
&data.name,
|
||||
CaseType::UpperCamelCase,
|
||||
IdentType::Structure,
|
||||
);
|
||||
}
|
||||
let data = self.db.struct_data(struct_id);
|
||||
self.create_incorrect_case_diagnostic_for_item_name(
|
||||
struct_id,
|
||||
&data.name,
|
||||
CaseType::UpperCamelCase,
|
||||
IdentType::Structure,
|
||||
);
|
||||
|
||||
// Check the field names.
|
||||
self.validate_struct_fields(struct_id);
|
||||
|
@ -407,10 +299,6 @@ impl<'a> DeclValidator<'a> {
|
|||
|
||||
/// Check incorrect names for struct fields.
|
||||
fn validate_struct_fields(&mut self, struct_id: StructId) {
|
||||
if self.allowed(struct_id.into(), allow::NON_SNAKE_CASE, false) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = self.db.struct_data(struct_id);
|
||||
let VariantData::Record(fields) = data.variant_data.as_ref() else {
|
||||
return;
|
||||
|
@ -484,11 +372,6 @@ impl<'a> DeclValidator<'a> {
|
|||
fn validate_enum(&mut self, enum_id: EnumId) {
|
||||
let data = self.db.enum_data(enum_id);
|
||||
|
||||
// Check whether non-camel case names are allowed for this enum.
|
||||
if self.allowed(enum_id.into(), allow::NON_CAMEL_CASE_TYPES, false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the enum name.
|
||||
self.create_incorrect_case_diagnostic_for_item_name(
|
||||
enum_id,
|
||||
|
@ -653,10 +536,6 @@ impl<'a> DeclValidator<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
if self.allowed(const_id.into(), allow::NON_UPPER_CASE_GLOBAL, false) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = self.db.const_data(const_id);
|
||||
let Some(name) = &data.name else {
|
||||
return;
|
||||
|
@ -676,10 +555,6 @@ impl<'a> DeclValidator<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
if self.allowed(static_id.into(), allow::NON_UPPER_CASE_GLOBAL, false) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.create_incorrect_case_diagnostic_for_item_name(
|
||||
static_id,
|
||||
&data.name,
|
||||
|
@ -695,11 +570,6 @@ impl<'a> DeclValidator<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
// Check whether non-snake case identifiers are allowed for this type alias.
|
||||
if self.allowed(type_alias_id.into(), allow::NON_CAMEL_CASE_TYPES, false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the type alias name.
|
||||
let data = self.db.type_alias_data(type_alias_id);
|
||||
self.create_incorrect_case_diagnostic_for_item_name(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue