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