Skip to content

Commit e774d7a

Browse files
committed
File-based programs live directive diagnostics
1 parent d17489e commit e774d7a

18 files changed

+1690
-0
lines changed

src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
<Compile Include="$(MSBuildThisFileDirectory)ConvertSwitchStatementToExpression\ConvertSwitchStatementToExpressionDiagnosticAnalyzer.Analyzer.cs" />
5555
<Compile Include="$(MSBuildThisFileDirectory)ConvertSwitchStatementToExpression\ConvertSwitchStatementToExpressionDiagnosticAnalyzer.cs" />
5656
<Compile Include="$(MSBuildThisFileDirectory)ConvertSwitchStatementToExpression\ConvertSwitchStatementToExpressionHelpers.cs" />
57+
<Compile Include="$(MSBuildThisFileDirectory)FileBasedPrograms\AppDirectiveDiagnosticAnalyzer.cs" />
58+
<Compile Include="$(MSBuildThisFileDirectory)FileBasedPrograms\AppDirectiveHelpers.cs" />
5759
<Compile Include="$(MSBuildThisFileDirectory)FileHeaders\CSharpFileHeaderDiagnosticAnalyzer.cs" />
5860
<Compile Include="$(MSBuildThisFileDirectory)FileHeaders\CSharpFileHeaderHelper.cs" />
5961
<Compile Include="$(MSBuildThisFileDirectory)InlineDeclaration\CSharpInlineDeclarationDiagnosticAnalyzer.cs" />

src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,4 +440,59 @@
440440
<data name="Property_accessor_can_be_simplified" xml:space="preserve">
441441
<value>Property accessor can be simplified</value>
442442
</data>
443+
444+
<!-- TODO2: should this live here? Or sdk source package gives us some other resource file? -->
445+
446+
<!-- CliStrings -->
447+
<data name="CouldNotFindAnyProjectInDirectory" xml:space="preserve">
448+
<value>Could not find any project in `{0}`.</value>
449+
</data>
450+
<data name="CouldNotFindProjectOrDirectory" xml:space="preserve">
451+
<value>Could not find project or directory `{0}`.</value>
452+
</data>
453+
<data name="MoreThanOneProjectInDirectory" xml:space="preserve">
454+
<value>Found more than one project in `{0}`. Specify which one to use.</value>
455+
</data>
456+
457+
<!-- CliCommandStrings -->
458+
<data name="PropertyDirectiveInvalidName" xml:space="preserve">
459+
<value>Invalid property name: {0}</value>
460+
<comment>{0} is an inner exception message.</comment>
461+
</data>
462+
<data name="PropertyDirectiveMissingParts" xml:space="preserve">
463+
<value>The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue'.</value>
464+
<comment>{Locked="#:property"}</comment>
465+
</data>
466+
<data name="StaticGraphRestoreNotSupported" xml:space="preserve">
467+
<value>Static graph restore is not supported for file-based apps. Remove the '#:property'.</value>
468+
<comment>{Locked="#:property"}</comment>
469+
</data>
470+
<data name="DirectiveError" xml:space="preserve">
471+
<value>error</value>
472+
<comment>Used when reporting directive errors like "file(location): error: message".</comment>
473+
</data>
474+
<data name="InvalidDirectiveName" xml:space="preserve">
475+
<value>The directive should contain a name without special characters and an optional value separated by '{1}' like '#:{0} Name{1}Value'.</value>
476+
<comment>{0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='.</comment>
477+
</data>
478+
<data name="CannotConvertDirective" xml:space="preserve">
479+
<value>Some directives cannot be converted. Run the file to see all compilation errors. Specify '--force' to convert anyway.</value>
480+
<comment>{Locked="--force"}</comment>
481+
</data>
482+
<data name="DuplicateDirective" xml:space="preserve">
483+
<value>Duplicate directives are not supported: {0}</value>
484+
<comment>{0} is the directive type and name.</comment>
485+
</data>
486+
<data name="InvalidProjectDirective" xml:space="preserve">
487+
<value>The '#:project' directive is invalid: {0}</value>
488+
<comment>{0} is the inner error message.</comment>
489+
</data>
490+
<data name="MissingDirectiveName" xml:space="preserve">
491+
<value>Missing name of '{0}'.</value>
492+
<comment>{0} is the directive name like 'package' or 'sdk'.</comment>
493+
</data>
494+
<data name="UnrecognizedDirective" xml:space="preserve">
495+
<value>Unrecognized directive '{0}'.</value>
496+
<comment>{0} is the directive name like 'package' or 'sdk'.</comment>
497+
</data>
443498
</root>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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 System.Collections.Immutable;
6+
using Microsoft.CodeAnalysis.Diagnostics;
7+
8+
namespace Microsoft.CodeAnalysis.FileBasedPrograms;
9+
10+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
11+
internal sealed class AppDirectiveDiagnosticAnalyzer : DiagnosticAnalyzer
12+
{
13+
public const string DiagnosticId = "FileBasedPrograms";
14+
15+
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
16+
DiagnosticId,
17+
title: DiagnosticId,
18+
// TODO: we probably want to have a different diagnostic for each kind that the SDK package can produce
19+
messageFormat: "{0}",
20+
category: "Usage",
21+
defaultSeverity: DiagnosticSeverity.Error,
22+
isEnabledByDefault: true);
23+
24+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
25+
26+
public override void Initialize(AnalysisContext context)
27+
{
28+
context.EnableConcurrentExecution();
29+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
30+
31+
context.RegisterCompilationStartAction(context =>
32+
{
33+
context.RegisterSyntaxTreeAction(visitSyntaxTree);
34+
});
35+
36+
void visitSyntaxTree(SyntaxTreeAnalysisContext context)
37+
{
38+
var tree = context.Tree;
39+
if (!tree.Options.Features.ContainsKey("FileBasedProgram"))
40+
return;
41+
42+
var root = tree.GetRoot(context.CancellationToken);
43+
if (!root.ContainsDirectives)
44+
return;
45+
46+
// App directives are only valid when they appear before the first C# token
47+
var rootLeadingTrivia = root.GetLeadingTrivia();
48+
#pragma warning disable IDE0002 // Name can be simplified
49+
var diagnosticBag = FileBasedPrograms.DiagnosticBag.Collect(out var diagnosticsBuilder);
50+
#pragma warning restore IDE0002
51+
AppDirectiveHelpers.FindDirectives(
52+
new SourceFile(tree.FilePath, tree.GetText(context.CancellationToken)),
53+
root.GetLeadingTrivia(),
54+
diagnosticBag,
55+
ImmutableArray.CreateBuilder<CSharpDirective>());
56+
57+
foreach (var diag in diagnosticsBuilder)
58+
{
59+
context.ReportDiagnostic(createDiagnostic(tree, diag));
60+
}
61+
62+
// The compiler already reports an error on all the directives past the first token in the file.
63+
// Console.WriteLine("Hello World!");
64+
// #:property foo=bar // error CS9297: '#:' directives cannot be after first token in file
65+
}
66+
67+
// TODO: should SimpleDiagnostics have IDs? message args? TextSpan?
68+
// It feels unreasonable for users to suppress these.
69+
// When these are present, the user cannot build/run, period.
70+
Diagnostic createDiagnostic(SyntaxTree syntaxTree, SimpleDiagnostic simpleDiagnostic)
71+
{
72+
return Diagnostic.Create(
73+
Rule,
74+
location: Location.Create(syntaxTree, simpleDiagnostic.Location.TextSpan),
75+
simpleDiagnostic.Message);
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)