mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-10-21 05:02: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' {
|
It 'keeps every literal FindName reference backed by its receiver schema' {
|
||||||
$xamlNames = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
|
# 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) {
|
foreach ($xamlFile in $script:XamlFiles) {
|
||||||
$content = Get-Content -LiteralPath $xamlFile.FullName -Raw
|
$names = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
|
||||||
foreach ($match in [regex]::Matches($content, '(?:x:Name|\bName)\s*=\s*"([^"]+)"')) {
|
[xml]$xml = Get-Content -LiteralPath $xamlFile.FullName -Raw
|
||||||
[void]$xamlNames.Add($match.Groups[1].Value)
|
$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) {
|
foreach ($guiFile in Get-ChildItem -LiteralPath $script:GuiPath -Filter '*.ps1' -File -Recurse) {
|
||||||
$content = Get-Content -LiteralPath $guiFile.FullName -Raw
|
$tokens = $null
|
||||||
foreach ($match in [regex]::Matches($content, "\.FindName\(\s*'([^']+)'\s*\)")) {
|
$parseErrors = $null
|
||||||
[void]$references.Add($match.Groups[1].Value)
|
$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)
|
$failures | Should -BeNullOrEmpty
|
||||||
$missing | Should -BeNullOrEmpty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
It 'keeps the main window navigation and deployment controls available' {
|
It 'keeps the main window navigation and deployment controls available' {
|
||||||
|
|||||||
Reference in New Issue
Block a user