mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-10-15 18:22:01 +00:00
feat: enhance FindName reference validation (#826)
This commit is contained in:
@@ -12,25 +12,92 @@ Describe 'XAML UI contracts' {
|
||||
}
|
||||
}
|
||||
|
||||
It 'keeps every literal FindName reference backed by an XAML control' {
|
||||
$xamlNames = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
|
||||
It 'keeps every literal FindName reference backed by its receiver schema' {
|
||||
# Map receivers, not whole files: dialogs also look up controls on their owners.
|
||||
$receiverSchemas = @{
|
||||
'Show-MainWindow.ps1' = @{ window = 'MainWindow.xaml' }
|
||||
'MainWindow-AppSelection.ps1' = @{ window = 'MainWindow.xaml'; w = 'MainWindow.xaml' }
|
||||
'MainWindow-Deployment.ps1' = @{ window = 'MainWindow.xaml' }
|
||||
'MainWindow-Localization.ps1' = @{ window = 'MainWindow.xaml' }
|
||||
'MainWindow-Navigation.ps1' = @{ window = 'MainWindow.xaml' }
|
||||
'MainWindow-TweaksBuilder.ps1' = @{ window = 'MainWindow.xaml' }
|
||||
'Show-AboutDialog.ps1' = @{ aboutWindow = 'AboutWindow.xaml'; ownerWindow = 'MainWindow.xaml' }
|
||||
'Show-AppSelectionWindow.ps1' = @{ window = 'AppSelectionWindow.xaml'; 'script:GuiWindow' = 'MainWindow.xaml' }
|
||||
'Show-ApplyModal.ps1' = @{ applyWindow = 'ApplyChangesWindow.xaml'; ownerWindow = 'MainWindow.xaml' }
|
||||
'Show-SettingsDialog.ps1' = @{ dialog = 'SettingsWindow.xaml'; Owner = 'MainWindow.xaml' }
|
||||
'Show-MessageBox.ps1' = @{
|
||||
msgWindow = 'MessageBox.xaml'
|
||||
ownerWindow = @('MainWindow.xaml', 'AppSelectionWindow.xaml', 'RestoreBackupWindow.xaml')
|
||||
}
|
||||
'Show-ImportExportConfigWindow.ps1' = @{ dlg = 'ImportExportConfigWindow.xaml'; owner = 'MainWindow.xaml' }
|
||||
'Show-RestoreBackupDialog.ps1' = @{ window = 'RestoreBackupWindow.xaml'; ownerWindow = 'MainWindow.xaml' }
|
||||
'Show-Bubble.ps1' = @{ bubblePanel = 'BubbleHint.xaml' }
|
||||
}
|
||||
$schemaNames = @{}
|
||||
foreach ($xamlFile in $script:XamlFiles) {
|
||||
$content = Get-Content -LiteralPath $xamlFile.FullName -Raw
|
||||
foreach ($match in [regex]::Matches($content, '(?:x:Name|\bName)\s*=\s*"([^"]+)"')) {
|
||||
[void]$xamlNames.Add($match.Groups[1].Value)
|
||||
$names = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
|
||||
[xml]$xml = Get-Content -LiteralPath $xamlFile.FullName -Raw
|
||||
$namespaces = [System.Xml.XmlNamespaceManager]::new($xml.NameTable)
|
||||
$namespaces.AddNamespace('p', 'http://schemas.microsoft.com/winfx/2006/xaml/presentation')
|
||||
# Template children belong to separate namescopes and cannot satisfy window lookups.
|
||||
$attributes = $xml.SelectNodes(
|
||||
'//@*[not(ancestor::p:ControlTemplate or ancestor::p:DataTemplate or ancestor::p:ItemsPanelTemplate)]',
|
||||
$namespaces)
|
||||
foreach ($attribute in $attributes) {
|
||||
if ($attribute.LocalName -eq 'Name' -and
|
||||
$attribute.NamespaceURI -in @('', 'http://schemas.microsoft.com/winfx/2006/xaml')) {
|
||||
[void]$names.Add($attribute.Value)
|
||||
}
|
||||
}
|
||||
$schemaNames[$xamlFile.Name] = $names
|
||||
}
|
||||
|
||||
$references = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
|
||||
$failures = @()
|
||||
foreach ($guiFile in Get-ChildItem -LiteralPath $script:GuiPath -Filter '*.ps1' -File -Recurse) {
|
||||
$content = Get-Content -LiteralPath $guiFile.FullName -Raw
|
||||
foreach ($match in [regex]::Matches($content, "\.FindName\(\s*'([^']+)'\s*\)")) {
|
||||
[void]$references.Add($match.Groups[1].Value)
|
||||
$tokens = $null
|
||||
$parseErrors = $null
|
||||
$ast = [System.Management.Automation.Language.Parser]::ParseFile(
|
||||
$guiFile.FullName, [ref]$tokens, [ref]$parseErrors)
|
||||
$parseErrors | Should -BeNullOrEmpty
|
||||
$calls = $ast.FindAll({
|
||||
param($node)
|
||||
$node -is [System.Management.Automation.Language.InvokeMemberExpressionAst] -and
|
||||
$node.Member.Value -eq 'FindName' -and $node.Arguments.Count -eq 1 -and
|
||||
$node.Arguments[0] -is [System.Management.Automation.Language.StringConstantExpressionAst]
|
||||
}, $true)
|
||||
foreach ($call in $calls) {
|
||||
$name = $call.Arguments[0].Value
|
||||
$receiver = $call.Expression.Extent.Text
|
||||
$location = '{0}:{1} {2}.FindName({3})' -f
|
||||
$guiFile.Name, $call.Extent.StartLineNumber, $receiver, $name
|
||||
if ($call.Expression -isnot [System.Management.Automation.Language.VariableExpressionAst]) {
|
||||
$failures += "$location has no receiver mapping"
|
||||
continue
|
||||
}
|
||||
$receiver = $call.Expression.VariablePath.UserPath
|
||||
$mapping = $receiverSchemas[$guiFile.Name]
|
||||
$schemas = if ($mapping) { $mapping[$receiver] } else { $null }
|
||||
if (-not $schemas) {
|
||||
$failures += "$location has no receiver mapping"
|
||||
continue
|
||||
}
|
||||
foreach ($schema in @($schemas)) {
|
||||
if (-not $schemaNames.ContainsKey($schema)) {
|
||||
$failures += "$location maps to missing schema '$schema'"
|
||||
}
|
||||
elseif (-not $schemaNames[$schema].Contains($name)) {
|
||||
# Only the restore dialog owner may omit the message box overlay.
|
||||
if ($guiFile.Name -eq 'Show-MessageBox.ps1' -and $receiver -eq 'ownerWindow' -and
|
||||
$schema -eq 'RestoreBackupWindow.xaml' -and $name -ceq 'ModalOverlay') {
|
||||
continue
|
||||
}
|
||||
$failures += "$location is missing from '$schema'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$missing = @($references | Where-Object { -not $xamlNames.Contains($_) } | Sort-Object)
|
||||
$missing | Should -BeNullOrEmpty
|
||||
$failures | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It 'keeps the main window navigation and deployment controls available' {
|
||||
|
||||
Reference in New Issue
Block a user