Skip to content

Extend the path_within_root emit-safe guard to the translation/config (and middleware/feature/env) "Expected at:" diagnostic surfaces - #217

Merged
mikebronner merged 2 commits into
mainfrom
fix/214-extend-the-pathwithinroot-emit-safe-guard-to-the-t
Jun 18, 2026
Merged

mikebronner merged 2 commits into
mainfrom
fix/214-extend-the-pathwithinroot-emit-safe-guard-to-the-t

Conversation

@mikebronner

@mikebronner mikebronner commented Jun 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Implements #214. Extends the path_within_root emit-safe containment guard (in_root_expected_path_hint, added in PR #202) to the remaining from_diagnostic → CreateFile diagnostic surfaces that #201 deliberately left out of scope: translation, config, middleware, feature, and environment-variable "not found" diagnostics.

Each surface echoed an unfiltered expected_path into its Expected at: message, which FileAction::from_diagnostic parses back into a CreateFile target a client could follow. A user-registered out-of-root path or a dangling under-root symlink could be echoed into the message and turned into a create target outside the project tree — the exact escape #201 closed for the view/component surfaces. Routing every hint through in_root_expected_path_hint falls back to "unknown" for any candidate that isn't emit-safe, while still admitting a genuinely-absent in-root create target.

Audit findings (per surface)

Surface expected_path source Out-of-root vector Routed
Translation vendor_map.get(namespace) user-registered vendor lang dir ✅
Config root.join("config") structurally in-root; routed for the uniform invariant + dangling symlink ✅
Middleware resolve_class_to_file (PSR-4) + registry class file ..-injected / out-of-root PSR-4 class name ✅
Feature resolve_class_to_file + root.join("app/Features") as middleware + uniform invariant ✅
Env root.join(".env") path string not user-controlled, but routed to close a dangling .env symlink + uniform invariant ✅

The Copy from: (.env.example) line is left as-is — it is a copy source (read), structurally in-root, not the from_diagnostic → CreateFile target; out of this issue's scope.

Changes

  • create_translation_diagnostic / create_config_diagnostic: take a root: &Path and build the Expected at: string via in_root_expected_path_hint (guards all 3 translation message sites + the config site at one point).
  • Middleware (2 sites), feature (3 sites): guard each inline resolve_class_to_file / root.join hint.
  • Env (2 message branches): compute one guarded env_expected_hint and use it in both branches.
  • New test file src/tests/expected_path_diagnostic_containment.rs (+ mod registration) with per-surface regression + positive-control tests mirroring the Extend the path_within_root containment guard to the remaining diagnostic surfaces (Livewire diagnostic fallback + "Expected at:" message hint) #201 component test, plus a dangling-.env-symlink case.

Acceptance Criteria

  • Audit middleware "not found" arm; route expected_path through in_root_expected_path_hint (PSR-4-resolved class name — confirmed user-controllable).
  • Audit feature "not found" arm; route expected_path through the guard (PSR-4 + root.join).
  • Audit environment-variable arm; route through the guard (path is root.join-rooted, routed to close the dangling-.env-symlink vector and keep the invariant uniform).
  • Route the translation "not found" expected_path (vendor_map.get(namespace)) through the guard.
  • Route the config "not found" expected_path through the guard with the same guarantee.
  • Add an all-candidates-out-of-root → "unknown" regression test for each surface.
  • Add a positive-control test for each surface (single in-root missing candidate returned as-is).
  • All pre-existing tests in view_diagnostic_containment.rs, view_navigation_containment.rs, component_navigation_containment.rs, and code_action_create_containment.rs pass without modification.

Test Plan

  • cargo test --bin laravel-lsp — 416 tests pass (11 new), incl. the four AC-mandated suites unmodified.
  • cargo clippy --all-targets — clean.
  • cargo fmt --check — clean.

Fixes #214

mikebronner and others added 2 commits June 18, 2026 08:03
…afe guard

