Files
zed/tooling/lints/src/render_helpers.rs
T
Miguel Raz Guzmán Macedo 1d1e19985a Add dylint lint library for Zed-specific patterns (#58496)
Adds a dylint library under tooling/lints that flags Zed-specific
anti-patterns:

* shared_string_from_str_literal, 
* async_block_without_await, 
* entity_update_in_render, 
* notify_in_render, 
* owned_string_into_shared, 
* len_in_loop_condition, and 
* blocking_io_on_foreground. 

Includes UI tests, a single-lint helper, and workspace.metadata.dylint
registration so cargo dylint --all discovers it. The library pins its
own nightly toolchain (kept out of the main workspace) and tracks dylint
6.

Release Notes:

- N/A

Self-Review Checklist:

- [ ] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [ ] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [ ] Performance impact has been considered and is acceptable

Closes #ISSUE

Release Notes:

- N/A or Added/Fixed/Improved ...
2026-07-03 22:05:34 +00:00

99 lines
3.5 KiB
Rust

use rustc_hir::def_id::DefId;
use rustc_hir::{ExprKind, HirId, Node};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
/// Returns `true` when `hir_id` sits directly inside a `fn render` that
/// implements `gpui::Render` or `gpui::RenderOnce`, without an intervening
/// closure. If a closure sits between `hir_id` and the `render` method, the
/// expression executes later (e.g. in an event handler) and is not flagged.
pub(crate) fn is_directly_in_render_method(cx: &LateContext<'_>, hir_id: HirId) -> bool {
for (parent_id, node) in cx.tcx.hir_parent_iter(hir_id) {
match node {
Node::Expr(expr) if matches!(expr.kind, ExprKind::Closure(_)) => {
return false;
}
Node::ImplItem(impl_item) if impl_item.ident.name.as_str() == "render" => {
return is_render_trait_impl(cx, parent_id);
}
_ => {}
}
}
false
}
/// Returns `true` when the `impl` block that owns `impl_item_hir_id` is an
/// implementation of `gpui::Render` or `gpui::RenderOnce`.
fn is_render_trait_impl(cx: &LateContext<'_>, impl_item_hir_id: HirId) -> bool {
let parent_owner = cx.tcx.hir_get_parent_item(impl_item_hir_id);
let node = cx.tcx.hir_node(parent_owner.into());
if let Node::Item(item) = node {
if let rustc_hir::ItemKind::Impl(impl_block) = &item.kind {
if let Some(trait_ref) = &impl_block.of_trait {
if let rustc_hir::def::Res::Def(_, trait_def_id) = trait_ref.trait_ref.path.res {
return is_gpui_render_trait(cx, trait_def_id);
}
}
}
}
false
}
fn is_gpui_render_trait(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
let crate_name = cx.tcx.crate_name(trait_def_id.krate);
if crate_name.as_str() != "gpui" {
return false;
}
let name = cx.tcx.item_name(trait_def_id);
name.as_str() == "Render" || name.as_str() == "RenderOnce"
}
/// Returns `true` when `ty` is `gpui::Entity<T>` or `gpui::WeakEntity<T>`.
pub(crate) fn is_gpui_entity_or_weak(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
let peeled = ty.peel_refs();
let Some(adt) = peeled.ty_adt_def() else {
return false;
};
let did = adt.did();
let crate_name = cx.tcx.crate_name(did.krate);
if crate_name.as_str() != "gpui" {
return false;
}
let name = cx.tcx.item_name(did);
name.as_str() == "Entity" || name.as_str() == "WeakEntity"
}
/// Returns `true` when `ty` is `gpui::Context<T>`.
pub(crate) fn is_gpui_context(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
let peeled = ty.peel_refs();
let Some(adt) = peeled.ty_adt_def() else {
return false;
};
let did = adt.did();
let crate_name = cx.tcx.crate_name(did.krate);
if crate_name.as_str() != "gpui" {
return false;
}
cx.tcx.item_name(did).as_str() == "Context"
}
/// Returns `true` when the expression type indicates a unit-returning update
/// call — either `()` (from `Entity::update`) or `Result<(), _>` (from
/// `WeakEntity::update`).
pub(crate) fn is_unit_or_result_unit(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
if ty.is_unit() {
return true;
}
if let Some(adt) = ty.ty_adt_def() {
let path = cx.tcx.def_path_str(adt.did());
if path == "core::result::Result" || path == "std::result::Result" {
if let Some(substs) = ty.walk().nth(1) {
if let Some(inner_ty) = substs.as_type() {
return inner_ty.is_unit();
}
}
}
}
false
}