MCPcopy Create free account
hub / github.com/PHPantom-dev/phpantom_lsp / check_scope

Function check_scope

src/diagnostics/unused_variables.rs:175–340  ·  view source on GitHub ↗

Check a single scope for unused variables. `promoted_params` is `Some` when checking a method — it lists constructor promoted parameter names that should be skipped.

(
    scope: &ScopeMap,
    ctx: &mut DiagnosticCtx<'_>,
    promoted_params: Option<&HashSet<String>>,
    compact_vars: &HashSet<String>,
)

Source from the content-addressed store, hash-verified

173/// `promoted_params` is `Some` when checking a method — it lists
174/// constructor promoted parameter names that should be skipped.
175fn check_scope(
176 scope: &ScopeMap,
177 ctx: &mut DiagnosticCtx<'_>,
178 promoted_params: Option<&HashSet<String>>,
179 compact_vars: &HashSet<String>,
180) {
181 if scope.frames.is_empty() {
182 return;
183 }
184
185 let always_skip: HashSet<&str> = {
186 let mut set: HashSet<&str> = HashSet::new();
187 for sg in SUPERGLOBALS {
188 set.insert(sg);
189 }
190 set.insert("$this");
191 set
192 };
193
194 // Build a set of parameter names for each nested frame so we can
195 // exclude them from the parent frame's writes. Closure and arrow
196 // function parameters are written at offsets that are inside the
197 // parent frame but outside the child frame body — the parent must
198 // not claim them as its own writes.
199 for frame in scope.frames.iter() {
200 // Skip top-level frames — global scope has too many implicit defs.
201 if frame.kind == FrameKind::TopLevel {
202 continue;
203 }
204
205 // For catch frames, we only check the catch variable (which is
206 // in frame.parameters). We don't re-check variables inherited
207 // from the parent — those are the parent frame's responsibility.
208 // This avoids duplicate diagnostics.
209 if frame.kind == FrameKind::Catch {
210 // Before PHP 8.0, catch variables are mandatory syntax —
211 // there is no way to omit them, so flagging them is noise.
212 if ctx.php_version >= PhpVersion::new(8, 0) {
213 check_catch_frame(frame, scope, ctx, &always_skip);
214 }
215 continue;
216 }
217
218 // Collect all variables written in this frame (directly, not
219 // inside nested frames that create their own scope).
220 let mut written_vars: HashMap<&str, u32> = HashMap::new();
221
222 for access in &scope.accesses {
223 if !matches!(access.kind, AccessKind::Write | AccessKind::ReadWrite) {
224 continue;
225 }
226 if access.offset < frame.start || access.offset > frame.end {
227 continue;
228 }
229 // Skip writes inside nested frames (closures, arrow fns,
230 // catch blocks) — those belong to the child scope.
231 if is_in_nested_frame(access.offset, frame, &scope.frames) {
232 continue;

Callers 2

collect_from_statementFunction · 0.70

Calls 15

check_catch_frameFunction · 0.85
is_catch_frame_parameterFunction · 0.85
is_blade_fileFunction · 0.85
has_reads_in_scopeFunction · 0.85
iterMethod · 0.80
as_strMethod · 0.80
containsMethod · 0.80
pushMethod · 0.80
is_in_nested_frameFunction · 0.70
is_emptyMethod · 0.45

Tested by

no test coverage detected