File: Parsing\IgnoredDirectiveParsingTests.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Syntax\Microsoft.CodeAnalysis.CSharp.Syntax.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Syntax.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
using Xunit.Abstractions;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests;
 
public sealed class IgnoredDirectiveParsingTests(ITestOutputHelper output) : ParsingTests(output)
{
    private const string FeatureName = "FileBasedProgram";
 
    [Theory, CombinatorialData]
    public void FeatureFlag(bool script)
    {
        var options = script ? TestOptions.Script : TestOptions.Regular;
 
        var source = """
            #!xyz
            #:name value
            """;
 
        VerifyTrivia();
        UsingTree(source, options,
            // (2,2): error CS9282: '#:' directives can be only used in file-based programs ('-features:FileBasedProgram')
            // #:name value
            Diagnostic(ErrorCode.ERR_PPIgnoredNeedsFileBasedProgram, ":").WithLocation(2, 2));
 
        N(SyntaxKind.CompilationUnit);
        {
            N(SyntaxKind.EndOfFileToken);
            {
                L(SyntaxKind.ShebangDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.ExclamationToken);
                    N(SyntaxKind.EndOfDirectiveToken);
                    {
                        L(SyntaxKind.PreprocessingMessageTrivia, "xyz");
                        T(SyntaxKind.EndOfLineTrivia, "\n");
                    }
                }
                L(SyntaxKind.IgnoredDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.ColonToken);
                    N(SyntaxKind.StringLiteralToken, "name value");
                    N(SyntaxKind.EndOfDirectiveToken);
                }
            }
        }
        EOF();
 
        UsingTree(source, options.WithFeature(FeatureName));
 
        N(SyntaxKind.CompilationUnit);
        {
            N(SyntaxKind.EndOfFileToken);
            {
                L(SyntaxKind.ShebangDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.ExclamationToken);
                    N(SyntaxKind.EndOfDirectiveToken);
                    {
                        L(SyntaxKind.PreprocessingMessageTrivia, "xyz");
                        T(SyntaxKind.EndOfLineTrivia, "\n");
                    }
                }
                L(SyntaxKind.IgnoredDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.ColonToken);
                    N(SyntaxKind.StringLiteralToken, "name value");
                    N(SyntaxKind.EndOfDirectiveToken);
                }
            }
        }
        EOF();
    }
 
    [Fact]
    public void Semantics()
    {
        var source = """
            #!xyz
            #:name value
            System.Console.WriteLine(123);
            """;
        CompileAndVerify(source,
            parseOptions: TestOptions.Regular.WithFeature(FeatureName),
            expectedOutput: "123").VerifyDiagnostics();
    }
 
    [Fact]
    public void Api()
    {
        var source = """
            #:abc
            """;
        var root = SyntaxFactory.ParseCompilationUnit(source, options: TestOptions.Regular.WithFeature(FeatureName));
        var trivia = root.EndOfFileToken.GetLeadingTrivia().Single();
        Assert.Equal(SyntaxKind.IgnoredDirectiveTrivia, trivia.Kind());
        Assert.True(SyntaxFacts.IsPreprocessorDirective(trivia.Kind()));
        Assert.True(SyntaxFacts.IsTrivia(trivia.Kind()));
        var structure = (IgnoredDirectiveTriviaSyntax)trivia.GetStructure()!;
        Assert.Equal(":", structure.DirectiveNameToken.ToFullString());
        Assert.Empty(structure.EndOfDirectiveToken.GetLeadingTrivia());
        var content = structure.Content;
        Assert.Equal(SyntaxKind.StringLiteralToken, content.Kind());
        Assert.Equal("abc", content.ToString());
        trivia.GetDiagnostics().Verify();
    }
 
    [Fact]
    public void Api_Diagnostics()
    {
        var source = """
            #if X
            #endif
            #:abc
            """;
        var root = SyntaxFactory.ParseCompilationUnit(source, options: TestOptions.Regular.WithFeature(FeatureName));
        var trivia = root.EndOfFileToken.GetLeadingTrivia().Last();
        Assert.Equal(SyntaxKind.IgnoredDirectiveTrivia, trivia.Kind());
        Assert.True(SyntaxFacts.IsPreprocessorDirective(trivia.Kind()));
        Assert.True(SyntaxFacts.IsTrivia(trivia.Kind()));
        var structure = (IgnoredDirectiveTriviaSyntax)trivia.GetStructure()!;
        Assert.Equal(":", structure.DirectiveNameToken.ToFullString());
        Assert.Empty(structure.EndOfDirectiveToken.GetLeadingTrivia());
        var content = structure.Content;
        Assert.Equal(SyntaxKind.StringLiteralToken, content.Kind());
        Assert.Equal("abc", content.ToString());
        trivia.GetDiagnostics().Verify(
            // (3,2): error CS9283: '#:' directives cannot be after '#if' directive
            // #:abc
            Diagnostic(ErrorCode.ERR_PPIgnoredFollowsIf, ":").WithLocation(3, 2));
    }
 
    [Fact]
    public void Api_Shebang()
    {
        var root = SyntaxFactory.ParseCompilationUnit("#!abc", options: TestOptions.Regular.WithFeature(FeatureName));
        var trivia = root.EndOfFileToken.GetLeadingTrivia().Last();
        Assert.Equal(SyntaxKind.ShebangDirectiveTrivia, trivia.Kind());
        var structure = (ShebangDirectiveTriviaSyntax)trivia.GetStructure()!;
        var xyz = SyntaxFactory.Token(default, SyntaxKind.StringLiteralToken, "xyz", "xyz", default);
        var ijk = SyntaxFactory.Token(default, SyntaxKind.StringLiteralToken, "ijk", "ijk", default);
        AssertEx.Equal("#!xyz", structure.WithContent(xyz).ToFullString());
        AssertEx.Equal("#!ijk", structure.WithContent(xyz).WithContent(ijk).ToFullString());
    }
 
    [Theory, CombinatorialData]
    public void ShebangNotFirst(bool script, bool featureFlag)
    {
        var options = script ? TestOptions.Script : TestOptions.Regular;
 
        if (featureFlag)
        {
            options = options.WithFeature(FeatureName);
        }
 
        var source = """
             #!xyz
            """;
 
        VerifyTrivia();
        UsingTree(source, options,
            // (1,2): error CS1024: Preprocessor directive expected
            //  #!xyz
            Diagnostic(ErrorCode.ERR_PPDirectiveExpected, "#").WithLocation(1, 2));
 
        N(SyntaxKind.CompilationUnit);
        {
            N(SyntaxKind.EndOfFileToken);
            {
                L(SyntaxKind.WhitespaceTrivia, " ");
                L(SyntaxKind.BadDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    M(SyntaxKind.IdentifierToken);
                    N(SyntaxKind.EndOfDirectiveToken);
                    {
                        L(SyntaxKind.SkippedTokensTrivia);
                        {
                            N(SyntaxKind.ExclamationToken);
                            N(SyntaxKind.IdentifierToken, "xyz");
                        }
                    }
                }
            }
        }
        EOF();
    }
 
    [Fact]
    public void AfterToken()
    {
        var source = """
            #:x
            M();
            #:y
            """;
 
        VerifyTrivia();
        UsingTree(source, TestOptions.Regular.WithFeature(FeatureName),
            // (3,2): error CS9281: '#:' directives cannot be after first token in file
            // #:y
            Diagnostic(ErrorCode.ERR_PPIgnoredFollowsToken, ":").WithLocation(3, 2));
 
        N(SyntaxKind.CompilationUnit);
        {
            N(SyntaxKind.GlobalStatement);
            {
                N(SyntaxKind.ExpressionStatement);
                {
                    N(SyntaxKind.InvocationExpression);
                    {
                        N(SyntaxKind.IdentifierName);
                        {
                            N(SyntaxKind.IdentifierToken, "M");
                            {
                                L(SyntaxKind.IgnoredDirectiveTrivia);
                                {
                                    N(SyntaxKind.HashToken);
                                    N(SyntaxKind.ColonToken);
                                    N(SyntaxKind.StringLiteralToken, "x");
                                    N(SyntaxKind.EndOfDirectiveToken);
                                    {
                                        T(SyntaxKind.EndOfLineTrivia, "\n");
                                    }
                                }
                            }
                        }
                        N(SyntaxKind.ArgumentList);
                        {
                            N(SyntaxKind.OpenParenToken);
                            N(SyntaxKind.CloseParenToken);
                        }
                    }
                    N(SyntaxKind.SemicolonToken);
                    {
                        T(SyntaxKind.EndOfLineTrivia, "\n");
                    }
                }
            }
            N(SyntaxKind.EndOfFileToken);
            {
                L(SyntaxKind.IgnoredDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.ColonToken);
                    N(SyntaxKind.StringLiteralToken, "y");
                    N(SyntaxKind.EndOfDirectiveToken);
                }
            }
        }
        EOF();
    }
 
    [Fact]
    public void AfterIf()
    {
        var source = """
            #:x
            #if X
            #:y
            #endif
            #:z
            """;
 
        VerifyTrivia();
        UsingTree(source, TestOptions.Regular.WithFeature(FeatureName),
            // (3,2): error CS9283: '#:' directives cannot be after '#if' directive
            // #:y
            Diagnostic(ErrorCode.ERR_PPIgnoredFollowsIf, ":").WithLocation(3, 2),
            // (5,2): error CS9283: '#:' directives cannot be after '#if' directive
            // #:z
            Diagnostic(ErrorCode.ERR_PPIgnoredFollowsIf, ":").WithLocation(5, 2));
 
        N(SyntaxKind.CompilationUnit);
        {
            N(SyntaxKind.EndOfFileToken);
            {
                L(SyntaxKind.IgnoredDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.ColonToken);
                    N(SyntaxKind.StringLiteralToken, "x");
                    N(SyntaxKind.EndOfDirectiveToken);
                    {
                        T(SyntaxKind.EndOfLineTrivia, "\n");
                    }
                }
                L(SyntaxKind.IfDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.IfKeyword);
                    {
                        T(SyntaxKind.WhitespaceTrivia, " ");
                    }
                    N(SyntaxKind.IdentifierName);
                    {
                        N(SyntaxKind.IdentifierToken, "X");
                    }
                    N(SyntaxKind.EndOfDirectiveToken);
                    {
                        T(SyntaxKind.EndOfLineTrivia, "\n");
                    }
                }
                L(SyntaxKind.IgnoredDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.ColonToken);
                    N(SyntaxKind.StringLiteralToken, "y");
                    N(SyntaxKind.EndOfDirectiveToken);
                    {
                        T(SyntaxKind.EndOfLineTrivia, "\n");
                    }
                }
                L(SyntaxKind.EndIfDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.EndIfKeyword);
                    N(SyntaxKind.EndOfDirectiveToken);
                    {
                        T(SyntaxKind.EndOfLineTrivia, "\n");
                    }
                }
                L(SyntaxKind.IgnoredDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.ColonToken);
                    N(SyntaxKind.StringLiteralToken, "z");
                    N(SyntaxKind.EndOfDirectiveToken);
                }
            }
        }
        EOF();
    }
 
    [Fact]
    public void AfterComment()
    {
        var source = """
            #:x
            // comment
            #:y
            """;
 
        VerifyTrivia();
        UsingTree(source, TestOptions.Regular.WithFeature(FeatureName));
 
        N(SyntaxKind.CompilationUnit);
        {
            N(SyntaxKind.EndOfFileToken);
            {
                L(SyntaxKind.IgnoredDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.ColonToken);
                    N(SyntaxKind.StringLiteralToken, "x");
                    N(SyntaxKind.EndOfDirectiveToken);
                    {
                        T(SyntaxKind.EndOfLineTrivia, "\n");
                    }
                }
                L(SyntaxKind.SingleLineCommentTrivia);
                L(SyntaxKind.EndOfLineTrivia, "\n");
                L(SyntaxKind.IgnoredDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.ColonToken);
                    N(SyntaxKind.StringLiteralToken, "y");
                    N(SyntaxKind.EndOfDirectiveToken);
                }
            }
        }
        EOF();
    }
 
    [Fact]
    public void AfterDefine()
    {
        var source = """
            #:x
            #define y
            #:y
            """;
 
        VerifyTrivia();
        UsingTree(source, TestOptions.Regular.WithFeature(FeatureName));
 
        N(SyntaxKind.CompilationUnit);
        {
            N(SyntaxKind.EndOfFileToken);
            {
                L(SyntaxKind.IgnoredDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.ColonToken);
                    N(SyntaxKind.StringLiteralToken, "x");
                    N(SyntaxKind.EndOfDirectiveToken);
                    {
                        T(SyntaxKind.EndOfLineTrivia, "\n");
                    }
                }
                L(SyntaxKind.DefineDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.DefineKeyword);
                    {
                        T(SyntaxKind.WhitespaceTrivia, " ");
                    }
                    N(SyntaxKind.IdentifierToken, "y");
                    N(SyntaxKind.EndOfDirectiveToken);
                    {
                        T(SyntaxKind.EndOfLineTrivia, "\n");
                    }
                }
                L(SyntaxKind.IgnoredDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.ColonToken);
                    N(SyntaxKind.StringLiteralToken, "y");
                    N(SyntaxKind.EndOfDirectiveToken);
                }
            }
        }
        EOF();
    }
 
    [Fact]
    public void SpaceBeforeHash()
    {
        var source = """
             #:x
            """;
 
        VerifyTrivia();
        UsingTree(source, TestOptions.Regular.WithFeature(FeatureName));
 
        N(SyntaxKind.CompilationUnit);
        {
            N(SyntaxKind.EndOfFileToken);
            {
                L(SyntaxKind.WhitespaceTrivia, " ");
                L(SyntaxKind.IgnoredDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.ColonToken);
                    N(SyntaxKind.StringLiteralToken, "x");
                    N(SyntaxKind.EndOfDirectiveToken);
                }
            }
        }
        EOF();
    }
 
    [Fact]
    public void SpacesAfterHash()
    {
        var source = """
             # : x
            """;
 
        VerifyTrivia();
        UsingTree(source, TestOptions.Regular.WithFeature(FeatureName),
            // (1,2): error CS1024: Preprocessor directive expected
            //  # : x
            Diagnostic(ErrorCode.ERR_PPDirectiveExpected, "#").WithLocation(1, 2));
 
        N(SyntaxKind.CompilationUnit);
        {
            N(SyntaxKind.EndOfFileToken);
            {
                L(SyntaxKind.WhitespaceTrivia, " ");
                L(SyntaxKind.BadDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    {
                        T(SyntaxKind.WhitespaceTrivia, " ");
                    }
                    M(SyntaxKind.IdentifierToken);
                    N(SyntaxKind.EndOfDirectiveToken);
                    {
                        L(SyntaxKind.SkippedTokensTrivia);
                        {
                            N(SyntaxKind.ColonToken);
                            {
                                T(SyntaxKind.WhitespaceTrivia, " ");
                            }
                            N(SyntaxKind.IdentifierToken, "x");
                        }
                    }
                }
            }
        }
        EOF();
    }
 
    [Fact]
    public void NoMessage()
    {
        var source = """
            #:
            """;
 
        VerifyTrivia();
        UsingTree(source, TestOptions.Regular.WithFeature(FeatureName));
 
        N(SyntaxKind.CompilationUnit);
        {
            N(SyntaxKind.EndOfFileToken);
            {
                L(SyntaxKind.IgnoredDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    N(SyntaxKind.ColonToken);
                    N(SyntaxKind.EndOfDirectiveToken);
                }
            }
        }
        EOF();
    }
 
    [Fact]
    public void NoColon()
    {
        var source = """
            #
            """;
 
        VerifyTrivia();
        UsingTree(source, TestOptions.Regular.WithFeature(FeatureName),
            // (1,1): error CS1024: Preprocessor directive expected
            // #
            Diagnostic(ErrorCode.ERR_PPDirectiveExpected, "#").WithLocation(1, 1));
 
        N(SyntaxKind.CompilationUnit);
        {
            N(SyntaxKind.EndOfFileToken);
            {
                L(SyntaxKind.BadDirectiveTrivia);
                {
                    N(SyntaxKind.HashToken);
                    M(SyntaxKind.IdentifierToken);
                    N(SyntaxKind.EndOfDirectiveToken);
                }
            }
        }
        EOF();
    }
}