π§ MSBuild File Quality Report β 2026-06-25
Files reviewed: 97 (70 NuGet build extensions Β· 7 MSTest.Sdk files Β· 20 repo-infrastructure files Β· 3 eng/common/* reviewed but excluded from findings per policy)
Findings: π΄ 0 errors Β· π‘ 2 warnings Β· π΅ 1 suggestion (affecting 14+ files)
π‘ Warnings
src/Adapter/MSTest.TestAdapter/buildTransitive/common/MSTest.TestAdapter.targets
- Rule A-2 β
GetMSTestV2CultureHierarchy is named with the Get prefix convention but uses item-group side effects instead of the Returns attribute; the data flow is implicit rather than declared.
- Lines: 49β82 (target declaration)
- Current:
<Target Name="GetMSTestV2CultureHierarchy">
<ItemGroup>
<CurrentUICultureHierarchy Include="$([System.Globalization.CultureInfo]::CurrentUICulture.Name)" />
<!-- ... 4 more culture levels ... -->
</ItemGroup>
<ItemGroup>
<MSTestV2Files Include="$(MSBuildThisFileDirectory)..\_localization\%(CurrentUICultureHierarchy.Identity)\*.dll">
<UICulture>%(CurrentUICultureHierarchy.Identity)</UICulture>
</MSTestV2Files>
</ItemGroup>
</Target>
- Suggested: Add
Returns="@(MSTestV2Files)" (and expose @(CurrentUICultureHierarchy) too if callers need it), so the output contract is explicit and callers can consume results via CallTarget Output or normal DependsOnTargets:
<Target Name="GetMSTestV2CultureHierarchy" Returns="@(MSTestV2Files)">
- Note: Same issue exists in
src/Adapter/MSTest.TestAdapter/buildTransitive/uwp/MSTest.TestAdapter.targets (line 16).
Directory.Packages.props
- Rule A-5 β
_ValidateBundledSdkFeatureVersions is a <Target> defined inside a .props file. Targets should live in .targets files, not .props files; .props is evaluated early during property/item collection and targets defined there can be confusing to maintain.
- Lines: last ~18 lines of the file
- Current (in
Directory.Packages.props):
<Target Name="_ValidateBundledSdkFeatureVersions"
BeforeTargets="CollectPackageReferences;GenerateNuspec;Build;Pack"
Condition=" '$(MSBuildProjectName)' == 'MSTest.Sdk' ">
<PropertyGroup>
<_AspireHostingTestingPackageVersion>
@(PackageVersion->WithMetadataValue('Identity','Aspire.Hosting.Testing')->'%(Version)')
</_AspireHostingTestingPackageVersion>
<_MicrosoftPlaywrightPackageVersion>
@(PackageVersion->WithMetadataValue('Identity','Microsoft.Playwright.MSTest.v4')->'%(Version)')
</_MicrosoftPlaywrightPackageVersion>
</PropertyGroup>
<Error Condition="..." Text="..." />
<Error Condition="..." Text="..." />
</Target>
- Suggested: Move the target to
Directory.Build.targets (which is already present at the repo root) or to a dedicated eng/Validation.targets that is imported from Directory.Build.targets. The PackageVersion items it references are populated before target execution regardless of which file the target is authored in.
π΅ Suggestions
All MTP extension buildTransitive/*.props files (12 packages)
- Rule E-1 β
buildTransitive/*.props for all MTP extension packages forward directly to buildMultiTargeting/ instead of forwarding through the corresponding build/ file. The standard NuGet convention is buildTransitive/ β build/ β (shared location), which establishes a clear ownership chain and makes it obvious that transitive consumers receive a subset of what direct consumers see.
- Affected files (one example pattern shown; identical in all 12):
src/Platform/Microsoft.Testing.Extensions.*/buildTransitive/Microsoft.Testing.Extensions.*.props
- Same pattern in
src/Platform/Microsoft.Testing.Platform/buildTransitive/Microsoft.Testing.Platform.props
- Current (e.g.
buildTransitive/Microsoft.Testing.Extensions.TrxReport.props):
<Project>
<Import Project="$(MSBuildThisFileDirectory)..\..\buildMultiTargeting\Microsoft.Testing.Extensions.TrxReport.props" />
</Project>
- Suggested β forward to
build/ which in turn already forwards to buildMultiTargeting/:
<Project>
<Import Project="$(MSBuildThisFileDirectory)..\..\build\Microsoft.Testing.Extensions.TrxReport.props" />
</Project>
- Packages affected: AzureDevOpsReport, AzureFoundry, CrashDump, CtrfReport, HangDump, HotReload, HtmlReport, JUnitReport, Retry, Telemetry, TrxReport, Microsoft.Testing.Platform (base).
Files reviewed without findings (83)
NuGet build extensions β clean
src/Adapter/MSTest.TestAdapter/build/net{462,8.0,9.0}/MSTest.TestAdapter.{props,targets} (6 files)
src/Adapter/MSTest.TestAdapter/build/uap10.0/MSTest.TestAdapter.{props,targets} (2 files)
src/Adapter/MSTest.TestAdapter/buildTransitive/Parallelize.targets
src/Adapter/MSTest.TestAdapter/buildTransitive/uwp/MSTest.TestAdapter.props
src/Analyzers/MSTest.Analyzers.Package/buildTransitive/MSTest.Analyzers.{props,targets} (2 files)
src/Platform/Microsoft.Testing.Extensions.{AzureDevOpsReport,AzureFoundry,CrashDump,CtrfReport,HangDump,HotReload,HtmlReport,JUnitReport,Retry,Telemetry,TrxReport}/build/*.props (11 files)
src/Platform/Microsoft.Testing.Extensions.{AzureDevOpsReport,AzureFoundry,CrashDump,CtrfReport,HangDump,HotReload,HtmlReport,JUnitReport,Retry,Telemetry,TrxReport}/buildMultiTargeting/*.props (11 files)
src/Platform/Microsoft.Testing.Platform.Internal.DotnetTest/build/Microsoft.Testing.Platform.Internal.DotnetTest.props
src/Platform/Microsoft.Testing.Platform.MSBuild/build/Microsoft.Testing.Platform.MSBuild.{props,targets} (2 files)
src/Platform/Microsoft.Testing.Platform.MSBuild/buildMultiTargeting/Microsoft.Testing.Platform.MSBuild.{CustomTestTarget,VSTest,props,targets}.targets (4 files)
src/Platform/Microsoft.Testing.Platform.MSBuild/buildTransitive/Microsoft.Testing.Platform.MSBuild.{props,targets} (2 files)
src/Platform/Microsoft.Testing.Platform/build/Microsoft.Testing.Platform.{props,targets} (2 files)
src/Platform/Microsoft.Testing.Platform/buildMultiTargeting/Microsoft.Testing.Platform.{props,targets} (2 files)
src/Platform/Microsoft.Testing.Platform/buildTransitive/Microsoft.Testing.Platform.{props,targets} (2 files)
src/TestFramework/TestFramework.Extensions/build/net{462,8.0,9.0,standard2.0}/MSTest.TestFramework.targets (4 files)
src/TestFramework/TestFramework.Extensions/build/uap10.0/MSTest.TestFramework.targets
src/TestFramework/TestFramework.Extensions/buildTransitive/{net8.0AndLater,others}/MSTest.TestFramework.targets (2 files)
MSTest.Sdk β clean
src/Package/MSTest.Sdk/Sdk/Features/Aspire.targets
src/Package/MSTest.Sdk/Sdk/Features/Playwright.targets
src/Package/MSTest.Sdk/Sdk/Runner/ClassicEngine.targets
src/Package/MSTest.Sdk/Sdk/Runner/Common.targets
src/Package/MSTest.Sdk/Sdk/Runner/NativeAOT.targets
src/Package/MSTest.Sdk/Sdk/Sdk.targets
src/Package/MSTest.Sdk/Sdk/VSTest/VSTest.targets
Repository infrastructure β clean
Directory.Build.props
Directory.Build.targets
eng/AfterSolutionBuild.targets
eng/Analyzers.props
eng/Build.props
eng/Publishing.props
eng/Tools.props
eng/Versions.props
eng/common/internal/Directory.Build.props (eng/common β reviewed, not in scope for findings)
eng/common/native/LocateNativeCompiler.targets (eng/common β reviewed, not in scope for findings)
eng/common/native/NativeAotSupported.props (eng/common β reviewed, not in scope for findings)
samples/public/Directory.Build.{props,targets} (2 files)
samples/public/Directory.Packages.props
src/Directory.Build.props
src/Platform/Directory.Build.props
test/Directory.Build.{props,targets} (2 files)
test/IntegrationTests/TestAssets/Directory.Build.targets
Reference
This review applies the rule catalog defined in
.github/agents/msbuild-reviewer.agent.md.
Generated by MSBuild Quality Review workflow run 28146295060.
π€ Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account β the account owner did not write or approve this content personally. Generated by the MSBuild Quality Review workflow. Β· 1.1K AIC Β· β 13.1 AIC Β· β 41.7K Β· [β·]( Β· β·)
π§ MSBuild File Quality Report β 2026-06-25
Files reviewed: 97 (70 NuGet build extensions Β· 7 MSTest.Sdk files Β· 20 repo-infrastructure files Β· 3
eng/common/*reviewed but excluded from findings per policy)Findings: π΄ 0 errors Β· π‘ 2 warnings Β· π΅ 1 suggestion (affecting 14+ files)
π‘ Warnings
src/Adapter/MSTest.TestAdapter/buildTransitive/common/MSTest.TestAdapter.targetsGetMSTestV2CultureHierarchyis named with theGetprefix convention but uses item-group side effects instead of theReturnsattribute; the data flow is implicit rather than declared.Returns="@(MSTestV2Files)"(and expose@(CurrentUICultureHierarchy)too if callers need it), so the output contract is explicit and callers can consume results viaCallTargetOutputor normalDependsOnTargets:src/Adapter/MSTest.TestAdapter/buildTransitive/uwp/MSTest.TestAdapter.targets(line 16).Directory.Packages.props_ValidateBundledSdkFeatureVersionsis a<Target>defined inside a.propsfile. Targets should live in.targetsfiles, not.propsfiles;.propsis evaluated early during property/item collection and targets defined there can be confusing to maintain.Directory.Packages.props):Directory.Build.targets(which is already present at the repo root) or to a dedicatedeng/Validation.targetsthat is imported fromDirectory.Build.targets. ThePackageVersionitems it references are populated before target execution regardless of which file the target is authored in.π΅ Suggestions
All MTP extension
buildTransitive/*.propsfiles (12 packages)buildTransitive/*.propsfor all MTP extension packages forward directly tobuildMultiTargeting/instead of forwarding through the correspondingbuild/file. The standard NuGet convention isbuildTransitive/ β build/ β (shared location), which establishes a clear ownership chain and makes it obvious that transitive consumers receive a subset of what direct consumers see.src/Platform/Microsoft.Testing.Extensions.*/buildTransitive/Microsoft.Testing.Extensions.*.propssrc/Platform/Microsoft.Testing.Platform/buildTransitive/Microsoft.Testing.Platform.propsbuildTransitive/Microsoft.Testing.Extensions.TrxReport.props):build/which in turn already forwards tobuildMultiTargeting/:Files reviewed without findings (83)
NuGet build extensions β clean
src/Adapter/MSTest.TestAdapter/build/net{462,8.0,9.0}/MSTest.TestAdapter.{props,targets}(6 files)src/Adapter/MSTest.TestAdapter/build/uap10.0/MSTest.TestAdapter.{props,targets}(2 files)src/Adapter/MSTest.TestAdapter/buildTransitive/Parallelize.targetssrc/Adapter/MSTest.TestAdapter/buildTransitive/uwp/MSTest.TestAdapter.propssrc/Analyzers/MSTest.Analyzers.Package/buildTransitive/MSTest.Analyzers.{props,targets}(2 files)src/Platform/Microsoft.Testing.Extensions.{AzureDevOpsReport,AzureFoundry,CrashDump,CtrfReport,HangDump,HotReload,HtmlReport,JUnitReport,Retry,Telemetry,TrxReport}/build/*.props(11 files)src/Platform/Microsoft.Testing.Extensions.{AzureDevOpsReport,AzureFoundry,CrashDump,CtrfReport,HangDump,HotReload,HtmlReport,JUnitReport,Retry,Telemetry,TrxReport}/buildMultiTargeting/*.props(11 files)src/Platform/Microsoft.Testing.Platform.Internal.DotnetTest/build/Microsoft.Testing.Platform.Internal.DotnetTest.propssrc/Platform/Microsoft.Testing.Platform.MSBuild/build/Microsoft.Testing.Platform.MSBuild.{props,targets}(2 files)src/Platform/Microsoft.Testing.Platform.MSBuild/buildMultiTargeting/Microsoft.Testing.Platform.MSBuild.{CustomTestTarget,VSTest,props,targets}.targets(4 files)src/Platform/Microsoft.Testing.Platform.MSBuild/buildTransitive/Microsoft.Testing.Platform.MSBuild.{props,targets}(2 files)src/Platform/Microsoft.Testing.Platform/build/Microsoft.Testing.Platform.{props,targets}(2 files)src/Platform/Microsoft.Testing.Platform/buildMultiTargeting/Microsoft.Testing.Platform.{props,targets}(2 files)src/Platform/Microsoft.Testing.Platform/buildTransitive/Microsoft.Testing.Platform.{props,targets}(2 files)src/TestFramework/TestFramework.Extensions/build/net{462,8.0,9.0,standard2.0}/MSTest.TestFramework.targets(4 files)src/TestFramework/TestFramework.Extensions/build/uap10.0/MSTest.TestFramework.targetssrc/TestFramework/TestFramework.Extensions/buildTransitive/{net8.0AndLater,others}/MSTest.TestFramework.targets(2 files)MSTest.Sdk β clean
src/Package/MSTest.Sdk/Sdk/Features/Aspire.targetssrc/Package/MSTest.Sdk/Sdk/Features/Playwright.targetssrc/Package/MSTest.Sdk/Sdk/Runner/ClassicEngine.targetssrc/Package/MSTest.Sdk/Sdk/Runner/Common.targetssrc/Package/MSTest.Sdk/Sdk/Runner/NativeAOT.targetssrc/Package/MSTest.Sdk/Sdk/Sdk.targetssrc/Package/MSTest.Sdk/Sdk/VSTest/VSTest.targetsRepository infrastructure β clean
Directory.Build.propsDirectory.Build.targetseng/AfterSolutionBuild.targetseng/Analyzers.propseng/Build.propseng/Publishing.propseng/Tools.propseng/Versions.propseng/common/internal/Directory.Build.props(eng/common β reviewed, not in scope for findings)eng/common/native/LocateNativeCompiler.targets(eng/common β reviewed, not in scope for findings)eng/common/native/NativeAotSupported.props(eng/common β reviewed, not in scope for findings)samples/public/Directory.Build.{props,targets}(2 files)samples/public/Directory.Packages.propssrc/Directory.Build.propssrc/Platform/Directory.Build.propstest/Directory.Build.{props,targets}(2 files)test/IntegrationTests/TestAssets/Directory.Build.targetsReference
This review applies the rule catalog defined in
.github/agents/msbuild-reviewer.agent.md.Generated by MSBuild Quality Review workflow run 28146295060.