extern crate rustc_ast;
extern crate rustc_span;
use rustc_ast::{Item, ItemKind};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_span::Span;
dylint_linting::declare_early_lint! {
#[doc = include_str!("../../docs/de05_client_layer/de0503_plugin_client_suffix/README.md")]
pub DE0503_PLUGIN_CLIENT_SUFFIX,
Deny,
"plugin client traits should use *PluginClient suffix, not *Api or *PluginApi (DE0503)"
}
impl EarlyLintPass for De0503PluginClientSuffix {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
let ItemKind::Trait(trait_data) = &item.kind else {
return;
};
if !crate::lint_utils::is_in_sdk_crate(cx, item.span) {
return;
}
let trait_name = trait_data.ident.name.as_str();
if trait_name.is_empty() {
return;
}
let version = crate::lint_utils::parse_version_suffix(trait_name);
let base_name = version.base;
if base_name.ends_with("PluginApi") {
emit_lint(cx, item.span, trait_name, "PluginApi", "PluginClient");
} else if base_name.ends_with("Api") {
let base_without_api = base_name.strip_suffix("Api").unwrap_or(base_name);
let name_lower = base_name.to_lowercase();
let is_plugin_api =
base_without_api.ends_with("Plugin") || name_lower.contains("plugin");
if is_plugin_api && !base_without_api.ends_with("Client") {
emit_lint(cx, item.span, trait_name, "Api", "PluginClient");
} else if base_without_api.ends_with("Client") {
emit_lint(cx, item.span, trait_name, "ClientApi", "Client");
}
}
}
}
fn emit_lint(
cx: &EarlyContext<'_>,
span: Span,
trait_name: &str,
wrong_suffix: &str,
suggested_suffix: &str,
) {
let version = crate::lint_utils::parse_version_suffix(trait_name);
let suggestion = if version.base.ends_with(wrong_suffix) {
let base = version.base.strip_suffix(wrong_suffix).unwrap();
if version.has_valid_version() {
format!("{base}{suggested_suffix}{}", version.version_suffix)
} else if version.has_malformed_version() {
if !version.malformed_digits.starts_with('0') {
format!("{base}{suggested_suffix}V{}", version.malformed_digits)
} else {
format!("{base}{suggested_suffix}")
}
} else {
format!("{base}{suggested_suffix}")
}
} else {
format!("{trait_name}Client")
};
cx.span_lint(DE0503_PLUGIN_CLIENT_SUFFIX, span, |diag| {
diag.primary_message(format!(
"plugin client trait `{trait_name}` should use `*{suggested_suffix}` suffix, not `*{wrong_suffix}` (DE0503)"
));
diag.help(format!(
"rename trait to `{suggestion}` to follow plugin client naming conventions"
));
});
}