Initial end to end working completion with tests
This commit is contained in:
Родитель
f819973720
Коммит
dc849d3adf
12
README.md
12
README.md
|
@ -1,2 +1,12 @@
|
|||
# codeanalysis
|
||||
# Xamarin CodeAnalyis
|
||||
|
||||
Analyzers, code fixers and custom completion for Xamarin projects
|
||||
|
||||
## Building
|
||||
|
||||
Just open Xamarin.CodeAnalysis.sln and build.
|
||||
|
||||
> NOTE: the *first* build ever needs to be run from an administrator command prompt,
|
||||
> because the extension provides MSBuild targets that need to be symlinked from the
|
||||
> `Exp` hive location to the `VsInstallDir\MSBuild` location, which requires elevation.
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.28218.60
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.28223.52
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.CodeAnalysis", "src\Xamarin.CodeAnalysis\Xamarin.CodeAnalysis.csproj", "{A83DFC4B-4270-4A1A-8438-897A8AA61AE0}"
|
||||
EndProject
|
||||
|
@ -19,6 +19,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
src\GitInfo.txt = src\GitInfo.txt
|
||||
src\NuGet.Config = src\NuGet.Config
|
||||
Packages.props = Packages.props
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
|
|
|
@ -13,8 +13,9 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<GitSkipCache Condition="'$(CI)' == 'true'">true</GitSkipCache>
|
||||
<Configuration Condition="'$(Configuration)' == '' and '$(CI)' == 'true'">Release</Configuration>
|
||||
<GitSkipCache Condition="$(CI)">true</GitSkipCache>
|
||||
<Configuration Condition="'$(Configuration)' == '' and $(CI)">Release</Configuration>
|
||||
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Redeclared by GitInfo -->
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Xamarin.CodeAnalysis
|
||||
{
|
||||
internal static class Extensions
|
||||
{
|
||||
//public static async Task<SemanticModel> GetSemanticModelForSpanAsync(this Document document, TextSpan span, CancellationToken cancellationToken)
|
||||
//{
|
||||
// var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
|
||||
// var token = root.FindToken(span.Start);
|
||||
// if (token.Parent == null)
|
||||
// {
|
||||
// return await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Completion;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Options;
|
||||
using Microsoft.CodeAnalysis.Tags;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Xamarin.CodeAnalysis
|
||||
{
|
||||
[ExportCompletionProvider(nameof(ResourceCompletionProvider), LanguageNames.CSharp)]
|
||||
public class ResourceCompletionProvider : CompletionProvider
|
||||
{
|
||||
private static readonly CompletionItemRules StandardCompletionRules = CompletionItemRules.Default.WithSelectionBehavior(CompletionItemSelectionBehavior.SoftSelection);
|
||||
|
||||
public override bool ShouldTriggerCompletion(SourceText text, int caretPosition, CompletionTrigger trigger, OptionSet options)
|
||||
{
|
||||
// TODO: should trigger if we're inside a string
|
||||
return base.ShouldTriggerCompletion(text, caretPosition, trigger, options);
|
||||
}
|
||||
|
||||
public override Task<CompletionDescription> GetDescriptionAsync(Document document, CompletionItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: get the actual xml file location.
|
||||
return Task.FromResult(CompletionDescription.FromText($"{item.Properties["Path"]}({item.Properties["Line"]},{item.Properties["Position"]})"));
|
||||
}
|
||||
|
||||
public override Task<CompletionChange> GetChangeAsync(Document document, CompletionItem item, char? commitKey, CancellationToken cancellationToken)
|
||||
{
|
||||
// Determine change to insert
|
||||
return base.GetChangeAsync(document, item, commitKey, cancellationToken);
|
||||
}
|
||||
|
||||
static readonly Regex isResourceValue = new Regex(@"Resources\\values\\.*.xml", RegexOptions.Compiled);
|
||||
|
||||
public override async Task ProvideCompletionsAsync(CompletionContext completionContext)
|
||||
{
|
||||
if (!completionContext.Document.SupportsSemanticModel)
|
||||
return;
|
||||
|
||||
var position = completionContext.Position;
|
||||
var document = completionContext.Document;
|
||||
var cancellationToken = completionContext.CancellationToken;
|
||||
|
||||
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
|
||||
var span = completionContext.CompletionListSpan;
|
||||
var token = root.FindToken(span.Start);
|
||||
var node = token.Parent?.AncestorsAndSelf().FirstOrDefault(a => a.FullSpan.Contains(span));
|
||||
if (node is LiteralExpressionSyntax literal &&
|
||||
node?.Parent is AttributeArgumentSyntax argument &&
|
||||
node?.Parent?.Parent?.Parent is AttributeSyntax attribute)
|
||||
{
|
||||
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
|
||||
var symbol = semanticModel.GetSymbolInfo(attribute, cancellationToken).Symbol;
|
||||
if (symbol?.ContainingType.ToDisplayString() == "Android.App.ActivityAttribute" ||
|
||||
(symbol == null && attribute.Name.ToString() == "Activity"))
|
||||
{
|
||||
var name = argument.NameEquals.Name.ToString();
|
||||
// TODO: consider resource files some other way?
|
||||
var valueDocs = document.Project.AdditionalDocuments.Where(doc => isResourceValue.IsMatch(doc.FilePath));
|
||||
var elementName = "string";
|
||||
if (name == "Theme")
|
||||
elementName = "style";
|
||||
|
||||
var strings = new HashSet<string>();
|
||||
var styles = new HashSet<string>();
|
||||
var xmlSettings = new XmlReaderSettings
|
||||
{
|
||||
IgnoreComments = true,
|
||||
IgnoreProcessingInstructions = true,
|
||||
IgnoreWhitespace = true,
|
||||
};
|
||||
|
||||
foreach (var doc in valueDocs)
|
||||
{
|
||||
XmlReader reader = null;
|
||||
try
|
||||
{
|
||||
if (File.Exists(doc.FilePath))
|
||||
{
|
||||
reader = XmlReader.Create(doc.FilePath, xmlSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
// In tests, the file doesn't exist.
|
||||
var writer = new StringWriter();
|
||||
(await doc.GetTextAsync(cancellationToken)).Write(writer, cancellationToken);
|
||||
reader = XmlReader.Create(new StringReader(writer.ToString()), xmlSettings);
|
||||
}
|
||||
|
||||
if (reader.MoveToContent() == XmlNodeType.Element)
|
||||
{
|
||||
// TODO: cache already parsed results as long as the text version remains the same?
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.LocalName == elementName && reader.GetAttribute("name") is string id)
|
||||
{
|
||||
completionContext.AddItem(CompletionItem.Create($"@{reader.LocalName}/{id}",
|
||||
tags: ImmutableArray.Create(WellKnownTags.Constant, "Xamarin"),
|
||||
properties: ImmutableDictionary.Create<string, string>()
|
||||
.Add("Path", doc.FilePath)
|
||||
.Add("Line", ((IXmlLineInfo)reader).LineNumber.ToString())
|
||||
.Add("Position", ((IXmlLineInfo)reader).LinePosition.ToString()),
|
||||
rules: StandardCompletionRules));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Completion;
|
||||
using Microsoft.CodeAnalysis.Options;
|
||||
using Microsoft.CodeAnalysis.Tags;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Xamarin.CodeAnalysis
|
||||
{
|
||||
[ExportCompletionProvider(nameof(StringCompletionProvider), LanguageNames.CSharp)]
|
||||
public class StringCompletionProvider : CompletionProvider
|
||||
{
|
||||
private static readonly CompletionItemRules StandardCompletionRules = CompletionItemRules.Default.WithSelectionBehavior(CompletionItemSelectionBehavior.SoftSelection);
|
||||
|
||||
public override bool ShouldTriggerCompletion(SourceText text, int caretPosition, CompletionTrigger trigger, OptionSet options)
|
||||
{
|
||||
// TODO: should trigger if we're inside a string
|
||||
return base.ShouldTriggerCompletion(text, caretPosition, trigger, options);
|
||||
}
|
||||
|
||||
public override Task<CompletionDescription> GetDescriptionAsync(Document document, CompletionItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: get the actual xml file location.
|
||||
return Task.FromResult(CompletionDescription.FromText("strings.xml(120,34)"));
|
||||
}
|
||||
|
||||
public override Task<CompletionChange> GetChangeAsync(Document document, CompletionItem item, char? commitKey, CancellationToken cancellationToken)
|
||||
{
|
||||
// Determine change to insert
|
||||
return base.GetChangeAsync(document, item, commitKey, cancellationToken);
|
||||
}
|
||||
|
||||
public override async Task ProvideCompletionsAsync(CompletionContext context)
|
||||
{
|
||||
context.AddItem(CompletionItem.Create("@style/MainTheme",
|
||||
tags: ImmutableArray.Create(WellKnownTags.Constant),
|
||||
// TODO: props should contain the file location it was retrieved from to use in description
|
||||
// properties: ImmutableDictionary.CreateBuilder<string, string>().Add(,
|
||||
rules: StandardCompletionRules));
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="@(AndroidResource -> WithMetadataValue('RelativeDir', 'Resources\values\'))" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -20,12 +20,15 @@
|
|||
<IsExperimental>true</IsExperimental>
|
||||
<IsSystemComponent>false</IsSystemComponent>
|
||||
|
||||
<!--<CreateVsixContainer>true</CreateVsixContainer>-->
|
||||
<VsixVersion>42.42.42</VsixVersion>
|
||||
<DefaultItemExcludes>*.targets</DefaultItemExcludes>
|
||||
|
||||
<RestoreSources>$(RestoreSources);https://api.nuget.org/v3/index.json;https://nugets.blob.core.windows.net/xvssdk/index.json</RestoreSources>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(CI)' == 'true'">
|
||||
|
||||
<PropertyGroup Condition="$(CI)">
|
||||
<IsProductComponent>true</IsProductComponent>
|
||||
<IsExperimental>false</IsExperimental>
|
||||
<IsSystemComponent>true</IsSystemComponent>
|
||||
|
@ -59,9 +62,16 @@
|
|||
<Resource Include="Properties\ICN_Xamarin.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="*.targets" IncludeInVSIX="true" VSIXSubPath="Xamarin" SymLink="true" />
|
||||
<Content Update="Xamarin.CodeAnalysis.ImportAfter.targets" VSIXSubPath="Current\Microsoft.Common.Targets\ImportAfter" />
|
||||
<Content Update="@(Content)" Condition="$(CI)" InstallRoot="MSBuild" />
|
||||
<UpToDateCheckInput Include="@(Content)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="GetVsixVersion" DependsOnTargets="SetVersions" Returns="$(VsixVersion)">
|
||||
<PropertyGroup>
|
||||
<VsixVersion Condition="'$(CI)' == 'true'">$(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch)</VsixVersion>
|
||||
<VsixVersion Condition="$(CI)">$(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch)</VsixVersion>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
|
||||
|
@ -83,4 +93,71 @@
|
|||
<Target Name="IsExperimental" Returns="$(IsExperimental)" />
|
||||
<Target Name="IsSystemComponent" Returns="$(IsSystemComponent)" />
|
||||
|
||||
<PropertyGroup Condition="'$(OS)' == 'Windows_NT'">
|
||||
<BuildDependsOn Condition="!$(CI)">
|
||||
$(BuildDependsOn);
|
||||
SymLink
|
||||
</BuildDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="SymLink" DependsOnTargets="IsAdministrator;CollectLinkItems;ReplaceLinkItems" />
|
||||
|
||||
<Target Name="IsAdministrator">
|
||||
<IsAdministrator>
|
||||
<Output TaskParameter="Result" PropertyName="IsAdministrator" />
|
||||
</IsAdministrator>
|
||||
<Warning Text="Current user isn't an Administrator, so MSBuild artifacts won't be symlinked." Condition="'$(IsAdministrator)' == 'false'" />
|
||||
</Target>
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<MkLinkCandidate>
|
||||
<Exists>false</Exists>
|
||||
<IsSymLink>false</IsSymLink>
|
||||
</MkLinkCandidate>
|
||||
<VSIXSourceItem>
|
||||
<VSIXSubPath />
|
||||
<SymLink />
|
||||
</VSIXSourceItem>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<Target Name="CollectLinkItems" DependsOnTargets="IsAdministrator;GetVsixDeploymentPath;GetVsixSourceItems" Condition="'$(IsAdministrator)' == 'true'">
|
||||
<ItemGroup>
|
||||
<MkLinkCandidate Include="@(VSIXSourceItem -> '$(MSBuildExtensionsPath)\%(VSIXSubPath)\%(Filename)%(Extension)')" Condition="'%(SymLink)' == 'true'">
|
||||
<LinkTarget>$(VsixDeploymentPath)\%(VSIXSubPath)\%(Filename)%(Extension)</LinkTarget>
|
||||
</MkLinkCandidate>
|
||||
<MkLinkCandidate Condition="Exists('%(FullPath)')">
|
||||
<IsSymLink Condition="$([MSBuild]::BitwiseAnd(1024, $([System.IO.File]::GetAttributes('%(FullPath)')))) == '1024'">true</IsSymLink>
|
||||
<Exists>true</Exists>
|
||||
</MkLinkCandidate>
|
||||
<MkLinkSource Include="@(MkLinkCandidate)" Condition="!Exists('%(FullPath)') Or '%(IsSymLink)' == 'false'" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="ReplaceLinkItems" Condition="'@(MkLinkSource)' != '' And '$(IsAdministrator)' == 'true'">
|
||||
<Message Text="In $(Configuration) builds, we attempt to symlink MSBuild files with current project output." Importance="high" />
|
||||
<ItemGroup>
|
||||
<_FilesToDelete Include="@(MkLinkSource -> WithMetadataValue('Exists', 'true'))" />
|
||||
</ItemGroup>
|
||||
<Exec Command='del "%(_FilesToDelete.FullPath)"' EchoOff="true" Condition="'@(_FilesToDelete)' != ''" />
|
||||
<Exec Command='mklink "%(MkLinkSource.Identity)" "%(MkLinkSource.LinkTarget)"'
|
||||
ConsoleToMSBuild="true"
|
||||
EchoOff="true"
|
||||
Condition="Exists('%(MkLinkSource.RootDir)%(MkLinkSource.Directory)')">
|
||||
<Output TaskParameter="ConsoleOutput" ItemName="MkLinked" />
|
||||
</Exec>
|
||||
<Message Importance="high" Text="%(MkLinked.Identity)" Condition="'@(MkLinked)' != ''" />
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="IsAdministrator" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
|
||||
<ParameterGroup>
|
||||
<Result ParameterType="System.Boolean" Output="true" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Using Namespace="System.Security.Principal" />
|
||||
<Code Type="Fragment" Language="cs">
|
||||
Result = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
|
||||
</Code>
|
||||
</Task>
|
||||
</UsingTask>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,9 @@
|
|||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
|
||||
<XamarinCodeAnalysisTargets Condition="'$(XamarinCodeAnalysisTargets)' == ''">$(MSBuildExtensionsPath)\Xamarin\Xamarin.CodeAnalysis.targets</XamarinCodeAnalysisTargets>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Condition="Exists('$(XamarinCodeAnalysisTargets)')" Project="$(XamarinCodeAnalysisTargets)" />
|
||||
</Project>
|
|
@ -0,0 +1,5 @@
|
|||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<Import Project="Xamarin.CodeAnalysis.Android.targets" Condition="'$(TargetFrameworkIdentifier)' == 'MonoAndroid'" />
|
||||
|
||||
</Project>
|
|
@ -1,6 +1,4 @@
|
|||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Completion;
|
||||
|
@ -13,21 +11,24 @@ namespace Xamarin.CodeAnalysis.Tests
|
|||
public class CompletionTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(@"using System;
|
||||
public class Foo
|
||||
[InlineData(@"using Android.App;
|
||||
using Android.Support.Design.Widget;
|
||||
using Android.Support.V7.App;
|
||||
using Android.Views;
|
||||
|
||||
[Activity(Label = ""`"", MainLauncher = true)]
|
||||
public class MainActivity : AppCompatActivity, NavigationView.IOnNavigationItemSelectedListener
|
||||
{
|
||||
public void Do()
|
||||
{
|
||||
Console.WriteLine(""`"");
|
||||
}
|
||||
}")]
|
||||
public async Task can_retrieve_completion(string code)
|
||||
public bool OnNavigationItemSelected(IMenuItem menuItem) => true;
|
||||
}
|
||||
", "@string/app_name")]
|
||||
public async Task can_retrieve_completion(string code, string completion)
|
||||
{
|
||||
var hostServices = MefHostServices.Create(MefHostServices.DefaultAssemblies.Concat(
|
||||
new[]
|
||||
{
|
||||
typeof(CompletionService).Assembly,
|
||||
typeof(StringCompletionProvider).Assembly,
|
||||
typeof(ResourceCompletionProvider).Assembly,
|
||||
}));
|
||||
|
||||
var workspace = new AdhocWorkspace(hostServices);
|
||||
|
@ -40,7 +41,17 @@ public class Foo
|
|||
MetadataReference.CreateFromFile("Xamarin.CodeAnalysis.dll"),
|
||||
MetadataReference.CreateFromFile("Xamarin.CodeAnalysis.Completion.dll"),
|
||||
})
|
||||
.AddDocument("TestDocument.cs", code);
|
||||
.AddAdditionalDocument("strings.xml", @"<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name='app_name'>TestApp</string>
|
||||
</resources>", new[] { "Resources\\values" }, "Resources\\values\\strings.xml")
|
||||
.Project
|
||||
.AddAdditionalDocument("styles.xml", @"<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<style name='AppTheme' parent='Theme.AppCompat.Light.DarkActionBar' />
|
||||
</resources>", new[] { "Resources\\values" }, "Resources\\values\\styles.xml")
|
||||
.Project
|
||||
.AddDocument("TestDocument.cs", code.Replace("`", ""));
|
||||
|
||||
var service = CompletionService.GetService(document);
|
||||
Assert.NotNull(service);
|
||||
|
@ -51,7 +62,50 @@ public class Foo
|
|||
var completions = await service.GetCompletionsAsync(document, caret);
|
||||
|
||||
Assert.NotNull(completions);
|
||||
Assert.NotEmpty(completions.Items);
|
||||
Assert.Contains(completions.Items, x => x.Tags.Contains("Xamarin"));
|
||||
Assert.Contains(completions.Items, x => x.DisplayText == completion);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"using System;
|
||||
public class Foo
|
||||
{
|
||||
public void Do()
|
||||
{
|
||||
Console.`WriteLine("""");
|
||||
}
|
||||
}")]
|
||||
public async Task does_not_trigger_completion(string code)
|
||||
{
|
||||
var hostServices = MefHostServices.Create(MefHostServices.DefaultAssemblies.Concat(
|
||||
new[]
|
||||
{
|
||||
typeof(CompletionService).Assembly,
|
||||
typeof(ResourceCompletionProvider).Assembly,
|
||||
}));
|
||||
|
||||
var workspace = new AdhocWorkspace(hostServices);
|
||||
var document = workspace
|
||||
.AddProject("TestProject", LanguageNames.CSharp)
|
||||
.WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
|
||||
.WithMetadataReferences(new MetadataReference[]
|
||||
{
|
||||
MetadataReference.CreateFromFile(ThisAssembly.Metadata.NETStandardReference),
|
||||
MetadataReference.CreateFromFile("Xamarin.CodeAnalysis.dll"),
|
||||
MetadataReference.CreateFromFile("Xamarin.CodeAnalysis.Completion.dll"),
|
||||
})
|
||||
.AddDocument("TestDocument.cs", code.Replace("`", ""));
|
||||
|
||||
var service = CompletionService.GetService(document);
|
||||
Assert.NotNull(service);
|
||||
|
||||
var caret = code.IndexOf('`');
|
||||
Assert.NotEqual(-1, caret);
|
||||
|
||||
var completions = await service.GetCompletionsAsync(document, caret);
|
||||
|
||||
Assert.NotNull(completions);
|
||||
Assert.DoesNotContain(completions.Items, x => x.Tags.Contains("Xamarin"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net46</TargetFramework>
|
||||
<TargetFramework Condition="'$(VisualStudioVersion)' == '16.0'">net472</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
Загрузка…
Ссылка в новой задаче