Add analyzer section in project.assets.json file#7464
Conversation
jebriede
left a comment
There was a problem hiding this comment.
Approving with a few suggestions.
| private static bool IsAnalyzerAssetPath(string path) | ||
| { | ||
| return path.StartsWith("analyzers/", StringComparison.Ordinal) | ||
| && path.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) |
There was a problem hiding this comment.
The "analyzers/" prefix check is StringComparison.Ordinal (case-sensitive) while the .dll / .resources.dll checks are OrdinalIgnoreCase. If a package authored the folder as Analyzers/ this would skip it while the SDK's discovery (which we're mirroring) may still pick it up. Was case-sensitivity on the folder segment intentional? If not, consider OrdinalIgnoreCase for consistency with the extension checks.
There was a problem hiding this comment.
Yes this was intentional, we are mirroring sdk analysis discovery from https://github.com/dotnet/sdk/blob/8283adc1579c8bc2afb4458c81cbda0c6eac4466/src/Common/NuGetUtils.NuGet.cs#L43-L53.
jebriede
left a comment
There was a problem hiding this comment.
Approving with an optimization suggestion.
|
|
||
| telemetry.TelemetryEvent[UpdatedAssetsFile] = restoreResult._isAssetsFileDirty.Value; | ||
| telemetry.TelemetryEvent[UpdatedMSBuildFiles] = restoreResult._dirtyMSBuildFiles.Value.Count > 0; | ||
| telemetry.TelemetryEvent[AnalyzerAssetsCount] = CountAnalyzerAssets(assetsFile); |
There was a problem hiding this comment.
CountAnalyzerAssets runs on every real restore and walks every target and every library to populate AnalyzerAssetsCount. When the feature is off (which is the default and the common case during rollout) the count is always 0, so we pay for the full nested walk to produce a constant. Is it worth short-circuiting when the opt-in is disabled? Something like:
| telemetry.TelemetryEvent[AnalyzerAssetsCount] = CountAnalyzerAssets(assetsFile); | |
| telemetry.TelemetryEvent[AnalyzerAssetsCount] = | |
| (_request.Project.RestoreMetadata?.RestoreEnableAnalyzerAssets ?? false) | |
| ? CountAnalyzerAssets(assetsFile) | |
| : 0; |
The inner walk is cheap per library (frozen libraries return Array.Empty), so this is a minor optimization rather than a correctness concern.
There was a problem hiding this comment.
I'm going to make some changes here (like you said it always return 0) for the Telemetry, but we always want this since it will help us decide when to make this new behavior as default.
nkolev92
left a comment
There was a problem hiding this comment.
I think we're introducing a new implementation pattern in a couple of different places, but we already have an established one.
| <Output TaskParameter="AbsolutePaths" PropertyName="RestoreOutputAbsolutePath" /> | ||
| </ConvertToAbsolutePath> | ||
|
|
||
| <ItemGroup Condition="'$(TargetFrameworks)' != ''"> |
There was a problem hiding this comment.
Should we instead follow the logic for audit and pruning where the decision is made in code.
This implementation is only really a factor in standard CLI restore.
The other 3 restores share an implementation, this doesn't.
| return lockFileItems; | ||
| } | ||
|
|
||
| private static bool IsAnalyzerAssetPath(string path) |
There was a problem hiding this comment.
I think we should be using ManagedCodeConventions here.
That's what knows how to find the patterns.
| /// the group has items. Empty groups are left alone. | ||
| /// </summary> | ||
| private static void ClearIfExists<T>(IList<T> group, Func<string, T> factory) where T : LockFileItem | ||
| private static void ClearIfExists<T>(IList<T> group, Func<string, T> factory, bool copyProperties = true) where T : LockFileItem |
There was a problem hiding this comment.
Can you explain what are the properties we're stopping from copying in the analyzers scenario?
| int segmentLength = separator - segmentStart; | ||
| ReadOnlySpan<char> segment = path.AsSpan(segmentStart, segmentLength); | ||
|
|
||
| if (segment.Equals("cs".AsSpan(), StringComparison.OrdinalIgnoreCase)) |
There was a problem hiding this comment.
The hard coded strings still cause allocations. Is there any way to avoid them?
Bug
Progress: NuGet/Home#6279
SDK PR (draft): dotnet/sdk#54646
Description
Today
project.assets.jsondoes not record which analyzers a package contributes, and analyzers are applied regardless ofPrivateAssets/ExcludeAssets/IncludeAssets. This PR implements the NuGet restore half of the "indicate analyzer assets in project.assets.json" feature (spec: NuGet/Home#14455). The SDK consumption half ships as a separate dotnet/sdk PR.When a project opts in with
<RestoreEnableAnalyzerAssets>true</RestoreEnableAnalyzerAssets>, restore now:analyzersgroup under each package in the assets file, listing every analyzer assembly — any.dllunderanalyzers/at any depth, excluding satellite.resources.dll— mirroring the SDK's analyzer discovery so the layout isn't assumed to be fixed.PrivateAssets,ExcludeAssets, andIncludeAssetsnow filter analyzers like any other asset type. Analyzers excluded or transitively suppressed are written as a bare_._placeholder.codeLanguage(cs/vb/fs/any) and, when present in the path,compilerApiVersion(roslynX.Y) — mirroring how content files carrycodeLanguage— so the SDK selects applicable analyzers from metadata instead of parsing paths.Gating. The feature is opt-in and only honored for projects targeting .NET 11 or greater; on earlier frameworks the opt-in is forced off in
NuGet.targets(mirroringNuGetAuditModegating). For multi-targeted projects the effective value is OR'd across frameworks. A disabled, commented scaffold documents the future default-on rollout step.Telemetry.
ProjectRestoreInformationnow reportsAnalyzerAssets.Enabled(opt-in state, emitted on every restore including no-ops) andAnalyzerAssets.Count(number of analyzer assets emitted, excluding_._placeholders), so adoption and impact can be tracked through the rollout.Public API & serialization. Adds
ProjectRestoreMetadata.RestoreEnableAnalyzerAssets,LockFileTargetLibrary.AnalyzerAssets, andLockFileItem.CompilerApiVersionProperty. Both the Newtonsoft.Json and System.Text.Json read/write paths round-trip the new group, metadata, and restore property. The lock-file cache key includes the flag so toggling it invalidates correctly.PR Checklist