feat: recognize // volume: mounts in PHP scripts (#11018)

* feat: recognize `// volume:` mounts in PHP scripts

Volume annotations were parsed for every language but PHP, so a PHP script
could not mount a workspace volume. Two things stood in the way: PHP had no
entry in the comment-prefix maps, and a PHP script opens with `<?php`, which
ends the leading comment block the parsers scan before any annotation is read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3FR7iS9nRhpFt615cnuQ7

* fix: tolerate a PHP opener that carries code, drop the inert CLI hunk

The open-tag skip matched `<?php` exactly, so `<?php declare(strict_types=1);`
still ended the leading comment block and every annotation below it was silently
ignored. Match the tag as a case-insensitive prefix and skip the whole line.

The CLI local-graph hunk could never fire: PHP has no wasm asset parser, so
`fallbackParse` handles it, and its own header scan stops at `<?php` — the script
is dropped as a non-pipeline-member before any volume asset is read. Making only
the CLI PHP-aware would also put the local graph out of parity with the deployed
one, whose `parse_pipeline_annotations` stops there too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3FR7iS9nRhpFt615cnuQ7

* docs: correct the CLI mirror comment, state the own-line annotation rule

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3FR7iS9nRhpFt615cnuQ7

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-09-08 13:23:35 +02:00
committed by GitHub
co-authored by Claude Opus 5
parent 3e3a41d418
commit f081fb1070
5 changed files with 60 additions and 8 deletions
@@ -78,20 +78,26 @@ fn comment_prefix(lang: &ScriptLang) -> Option<&'static str> {
| ScriptLang::Bun
| ScriptLang::Bunnative
| ScriptLang::Nativets
| ScriptLang::Go => Some("//"),
| ScriptLang::Go
| ScriptLang::Php => Some("//"),
_ => None,
}
}
/// Mirror of the frontend `parseVolumeAnnotations` (infer.ts): `<prefix>
/// volume: <path>` lines in the leading comment block, each an `rw` volume
/// asset. Scanning stops at the first non-comment line (blank lines are
/// skipped), exactly like the frontend.
/// asset. Scanning stops at the first non-comment line; blank lines and PHP's
/// opening tag line are skipped whole (the tag may carry code, so an annotation
/// must sit on its own line below it), exactly like the frontend.
fn parse_volume_annotations(content: &str, prefix: &str) -> Vec<AssetWithAltAccessType> {
let mut volumes = Vec::new();
for line in content.lines() {
let trimmed = line.trim();
if trimmed.is_empty() {
if trimmed.is_empty()
|| trimmed
.get(..5)
.is_some_and(|p| p.eq_ignore_ascii_case("<?php"))
{
continue;
}
let Some(after) = trimmed.strip_prefix(prefix) else {
@@ -258,4 +264,13 @@ mod tests {
assert_eq!(vols[0].path, "my_vol");
assert_eq!(vols[0].access_type, Some(AssetUsageAccessType::RW));
}
#[test]
fn php_volume_annotations_survive_the_open_tag() {
let content = "<?php\n\n// volume: my_vol\nfunction main() {}\n";
let got = effective_script_assets(&ScriptLang::Php, content, None).unwrap();
let vols: Vec<_> = got.iter().filter(|a| a.kind == AssetKind::Volume).collect();
assert_eq!(vols.len(), 1);
assert_eq!(vols[0].path, "my_vol");
}
}