mirror of
https://github.com/zed-industries/zed.git
synced 2026-08-04 05:13:28 +00:00
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 ...
66 lines
2 KiB
Rust
66 lines
2 KiB
Rust
use clippy_utils::diagnostics::span_lint;
|
|
use rustc_hir::{Expr, ExprKind};
|
|
use rustc_lint::{LateContext, LateLintPass};
|
|
|
|
use crate::render_helpers::{
|
|
is_directly_in_render_method, is_gpui_entity_or_weak, is_unit_or_result_unit,
|
|
};
|
|
|
|
rustc_session::declare_lint! {
|
|
/// ### What it does
|
|
///
|
|
/// Flags calls to `Entity::update` or `WeakEntity::update` that execute
|
|
/// synchronously inside a `Render::render` or `RenderOnce::render` method
|
|
/// and whose closure returns `()` (indicating mutation rather than reading).
|
|
///
|
|
/// ### Why is this bad?
|
|
///
|
|
/// The `render` method should be a pure function of state. Calling
|
|
/// `.update()` mutates an entity during the render pass, which can trigger
|
|
/// re-renders mid-render and lead to inconsistent UI state or infinite
|
|
/// render loops.
|
|
pub ENTITY_UPDATE_IN_RENDER,
|
|
Warn,
|
|
"mutating an entity via `.update()` during render"
|
|
}
|
|
|
|
pub(crate) struct EntityUpdateInRender;
|
|
|
|
rustc_session::impl_lint_pass!(EntityUpdateInRender => [ENTITY_UPDATE_IN_RENDER]);
|
|
|
|
impl<'tcx> LateLintPass<'tcx> for EntityUpdateInRender {
|
|
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
|
if expr.span.from_expansion() {
|
|
return;
|
|
}
|
|
|
|
let ExprKind::MethodCall(segment, receiver, _args, _span) = &expr.kind else {
|
|
return;
|
|
};
|
|
|
|
if segment.ident.name.as_str() != "update" {
|
|
return;
|
|
}
|
|
|
|
let receiver_ty = cx.typeck_results().expr_ty(receiver);
|
|
if !is_gpui_entity_or_weak(cx, receiver_ty) {
|
|
return;
|
|
}
|
|
|
|
let call_ty = cx.typeck_results().expr_ty(expr);
|
|
if !is_unit_or_result_unit(cx, call_ty) {
|
|
return;
|
|
}
|
|
|
|
if !is_directly_in_render_method(cx, expr.hir_id) {
|
|
return;
|
|
}
|
|
|
|
span_lint(
|
|
cx,
|
|
ENTITY_UPDATE_IN_RENDER,
|
|
expr.span,
|
|
"entity `.update()` called during render mutates state in the render pass",
|
|
);
|
|
}
|
|
}
|