Extend the path_within_root emit-safe containment guard
(in_root_expected_path_hint, added in #202) to the translation, config,
middleware, feature, and environment-variable "not found" diagnostic
surfaces — the remaining members of the from_diagnostic -> CreateFile
family that #201 deliberately left out of scope.

Each surface echoed an unfiltered expected_path into its "Expected at:"
message, which FileAction::from_diagnostic parses back into a CreateFile
target. A user-registered out-of-root path (translation vendor_map, PSR-4
class resolution for middleware/feature) or a dangling under-root symlink
could be echoed and followed out of the project tree. Routing every hint
through in_root_expected_path_hint falls back to "unknown" for any
candidate that is not emit-safe, while still admitting a genuinely-absent
in-root create target.

- translation/config: guard expected_path in create_*_diagnostic (root param)
- middleware/feature: guard the inline resolve_class_to_file / root.join hints
- env: guard the root.join(".env") hint (closes the dangling .env symlink)
- add per-surface regression + positive-control tests mirroring #201

Fixes #214

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mikebronner
mikebronner marked this pull request as ready for review June 18, 2026 15:16

@mr-sherlock-holmes mr-sherlock-holmes Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved

Review Summary

  • Routes the five remaining from_diagnostic → CreateFile "Expected at:" surfaces — translation, config, middleware, feature, env — through the emit-safe in_root_expected_path_hint guard, completing the containment invariant #201 started for view/component. +368/−22 across main.rs and a new test module.
  • All 8 acceptance criteria met. Translation (create_translation_diagnostic) and config (create_config_diagnostic) gain a root: &Path param and route check.expected_path through the guard; all three translation call sites + both config call sites pass root correctly. Middleware (both arms: mw_class_path, mw_file_path), feature (all three: PSR-4 class, string-key app/Features, @feature directive), and env (env_expected_hint used in both message branches) route the correct path variable with the correct in-scope root.
  • Deliberate divergence, noted and correct: AC #3/#5 (env, config) are structurally root.join-rooted, so the AC's "if user-controlled, route it" condition didn't strictly compel routing them — Watson routed them anyway for invariant uniformity and to close the dangling-under-root-symlink leaf. Strict improvement, nothing dropped. ✅
  • Tests verified and honest. New expected_path_diagnostic_containment.rs gives each of the 5 surfaces a regression case (all-candidates-out-of-root → "unknown") and a positive-control case (in-root-but-missing → returned as-is), plus a #[cfg(unix)] dangling-symlink case for env. Real assert_eq! on the return, sound canonicalize().is_err() preconditions. They drive the helper directly — which I confirmed genuinely mirrors the #201 view_diagnostic_containment.rs style (those tests import and call in_root_expected_path_hint directly too), so "same style" is accurate, not a shortcut. Module wired into mod.rs. The four pre-existing containment test files are untouched (AC #8); diagnostic_severity.rs is a mechanical signature fixup only.
  • CI green: LSP test/fmt/clippy ✅, Extension wasm/fmt/clippy ✅, CodeQL ✅.

📋 Non-blocking follow-ups

  • Inertia "page not found" Expected at: surface is the next sibling in this lineage and was not in #214's scope — main.rs:~15911 emits page_create_path(...) raw via .to_string_lossy(), from_diagnostic parses it into a CreateFile target, and the build_code_action backstop uses path_within_root_lexical (canonical_containment(...).unwrap_or(true), path_containment.rs:~104), which admits a dangling under-root symlink — the exact narrow case path_within_root_emit_safe refuses. Defense-in-depth only (requires a hostile repo shipping a dangling symlink at the page path and a user clicking create; is_valid_page_name already blocks ../absolute escapes), same class and severity as the #130→#201→#214 lineage tracks as non-blocking. Outside #214's deliberately-scoped contract and named by no AC item here, so it does not block this PR — opening a tracked follow-up issue. (Adversarially verified: UPHELD.)

Ready for @mikebronner to merge.

@mikebronner
mikebronner merged commit 6be57b4 into main Jun 18, 2026
5 checks passed
@mikebronner
mikebronner deleted the fix/214-extend-the-pathwithinroot-emit-safe-guard-to-the-t branch June 18, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extend the path_within_root emit-safe guard to the translation/config (and middleware/feature/env) "Expected at:" diagnostic surfaces

1 participant