Skip to content

Commit eaed006

Browse files
kubafloCopilot
andcommitted
Teach the authoring prompt the Android API floor CA1416 cannot enforce
PR 443's reviewer found a device test reading the API 28 TextView.AccessibilityHeading in a project whose android floor is 21.0. The test compiles and ships: Controls.DeviceTests.csproj carries <NoWarn>$(NoWarn),CA1416</NoWarn>, so the .NET platform-compatibility analyzer that exists to catch exactly this is switched off. The repository sets dotnet_diagnostic.CA1416.severity = error globally and then suppresses it in all four device-test projects, so every Android device test this pipeline authors is unprotected, not just the one that happened to trip it. A detector was measured and refused. Keyed on bare member name against the real Mono.Android metadata it fires on 8 of 8 files, matching Controls (the MAUI namespace), True and NotEqual (xUnit asserts), Value and Handler. Excluding every name the BCL also declares still leaves 7 of 8, because Android overloads names across types at different levels - AccessibilityHeading alone is declared on hundreds of types, and PerformClick, Draw and GetChildAt are ancient on the type actually called while an obscure sibling carries a high annotation. Deciding which type a .Member read targets needs a semantic model, which is precisely CA1416. A regex here would be a refusal machine aimed at correct tests. So the rule is stated instead, per the established rule that every guard rule must be stated in the authoring prompt or the author can only find it by being refused - and here there is no guard at all, so the author cannot even be refused. The guidance asserts three facts about files it does not own, so a second test reads those files rather than the prose: the android floor, the CA1416 suppression, and the guard this repository already uses at HandlerTestBasementOfT.Android.cs, which version-guards that exact getter and records that there is no ViewCompat.GetAccessibilityHeading. Mutation tested: deleting the rule fails the first test; raising the real csproj floor to 28.0 fails the second. 2 killed, 0 survivors. Suite 961 passed, 0 failed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 735ac9a2-7bec-4baa-ad19-c298e5bc795a
1 parent 7eb6a0b commit eaed006

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

.github/scripts/Replicate-Issue.Tests.ps1

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6853,6 +6853,41 @@ Describe 'the test prompt names the compile traps runs actually hit' {
68536853
Should -BeNullOrEmpty
68546854
}
68556855

