Skip to content

Commit 9ff08be

Browse files
authored
File-based programs live directive diagnostics (#80575)
1 parent 48c74d9 commit 9ff08be

File tree

11 files changed

+118
-151
lines changed

11 files changed

+118
-151
lines changed

eng/Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@
324324
<PackageVersion Include="vswhere" Version="$(vswhereVersion)" />
325325
<PackageVersion Include="CliWrap" Version="3.8.2" />
326326
<PackageVersion Include="Microsoft.DotNet.DarcLib" Version="$(MicrosoftDotNetDarcLibPackageVersion)" />
327+
<PackageVersion Include="Microsoft.DotNet.FileBasedPrograms" Version="$(MicrosoftDotNetFileBasedProgramsPackageVersion)" />
327328
<PackageVersion Include="Spectre.Console" Version="0.51.1" />
328329
<!--
329330
The version of Roslyn we build Source Generators against that are built in this

eng/Version.Details.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ This file should be imported by eng/Versions.props
4545
<MicrosoftDiaSymReaderPackageVersion>2.0.0</MicrosoftDiaSymReaderPackageVersion>
4646
<!-- dotnet/arcade-services dependencies -->
4747
<MicrosoftDotNetDarcLibPackageVersion>1.1.0-beta.25503.1</MicrosoftDotNetDarcLibPackageVersion>
48+
<!-- dotnet/sdk dependencies -->
49+
<MicrosoftDotNetFileBasedProgramsPackageVersion>10.0.200-preview.0.25556.104</MicrosoftDotNetFileBasedProgramsPackageVersion>
4850
<!-- dotnet/roslyn-analyzers dependencies -->
4951
<MicrosoftCodeAnalysisAnalyzersPackageVersion>3.11.0</MicrosoftCodeAnalysisAnalyzersPackageVersion>
5052
<MicrosoftCodeAnalysisAnalyzerUtilitiesPackageVersion>3.3.0</MicrosoftCodeAnalysisAnalyzerUtilitiesPackageVersion>

src/Analyzers/Core/Analyzers/AbstractCodeQualityDiagnosticAnalyzer.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,17 @@ protected static DiagnosticDescriptor CreateDescriptor(
4444
bool isUnnecessary,
4545
bool isEnabledByDefault = true,
4646
bool isConfigurable = true,
47-
LocalizableString? description = null)
47+
LocalizableString? description = null,
48+
DiagnosticSeverity defaultSeverity = DiagnosticSeverity.Info,
49+
string? helpLinkUri = null)
4850
#pragma warning disable RS0030 // Do not use banned APIs
4951
=> new(
5052
id, title, messageFormat,
5153
DiagnosticCategory.CodeQuality,
52-
DiagnosticSeverity.Info,
54+
defaultSeverity,
5355
isEnabledByDefault,
5456
description,
55-
helpLinkUri: DiagnosticHelper.GetHelpLinkForDiagnosticId(id),
57+
helpLinkUri: helpLinkUri ?? DiagnosticHelper.GetHelpLinkForDiagnosticId(id),
5658
customTags: DiagnosticCustomTags.Create(isUnnecessary, isConfigurable, isCustomConfigurable: hasAnyCodeStyleOption, enforceOnBuild));
5759
#pragma warning restore RS0030 // Do not use banned APIs
5860
}

src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ private static void ValidateHelpLinkForDiagnostic(string diagnosticId, string he
8787
return;
8888
}
8989

