/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/EditorFeatures/CSharpTest/Completion/CompletionProviders/AppDirectiveCompletionProviderTests.cs
457 строк
17 KB
Rikki Gibson
File-based apps: Add support for `#:ref` directive (#83985)
06 авг 2026, 00:25
Не верифицирован
06 авг 2026, 00:25
7716b3b
Код
Авторство
О чём код?
// 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; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.CSharp.Completion.Providers; using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests; public sealed class SdkAppDirectiveCompletionProviderTests : AbstractAppDirectiveCompletionProviderTests { protected override string DirectiveKind => "sdk"; internal override Type GetCompletionProviderType() => typeof(SdkAppDirectiveCompletionProvider); } public sealed class PropertyAppDirectiveCompletionProviderTests : AbstractAppDirectiveCompletionProviderTests { protected override string DirectiveKind => "property"; internal override Type GetCompletionProviderType() => typeof(PropertyAppDirectiveCompletionProvider); } public sealed class PackageAppDirectiveCompletionProviderTests : AbstractAppDirectiveCompletionProviderTests { protected override string DirectiveKind => "package"; internal override Type GetCompletionProviderType() => typeof(PackageAppDirectiveCompletionProvider); } public sealed class ProjectAppDirectiveCompletionProviderTests : AbstractAppDirectiveCompletionProviderTests { protected override string DirectiveKind => "project"; internal override Type GetCompletionProviderType() => typeof(ProjectAppDirectiveCompletionProvider); [Fact] public async Task PathRecommendation_01() { using var tempRoot = new TempRoot(); var tempDirectory = tempRoot.CreateDirectory(); var nestedDirectory = tempDirectory.CreateDirectory("SubDirectory"); var scriptFilePath = Path.Combine(tempDirectory.Path, "App.cs"); var code = """ #:project $$ """; var markup = $""" <Workspace> <Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true"> <Document FilePath="{scriptFilePath}"><![CDATA[{code}]]></Document> </Project> </Workspace> """; await VerifyItemExistsAsync(markup, expectedItem: "SubDirectory"); } [Fact] public async Task PathRecommendation_02() { using var tempRoot = new TempRoot(); var tempDirectory = tempRoot.CreateDirectory(); var nestedDirectory = tempDirectory.CreateDirectory("SubDirectory"); var csprojFile = nestedDirectory.CreateFile("Project.csproj"); csprojFile.WriteAllText(""" <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> </PropertyGroup> </Project> """); var scriptFilePath = Path.Combine(tempDirectory.Path, "App.cs"); var code = """ #:project SubDirectory/$$ """; var markup = $""" <Workspace> <Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true"> <Document FilePath="{scriptFilePath}"><![CDATA[{code}]]></Document> </Project> </Workspace> """; await VerifyItemExistsAsync(markup, expectedItem: "Project.csproj"); } [Fact] public async Task PathRecommendation_03() { // Test a virtual file scenario (e.g. ctrl+N in VS Code or other cases where there is not an actual file on disk.) var code = """ #:project $$ """; var markup = $""" <Workspace> <Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true"> <Document FilePath="Untitled-1" ResolveFilePath="false"><![CDATA[{code}]]></Document> </Project> </Workspace> """; // In this case, only stuff like drive roots would be recommended. var expectedRoot = PlatformInformation.IsWindows ? "C:" : "/"; await VerifyItemExistsAsync(markup, expectedRoot); } // Note: The editor uses a shared mechanism to filter out completion items which don't match the prefix of what the user is typing. // Therefore we do not have "negative tests" here for file names. } public sealed class RefAppDirectiveCompletionProviderTests : AbstractAppDirectiveCompletionProviderTests { protected override string DirectiveKind => "ref"; internal override Type GetCompletionProviderType() => typeof(RefAppDirectiveCompletionProvider); [Fact] public async Task PathRecommendation_01() { using var tempRoot = new TempRoot(); var tempDirectory = tempRoot.CreateDirectory(); var nestedDirectory = tempDirectory.CreateDirectory("SubDirectory"); var scriptFilePath = Path.Combine(tempDirectory.Path, "App.cs"); var code = """ #:ref $$ """; var markup = $""" <Workspace> <Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true"> <Document FilePath="{scriptFilePath}"><![CDATA[{code}]]></Document> </Project> </Workspace> """; await VerifyItemExistsAsync(markup, expectedItem: "SubDirectory"); await VerifyItemIsAbsentAsync(markup, expectedItem: "*.cs"); await VerifyItemIsAbsentAsync(markup, expectedItem: "**/*.cs"); } [Fact] public async Task PathRecommendation_02() { using var tempRoot = new TempRoot(); var tempDirectory = tempRoot.CreateDirectory(); var nestedDirectory = tempDirectory.CreateDirectory("SubDirectory"); var utilFile = nestedDirectory.CreateFile("Util.cs"); utilFile.WriteAllText(""" public class Util { } """); var scriptFilePath = Path.Combine(tempDirectory.Path, "App.cs"); var code = """ #:ref SubDirectory/$$ """; var markup = $""" <Workspace> <Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true"> <Document FilePath="{scriptFilePath}"><![CDATA[{code}]]></Document> </Project> </Workspace> """; await VerifyItemExistsAsync(markup, expectedItem: "Util.cs"); await VerifyItemIsAbsentAsync(markup, expectedItem: "*.cs"); await VerifyItemIsAbsentAsync(markup, expectedItem: "**/*.cs"); } [Fact] public async Task PathRecommendation_OnlyShowsCsFiles() { using var tempRoot = new TempRoot(); var tempDirectory = tempRoot.CreateDirectory(); var csFile = tempDirectory.CreateFile("Library.cs"); csFile.WriteAllText("public class Library { }"); var txtFile = tempDirectory.CreateFile("Notes.txt"); txtFile.WriteAllText("some notes"); var scriptFilePath = Path.Combine(tempDirectory.Path, "App.cs"); var code = """ #:ref $$ """; var markup = $""" <Workspace> <Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true"> <Document FilePath="{scriptFilePath}"><![CDATA[{code}]]></Document> </Project> </Workspace> """; await VerifyItemExistsAsync(markup, expectedItem: "Library.cs"); await VerifyItemIsAbsentAsync(markup, expectedItem: "Notes.txt"); } [Fact] public async Task PathRecommendation_Virtual() { // Test a virtual file scenario (e.g. ctrl+N in VS Code or other cases where there is not an actual file on disk.) var code = """ #:ref $$ """; var markup = $""" <Workspace> <Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true"> <Document FilePath="Untitled-1" ResolveFilePath="false"><![CDATA[{code}]]></Document> </Project> </Workspace> """; // In this case, only stuff like drive roots would be recommended. var expectedRoot = PlatformInformation.IsWindows ? "C:" : "/"; await VerifyItemExistsAsync(markup, expectedRoot); await VerifyItemIsAbsentAsync(markup, expectedItem: "*.cs"); await VerifyItemIsAbsentAsync(markup, expectedItem: "**/*.cs"); } // Note: The editor uses a shared mechanism to filter out completion items which don't match the prefix of what the user is typing. // Therefore we do not have "negative tests" here for file names. } public sealed class IncludeAppDirectiveCompletionProviderTests : AbstractAppDirectiveCompletionProviderTests { protected override string DirectiveKind => "include"; internal override Type GetCompletionProviderType() => typeof(IncludeAppDirectiveCompletionProvider); [Fact] public async Task PathRecommendation_01() { using var tempRoot = new TempRoot(); var tempDirectory = tempRoot.CreateDirectory(); var nestedDirectory = tempDirectory.CreateDirectory("SubDirectory"); var scriptFilePath = Path.Combine(tempDirectory.Path, "App.cs"); var code = """ #:include $$ """; var markup = $""" <Workspace> <Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true"> <Document FilePath="{scriptFilePath}"><![CDATA[{code}]]></Document> </Project> </Workspace> """; await VerifyItemExistsAsync(markup, expectedItem: "SubDirectory"); await VerifyItemExistsAsync(markup, expectedItem: "*.cs"); await VerifyItemExistsAsync(markup, expectedItem: "**/*.cs"); } [Fact] public async Task PathRecommendation_02() { using var tempRoot = new TempRoot(); var tempDirectory = tempRoot.CreateDirectory(); var nestedDirectory = tempDirectory.CreateDirectory("SubDirectory"); var utilFile = nestedDirectory.CreateFile("Util.cs"); utilFile.WriteAllText(""" public class Util { } """); var scriptFilePath = Path.Combine(tempDirectory.Path, "App.cs"); var code = """ #:include SubDirectory/$$ """; var markup = $""" <Workspace> <Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true"> <Document FilePath="{scriptFilePath}"><![CDATA[{code}]]></Document> </Project> </Workspace> """; await VerifyItemExistsAsync(markup, expectedItem: "Util.cs"); await VerifyItemExistsAsync(markup, expectedItem: "*.cs"); await VerifyItemExistsAsync(markup, expectedItem: "**/*.cs"); } [Fact] public async Task PathRecommendation_Virtual_01() { // Test a virtual file scenario (e.g. ctrl+N in VS Code or other cases where there is not an actual file on disk.) var code = """ #:include $$ """; var markup = $""" <Workspace> <Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true"> <Document FilePath="Untitled-1" ResolveFilePath="false"><![CDATA[{code}]]></Document> </Project> </Workspace> """; // In this case, only stuff like drive roots would be recommended. var expectedRoot = PlatformInformation.IsWindows ? "C:" : "/"; await VerifyItemExistsAsync(markup, expectedRoot); await VerifyItemIsAbsentAsync(markup, expectedItem: "*.cs"); await VerifyItemIsAbsentAsync(markup, expectedItem: "**/*.cs"); } [Fact] public async Task PathRecommendation_Virtual_02() { // Test a virtual file scenario with an absolute path var root = PlatformInformation.IsWindows ? "C:/temp" : "/temp"; var code = $""" #:include {root}$$ """; var markup = $""" <Workspace> <Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true"> <Document FilePath="Untitled-1" ResolveFilePath="false"><![CDATA[{code}]]></Document> </Project> </Workspace> """; await VerifyItemExistsAsync(markup, expectedItem: "*.cs"); await VerifyItemExistsAsync(markup, expectedItem: "**/*.cs"); } [Fact] public async Task PathRecommendation_UnknownFileType() { using var tempRoot = new TempRoot(); var tempDirectory = tempRoot.CreateDirectory(); var nestedDirectory = tempDirectory.CreateDirectory("SubDirectory"); var utilFile = nestedDirectory.CreateFile("UNKNOWN"); utilFile.WriteAllText(""" public class Util { } """); var scriptFilePath = Path.Combine(tempDirectory.Path, "App.cs"); var code = """ #:include SubDirectory/$$ """; // Note: today, the completion provider doesn't actually respect this. // In future, we might want to use `FileBasedProgramsItemMapping` to restrict what file extensions we show in completion. var globalAnalyzerConfig = """ is_global = true build_property.FileBasedProgramsItemMapping = .cs=Compile;.resx=EmbeddedResource;.json=None;.razor=Content """; var markup = $""" <Workspace> <Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="FileBasedProgram=true"> <Document FilePath="{scriptFilePath}"><![CDATA[{code}]]></Document> <AnalyzerConfigDocument FilePath="{Path.Combine(tempDirectory.Path, ".globalconfig")}"><![CDATA[{globalAnalyzerConfig}]]></AnalyzerConfigDocument> </Project> </Workspace> """; await VerifyItemExistsAsync(markup, expectedItem: "UNKNOWN"); await VerifyItemExistsAsync(markup, expectedItem: "*.cs"); await VerifyItemExistsAsync(markup, expectedItem: "**/*.cs"); } // Note: The editor uses a shared mechanism to filter out completion items which don't match the prefix of what the user is typing. // Therefore we do not have "negative tests" here for file names. } public abstract class AbstractAppDirectiveCompletionProviderTests : AbstractCSharpCompletionProviderTests { /// <summary>The directive kind. For example, `package` in `#:package MyNugetPackage@Version`.</summary> /// <remarks>Term defined in feature doc: https://github.com/dotnet/sdk/blob/main/documentation/general/dotnet-run-file.md#directives-for-project-metadata</remarks> protected abstract string DirectiveKind { get; } protected static string GetMarkup(string code, string features = "FileBasedProgram=true") => $$""" <Workspace> <Project Language="C#" CommonReferences="true" AssemblyName="Test1" Features="{{features}}"> <Document><![CDATA[{{code}}]]></Document> </Project> </Workspace> """; [Fact] public Task AfterHashColon() => VerifyItemExistsAsync(GetMarkup(""" #:$$ """), expectedItem: DirectiveKind); [Fact] public Task AfterHashColonQuote() => VerifyItemIsAbsentAsync(GetMarkup(""" #:"$$ """), expectedItem: DirectiveKind); [Fact] public Task NotWhenFileBasedProgramIsDisabled() => VerifyItemIsAbsentAsync(GetMarkup(""" #:$$ """, features: ""), expectedItem: DirectiveKind); [Fact] public Task AfterHashColonSpace() => VerifyItemExistsAsync(GetMarkup(""" #: $$ """), expectedItem: DirectiveKind); [Fact] public Task NotAfterHashColonSpaceColon() => VerifyItemIsAbsentAsync(GetMarkup(""" #: :$$ """), expectedItem: DirectiveKind); [Fact] public Task NotAfterHashColonWord() => VerifyItemIsAbsentAsync(GetMarkup(""" #:word$$ """), expectedItem: DirectiveKind); [Fact] public Task AfterHashColonBeforeWord() => VerifyItemExistsAsync(GetMarkup(""" #:$$word """), expectedItem: DirectiveKind); [Fact] public Task AfterHashColonBeforeNameEqualsValue() => VerifyItemExistsAsync(GetMarkup(""" #:$$ Name=Value """), expectedItem: DirectiveKind); [Fact] public Task NotAfterHashOnly() => VerifyItemIsAbsentAsync(GetMarkup(""" #$$ """), expectedItem: DirectiveKind); [Fact] public Task NotAfterColonOnly() => VerifyItemIsAbsentAsync(GetMarkup(""" :$$ """), expectedItem: DirectiveKind); [Fact] public Task NotAfterStatement() => VerifyItemIsAbsentAsync(GetMarkup(""" Console.WriteLine(); $$ """), expectedItem: DirectiveKind); }