diff --git a/php-transformer/src/ArtifactCompiler/RuntimeDependencyParityReport.php b/php-transformer/src/ArtifactCompiler/RuntimeDependencyParityReport.php index c1bfc4b8..bb1d47ef 100644 --- a/php-transformer/src/ArtifactCompiler/RuntimeDependencyParityReport.php +++ b/php-transformer/src/ArtifactCompiler/RuntimeDependencyParityReport.php @@ -674,6 +674,20 @@ private function withRuntimeIslandTargets(array $targets, array $runtimeIslands) $targets['classes'][$class] = true; } } + + $tag = is_string($island['tag'] ?? null) ? strtolower(trim($island['tag'])) : ''; + foreach ( $attributes as $name => $value ) { + $attributeName = strtolower((string) $name); + if ( ! str_starts_with($attributeName, 'data-') || ! preg_match('/^data-[a-z][a-z0-9_-]*$/', $attributeName) ) { + continue; + } + + $attributeSelector = '[' . $attributeName . ']'; + $targets['selectors'][$attributeSelector] = true; + if ( '' !== $tag ) { + $targets['selectors'][$tag . $attributeSelector] = true; + } + } } return $targets; @@ -1175,7 +1189,7 @@ private function targetExists(array $dependency, array $targets): bool private function scriptKind(string $path, string $script): string { $haystack = strtolower($path . "\n" . substr($script, 0, 2000)); - if ( str_contains($haystack, 'netlify') || str_contains($haystack, 'rum') || str_contains($haystack, 'analytics') || str_contains($haystack, 'gtag') ) { + if ( preg_match('#(?:netlify|analytics|gtag|\brum\b|[-_./]rum[-_./])#', $haystack) ) { return 'telemetry'; } diff --git a/php-transformer/tests/contract/run.php b/php-transformer/tests/contract/run.php index 05b8fd06..3306df7e 100644 --- a/php-transformer/tests/contract/run.php +++ b/php-transformer/tests/contract/run.php @@ -1713,6 +1713,7 @@ public function match(DOMElement $element, PatternContext $context): ?array $assert(true === ($expandedRuntimeDependencies['canvas.preview']['canvas_api'] ?? null), 'compound canvas selector records canvas API usage'); $assert(str_contains($expandedRuntimeMarkup, ''), 'compound canvas selector preserves canvas markup'); $assert(str_contains($expandedRuntimeMarkup, 'data-tool'), 'data attribute runtime selector remains addressable in generated markup'); +$assert(true === ($expandedRuntimeDependencies['[data-tool]']['source_present'] ?? null), 'data attribute runtime selector is recorded as present in source markup'); $assert(str_contains($expandedRuntimeMarkup, 'mounted-app'), 'app root receiving appended children remains addressable in generated markup'); $assert(array() === ($expandedRuntimeReport['findings'] ?? array()), 'expanded runtime target selectors do not emit missing-target findings'); @@ -1774,6 +1775,32 @@ public function match(DOMElement $element, PatternContext $context): ?array 'runtime dependency parity does not fail entry output for selectors absent from that entry source' ); +$sharedDrumScriptSite = $compiler->compile( + array( + 'entrypoint' => 'index.html', + 'files' => array( + 'index.html' => '

Drum machine

', + 'patterns.html' => '
', + 'js/site.js' => 'document.querySelectorAll("[data-groove], [data-voice-demo]"); document.querySelectorAll(".is-playing").forEach(function (button) { button.classList.remove("is-playing"); });', + ), + ) +)->toArray(); +$sharedDrumScriptReport = $sharedDrumScriptSite['source_reports']['runtime_dependency_parity'] ?? array(); +$sharedDrumDependencies = $sharedDrumScriptReport['dependencies'] ?? array(); +$sharedDrumDependency = array_values(array_filter($sharedDrumDependencies, static fn (array $dependency): bool => '[data-voice-demo]' === ($dependency['selector'] ?? '')))[0] ?? null; +$assert( + null !== $sharedDrumDependency, + 'runtime dependency parity records shared data-attribute selectors absent from the entry source' +); +$assert( + 'first_party' === ($sharedDrumDependency['script_kind'] ?? ''), + 'runtime dependency parity does not classify drum scripts as RUM telemetry' +); +$assert( + array() === array_values(array_filter($sharedDrumScriptReport['findings'] ?? array(), static fn (array $finding): bool => in_array($finding['selector'] ?? '', array('.is-playing', '[data-voice-demo]', '[data-groove]'), true))), + 'runtime dependency parity does not fail entry output for shared drum script selectors absent from that entry source' +); + $hamburgerOverlaySite = $compiler->compile( array( 'entrypoint' => 'index.html',