90+
if (diagnosticId == FileBasedPrograms.FileLevelDirectiveDiagnosticAnalyzer.DiagnosticId)
91+
{
92+
Assert.Equal("https://learn.microsoft.com/dotnet/csharp/language-reference/preprocessor-directives#file-based-apps", helpLinkUri);
93+
return;
94+
}
95+
9096
if (helpLinkUri != $"https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/{diagnosticId.ToLowerInvariant()}")
9197
{
9298
Assert.True(false, $"Invalid help link for {diagnosticId}");
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#if !NET
6+
using Roslyn.Utilities;
7+
8+
namespace Microsoft.DotNet.FileBasedPrograms;
9+
10+
/// <summary>Provides implementations of certain methods to the FileBasedPrograms source package.</summary>
11+
internal partial class ExternalHelpers
12+
{
13+
public static partial int CombineHashCodes(int value1, int value2)
14+
=> Hash.Combine(value1, value2);
15+
16+
public static partial string GetRelativePath(string relativeTo, string path)
17+
=> PathUtilities.GetRelativePath(relativeTo, path);
18+
19+
public static partial bool IsPathFullyQualified(string path)
20+
=> PathUtilities.IsAbsolute(path);
21+
}
22+
#endif
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Microsoft.CodeAnalysis.CodeQuality;
6+
using Microsoft.CodeAnalysis.CodeStyle;
7+
using Microsoft.CodeAnalysis.Diagnostics;
8+
using Microsoft.DotNet.FileBasedPrograms;
9+
10+
namespace Microsoft.CodeAnalysis.FileBasedPrograms;
11+
12+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
13+
internal sealed class FileLevelDirectiveDiagnosticAnalyzer()
14+
: AbstractCodeQualityDiagnosticAnalyzer(
15+
descriptors: [s_descriptor],
16+
generatedCodeAnalysisFlags: GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics)
17+
{
18+
public const string DiagnosticId = "FileBasedPrograms";
19+
20+
private static readonly DiagnosticDescriptor s_descriptor = CreateDescriptor(
21+
id: DiagnosticId,
22+
enforceOnBuild: EnforceOnBuild.Never,
23+
title: DiagnosticId,
24+
messageFormat: "{0}",
25+
hasAnyCodeStyleOption: false,
26+
isUnnecessary: false,
27+
isEnabledByDefault: true,
28+
isConfigurable: false,
29+
defaultSeverity: DiagnosticSeverity.Error,
30+
helpLinkUri: "https://learn.microsoft.com/dotnet/csharp/language-reference/preprocessor-directives#file-based-apps");
31+
32+
public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
33+
=> DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis;
34+
35+
protected override void InitializeWorker(AnalysisContext context)
36+
{
37+
context.RegisterSyntaxTreeAction(context =>
38+
{
39+
var cancellationToken = context.CancellationToken;
40+
var tree = context.Tree;
41+
if (!tree.Options.Features.ContainsKey("FileBasedProgram"))
42+
return;
43+
44+
var root = tree.GetRoot(cancellationToken);
45+
if (!root.ContainsDirectives)
46+
return;
47+
48+
// The compiler already reports an error on all the directives past the first token in the file.
49+
// Therefore, the analyzer only deals with the directives on the first token.
50+
// Console.WriteLine("Hello World!");
51+
// #:property foo=bar // error CS9297: '#:' directives cannot be after first token in file
52+
var diagnosticBag = DiagnosticBag.Collect(out var diagnosticsBuilder);
53+
FileLevelDirectiveHelpers.FindLeadingDirectives(
54+
new SourceFile(tree.FilePath, tree.GetText(cancellationToken)),
55+
root.GetLeadingTrivia(),
56+
diagnosticBag,
57+
builder: null);
58+
59+
foreach (var simpleDiagnostic in diagnosticsBuilder)
60+
{
61+
context.ReportDiagnostic(Diagnostic.Create(
62+
s_descriptor,
63+
location: Location.Create(tree, simpleDiagnostic.Location.TextSpan),
64+
simpleDiagnostic.Message));
65+
}
66+
});
67+
}
68+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[*.cs]
2+
dotnet_diagnostic.RS0051.severity = none # Symbol is not part of the declared API
3+
dotnet_diagnostic.RS0056.severity = none # InternalAPI.txt is missing '#nullable enable', so the nullability annotations of API isn't recorded. It is recommended to enable this tracking.

src/Features/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Features.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,15 @@
6767
</ItemGroup>
6868
<ItemGroup>
6969
<PackageReference Include="Humanizer.Core" PrivateAssets="compile" />
70+
<PackageReference Include="Microsoft.DotNet.FileBasedPrograms" GeneratePathProperty="true" />
71+
<EmbeddedResource
72+
Include="$(PkgMicrosoft_DotNet_FileBasedPrograms)\contentFiles\cs\any\FileBasedProgramsResources.resx"
73+
GenerateSource="true"
74+
Namespace="Microsoft.DotNet.FileBasedPrograms" />
7075
</ItemGroup>
76+
<PropertyGroup>
77+
<DefineConstants>$(DefineConstants);FILE_BASED_PROGRAMS_SOURCE_PACKAGE_GRACEFUL_EXCEPTION</DefineConstants>
78+
</PropertyGroup>
7179
<Import Project="..\..\..\Analyzers\CSharp\Analyzers\CSharpAnalyzers.projitems" Label="Shared" />
7280
<Import Project="..\..\..\Analyzers\CSharp\CodeFixes\CSharpCodeFixes.projitems" Label="Shared" />
7381
<Import Project="..\..\..\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems" Label="Shared" />

src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/FileBasedProgramsProjectSystem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,9 @@ public async ValueTask<bool> TryRemoveMiscellaneousDocumentAsync(DocumentUri uri
193193
};
194194
}
195195

196-
protected override async ValueTask OnProjectUnloadedAsync(string projectFilePath)
196+
protected override ValueTask OnProjectUnloadedAsync(string projectFilePath)
197197
{
198-
await _projectXmlProvider.UnloadCachedDiagnosticsAsync(projectFilePath);
198+
return ValueTask.CompletedTask;
199199
}
200200

201201
protected override async ValueTask TransitionPrimordialProjectToLoadedAsync(

src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/FileBasedPrograms/VirtualProjectXmlDiagnosticSourceProvider.cs

Lines changed: 0 additions & 91 deletions
This file was deleted.

0 commit comments

Comments
 (0)