6856+
It 'states the Android API floor rule, which no analyzer is left to enforce' {
6857+
# PR 443 read the API 28 TextView.AccessibilityHeading in a device test
6858+
# whose project declares an android floor of 21.0 and suppresses CA1416.
6859+
# The analyzer that exists to catch exactly this is switched off, so the
6860+
# author gets no diagnostic and no refusal - the test simply ships and
6861+
# misreports on API 21 to 27. A bare-name detector cannot replace the
6862+
# analyzer: AccessibilityHeading is declared on hundreds of Android
6863+
# types, so resolving which one a `.Member` read targets needs a
6864+
# semantic model. The rule therefore has to be stated, or it cannot be
6865+
# learned at all.
6866+
$script:Source | Should -Match 'OperatingSystem\.IsAndroidVersionAtLeast\(28\)'
6867+
$script:Source | Should -Match 'CA1416'
6868+
$script:Source | Should -Match 'AccessibilityHeading'
6869+
}
6870+
6871+
It 'still describes the Android floor and the disabled analyzer accurately' {
6872+
# The rule above asserts three facts about files it does not own: the
6873+
# device test project's android floor, its CA1416 suppression, and the
6874+
# guard this repository already uses. Raise the floor, drop the NoWarn
6875+
# or move the helper and the guidance becomes a lie nobody is watching.
6876+
# Read the files rather than trusting the prose.
6877+
$repoRoot = Split-Path (Split-Path (Split-Path $script:ScriptPath))
6878+
6879+
$deviceTests = Get-Content -Raw -LiteralPath (Join-Path $repoRoot 'src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj')
6880+
$deviceTests | Should -Match '== ''android''">21\.0<'
6881+
$deviceTests | Should -Match '<NoWarn>[^<]*CA1416'
6882+
6883+
$guardPath = Join-Path $repoRoot 'src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.Android.cs'
6884+
$guardPath | Should -Exist
6885+
$guard = Get-Content -Raw -LiteralPath $guardPath
6886+
6887+
$guard | Should -Match 'OperatingSystem\.IsAndroidVersionAtLeast\(28\)'
6888+
$guard | Should -Match 'AccessibilityHeading'
6889+
}
6890+
68566891
It 'names the assertion that can actually carry the declared signature' {
68576892
# Build 15069705 lost attempts 3 and 4 to 'Assert.True() Failure /
68586893
# Expected: True' and 'Assert.Equal() Failure: Values differ', neither

.github/scripts/Replicate-Issue.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6227,6 +6227,7 @@ Create exactly the new test files listed in test-proposal.json. Do not create an
62276227
The generated test must run normally and fail without an environment variable, command-line switch, category override, or other opt-in gate. Do not reference MAUI_REPRODUCTION_ISSUE.
62286228
This repository builds with warnings as errors, so warning-level diagnostics still break the build. Do not declare a member whose name hides an inherited MAUI member such as Page.Title, Element.Parent, VisualElement.Window, or View.Handler; give the field a distinct name instead of using `new`. Do not leave an unused field, variable, or using directive.
62296229
The nullable setting that governs your file is not written in your file, nor in the project beside it, so do not try to infer it from either. A device test under src/Controls/tests/DeviceTests is compiled by Controls.DeviceTests.csproj, which never enables nullable annotations - Core.DeviceTests.Shared.csproj keeps the property commented out - so a `?` annotation on a reference type there is CS8632 and breaks the build. A UI test under TestCases.Shared.Tests is different: the body inside your #if ANDROID, #if IOS, #if MACCATALYST or #if WINDOWS block is compiled by the matching platform runner, and Controls.TestCases.Android.Tests.csproj and its iOS, Mac and WinUI siblings all set <Nullable>enable</Nullable> and glob those shared files, so there the compiler tracks nullability and refuses an unguarded dereference or argument with CS8602, CS8604 or CS8600. These four codes are 107 of the compiler errors measured across this pipeline's runs. Write no `?` annotation on a reference type in either place, and in a UI test guard every lookup that can return null with an explicit null check or Assert.NotNull before using its result.
6230+
The Android API level that governs a device test is likewise not written in your file. Controls.DeviceTests.csproj sets <SupportedOSPlatformVersion> for android to 21.0 and suppresses CA1416 with <NoWarn>$(NoWarn),CA1416</NoWarn>, so the .NET platform-compatibility analyzer that exists to catch this is switched off and a call to a newer API compiles silently - the build stays green and the defect only appears on an older device. Reading a member introduced after API 21, such as the API 28 TextView.AccessibilityHeading, therefore misreports or throws on API 21 to 27 even when the product is correct. Guard every such member the way this repository already guards that exact getter in src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.Android.cs, with `if (OperatingSystem.IsAndroidVersionAtLeast(28))` and a fallback for older levels. Note that ViewCompat offers SetAccessibilityHeading but no ViewCompat.GetAccessibilityHeading, so a read has no compat shim and must be version-guarded or avoided outright.
62306231
On Windows a MAUI Controls type and its WinUI counterpart often share a name, so an unqualified reference fails with CS0104: run 15014604 lost four of its five attempts to "'Window' is an ambiguous reference between 'Microsoft.Maui.Controls.Window' and 'Microsoft.UI.Xaml.Window'". This repository resolves that with a W-prefixed alias in 329 places, such as "using WWindow = Microsoft.UI.Xaml.Window;" or "using WBrush = Microsoft.UI.Xaml.Media.Brush;". Add that alias for the WinUI type you need instead of a bare "using Microsoft.UI.Xaml;".
62316232
Call only members you have confirmed in this checked-out repository. Runs failed on CS1061 for invented APIs such as IMauiHandlersCollection.AddMauiControlsHandlers, and on CS0122 for reaching into a private HostApp member; read the declaration first, and expose what you need through the public API the report itself uses rather than guessing a name that reads plausibly.
62326233
Do not use snapshots/baselines, delays, process execution, network access, external data, or a hard-coded failure unrelated to the reported behavior.

0 commit comments

Comments
 (0)