diff options
author | James Newton-King <james@newtonking.com> | 2022-09-28 05:37:35 +0300 |
---|---|---|
committer | James Newton-King <james@newtonking.com> | 2022-09-28 05:37:35 +0300 |
commit | 99f475daea151302e18d13f0732fb19e36d5357e (patch) | |
tree | 5c2cd37e57808ec3481ea6f4c708c852f720f3a7 | |
parent | 9da05de7b25398e6dcf770f8f6a3b1202f52c67a (diff) |
Remove framework parameters completion providerjamesnk/route-syntax-net8-squash
3 files changed, 1 insertions, 1440 deletions
diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/FrameworkParametersCompletionProvider.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/FrameworkParametersCompletionProvider.cs deleted file mode 100644 index 01d3932c10..0000000000 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/FrameworkParametersCompletionProvider.cs +++ /dev/null @@ -1,582 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure; -using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure.VirtualChars; -using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.RoutePattern; -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; -using Document = Microsoft.CodeAnalysis.Document; -using RoutePatternToken = Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure.EmbeddedSyntax.EmbeddedSyntaxToken<Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.RoutePattern.RoutePatternKind>; - -namespace Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage; - -[ExportCompletionProvider(nameof(RoutePatternCompletionProvider), LanguageNames.CSharp)] -[Shared] -public class FrameworkParametersCompletionProvider : CompletionProvider -{ - private const string StartKey = nameof(StartKey); - private const string LengthKey = nameof(LengthKey); - private const string NewTextKey = nameof(NewTextKey); - private const string NewPositionKey = nameof(NewPositionKey); - private const string DescriptionKey = nameof(DescriptionKey); - - // Always soft-select these completion items. Also, never filter down. - private static readonly CompletionItemRules s_rules = CompletionItemRules.Create( - selectionBehavior: CompletionItemSelectionBehavior.SoftSelection, - filterCharacterRules: ImmutableArray.Create(CharacterSetModificationRule.Create(CharacterSetModificationKind.Replace, Array.Empty<char>()))); - - // The space between type and parameter name. - // void TestMethod(int // <- space after type name - public ImmutableHashSet<char> TriggerCharacters { get; } = ImmutableHashSet.Create(' '); - - public override bool ShouldTriggerCompletion(SourceText text, int caretPosition, CompletionTrigger trigger, OptionSet options) - { - if (trigger.Kind is CompletionTriggerKind.Invoke or CompletionTriggerKind.InvokeAndCommitIfUnique) - { - return true; - } - - if (trigger.Kind == CompletionTriggerKind.Insertion) - { - return TriggerCharacters.Contains(trigger.Character); - } - - return false; - } - - public override Task<CompletionDescription> GetDescriptionAsync(Document document, CompletionItem item, CancellationToken cancellationToken) - { - if (!item.Properties.TryGetValue(DescriptionKey, out var description)) - { - return Task.FromResult<CompletionDescription>(null); - } - - return Task.FromResult(CompletionDescription.Create( - ImmutableArray.Create(new TaggedText(TextTags.Text, description)))); - } - - public override Task<CompletionChange> GetChangeAsync(Document document, CompletionItem item, char? commitKey, CancellationToken cancellationToken) - { - // These values have always been added by us. - var startString = item.Properties[StartKey]; - var lengthString = item.Properties[LengthKey]; - var newText = item.Properties[NewTextKey]; - - // This value is optionally added in some cases and may not always be there. - item.Properties.TryGetValue(NewPositionKey, out var newPositionString); - - return Task.FromResult(CompletionChange.Create( - new TextChange(new TextSpan(int.Parse(startString, CultureInfo.InvariantCulture), int.Parse(lengthString, CultureInfo.InvariantCulture)), newText), - newPositionString == null ? null : int.Parse(newPositionString, CultureInfo.InvariantCulture))); - } - - public override async Task ProvideCompletionsAsync(CompletionContext context) - { - var position = context.Position; - - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - if (root == null) - { - return; - } - - SyntaxToken? parentOpt = null; - - var token = root.FindTokenOnLeftOfPosition(position); - - // If space is after ? or > then it's likely a nullable or generic type. Move to previous type token. - if (token.IsKind(SyntaxKind.QuestionToken) || token.IsKind(SyntaxKind.GreaterThanToken)) - { - token = token.GetPreviousToken(); - } - - // Whitespace should follow the identifier token of the parameter. - if (!IsArgumentTypeToken(token)) - { - return; - } - - var container = TryFindMinimalApiArgument(token.Parent) ?? TryFindMvcActionParameter(token.Parent); - if (container == null) - { - return; - } - - var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); - if (semanticModel == null) - { - return; - } - - if (!WellKnownTypes.TryGetOrCreate(semanticModel.Compilation, out var wellKnownTypes)) - { - return; - } - - // Don't offer route parameter names when the parameter type can't be bound to route parameters. e.g. HttpContext. - var isCurrentParameterSpecialType = IsCurrentParameterNotBindable(token, semanticModel, wellKnownTypes, context.CancellationToken); - if (isCurrentParameterSpecialType) - { - return; - } - - // Don't offer route parameter names when the parameter has an attribute that can't be bound to route parameters. - // e.g [AsParameters] or [IFromBodyMetadata]. - var hasNonRouteAttribute = HasNonRouteAttribute(token, semanticModel, wellKnownTypes, context.CancellationToken); - if (hasNonRouteAttribute) - { - return; - } - - SyntaxToken routeStringToken; - SyntaxNode methodNode; - - if (container.Parent.IsKind(SyntaxKind.Argument)) - { - // Minimal API - var mapMethodParts = RoutePatternUsageDetector.FindMapMethodParts(semanticModel, wellKnownTypes, container, context.CancellationToken); - if (mapMethodParts == null) - { - return; - } - var (_, routeStringExpression, delegateExpression) = mapMethodParts.Value; - - routeStringToken = routeStringExpression.Token; - methodNode = delegateExpression; - - // Incomplete inline delegate syntax is very messy and arguments are mixed together. - // Examine tokens to figure out wether the current token is the argument name. - var previous = token.GetPreviousToken(); - if (previous.IsKind(SyntaxKind.CommaToken) || - previous.IsKind(SyntaxKind.OpenParenToken) || - previous.IsKind(SyntaxKind.OutKeyword) || - previous.IsKind(SyntaxKind.InKeyword) || - previous.IsKind(SyntaxKind.RefKeyword) || - previous.IsKind(SyntaxKind.ParamsKeyword) || - (previous.IsKind(SyntaxKind.CloseBracketToken) && previous.GetRequiredParent().FirstAncestorOrSelf<AttributeListSyntax>() != null) || - (previous.IsKind(SyntaxKind.LessThanToken) && previous.GetRequiredParent().FirstAncestorOrSelf<GenericNameSyntax>() != null)) - { - // Positioned after type token. Don't replace current. - } - else - { - // Space after argument name. Don't show completion options. - if (context.Trigger.Kind == CompletionTriggerKind.Insertion) - { - return; - } - - parentOpt = token; - } - } - else if (container.IsKind(SyntaxKind.Parameter)) - { - // MVC - var methodSyntax = container.FirstAncestorOrSelf<MethodDeclarationSyntax>(); - var methodSymbol = semanticModel.GetDeclaredSymbol(methodSyntax, context.CancellationToken); - - // Check method is a valid MVC action. - if (methodSymbol?.ContainingType is not INamedTypeSymbol typeSymbol || - !MvcDetector.IsController(typeSymbol, wellKnownTypes) || - !MvcDetector.IsAction(methodSymbol, wellKnownTypes)) - { - return; - } - - var routeToken = TryGetMvcActionRouteToken(context, semanticModel, methodSyntax); - if (routeToken == null) - { - return; - } - - routeStringToken = routeToken.Value; - methodNode = methodSyntax; - - if (((ParameterSyntax)container).Identifier == token) - { - // Space after argument name. Don't show completion options. - if (context.Trigger.Kind == CompletionTriggerKind.Insertion) - { - return; - } - - parentOpt = token; - } - } - else - { - return; - } - - var virtualChars = CSharpVirtualCharService.Instance.TryConvertToVirtualChars(routeStringToken); - var tree = RoutePatternParser.TryParse(virtualChars, supportTokenReplacement: false); - if (tree == null) - { - return; - } - - var routePatternCompletionContext = new EmbeddedCompletionContext(context, tree, wellKnownTypes); - - var existingParameterNames = GetExistingParameterNames(methodNode); - foreach (var parameterName in existingParameterNames) - { - routePatternCompletionContext.AddUsedParameterName(parameterName); - } - - ProvideCompletions(routePatternCompletionContext, parentOpt); - - if (routePatternCompletionContext.Items == null || routePatternCompletionContext.Items.Count == 0) - { - return; - } - - foreach (var embeddedItem in routePatternCompletionContext.Items) - { - var change = embeddedItem.Change; - var textChange = change.TextChange; - - var properties = ImmutableDictionary.CreateBuilder<string, string>(); - properties.Add(StartKey, textChange.Span.Start.ToString(CultureInfo.InvariantCulture)); - properties.Add(LengthKey, textChange.Span.Length.ToString(CultureInfo.InvariantCulture)); - properties.Add(NewTextKey, textChange.NewText); - properties.Add(DescriptionKey, embeddedItem.FullDescription); - - if (change.NewPosition != null) - { - properties.Add(NewPositionKey, change.NewPosition.ToString()); - } - - // Keep everything sorted in the order we just produced the items in. - var sortText = routePatternCompletionContext.Items.Count.ToString("0000", CultureInfo.InvariantCulture); - context.AddItem(CompletionItem.Create( - displayText: embeddedItem.DisplayText, - inlineDescription: "", - sortText: sortText, - properties: properties.ToImmutable(), - rules: s_rules, - tags: ImmutableArray.Create(embeddedItem.Glyph))); - } - - context.SuggestionModeItem = CompletionItem.Create( - displayText: "<Name>", - inlineDescription: "", - rules: CompletionItemRules.Default); - - context.IsExclusive = true; - } - - private static bool IsArgumentTypeToken(SyntaxToken token) - { - return SyntaxFacts.IsPredefinedType(token.Kind()) || token.IsKind(SyntaxKind.IdentifierToken); - } - - private static SyntaxToken? TryGetMvcActionRouteToken(CompletionContext context, SemanticModel? semanticModel, MethodDeclarationSyntax? method) - { - foreach (var attributeList in method.AttributeLists) - { - foreach (var attribute in attributeList.Attributes) - { - foreach (var attributeArgument in attribute.ArgumentList.Arguments) - { - if (RouteStringSyntaxDetector.IsArgumentToAttributeParameterWithMatchingStringSyntaxAttribute( - semanticModel, - attributeArgument, - context.CancellationToken, - out var identifer) && - identifer == "Route" && - attributeArgument.Expression is LiteralExpressionSyntax literalExpression) - { - return literalExpression.Token; - } - } - } - } - - return null; - } - - private static SyntaxNode? TryFindMvcActionParameter(SyntaxNode node) - { - var current = node; - while (current != null) - { - if (current.IsKind(SyntaxKind.Parameter)) - { - return current; - } - - current = current.Parent; - } - - return null; - } - - private static SyntaxNode? TryFindMinimalApiArgument(SyntaxNode node) - { - var current = node; - while (current != null) - { - if (current.Parent?.IsKind(SyntaxKind.Argument) ?? false) - { - if (current.Parent?.Parent?.IsKind(SyntaxKind.ArgumentList) ?? false) - { - return current; - } - } - - current = current.Parent; - } - - return null; - } - - private static bool HasNonRouteAttribute(SyntaxToken token, SemanticModel semanticModel, WellKnownTypes wellKnownTypes, CancellationToken cancellationToken) - { - if (token.Parent?.Parent is ParameterSyntax parameter) - { - foreach (var attributeList in parameter.AttributeLists.OfType<AttributeListSyntax>()) - { - foreach (var attribute in attributeList.Attributes) - { - var attributeTypeSymbol = semanticModel.GetSymbolInfo(attribute, cancellationToken).GetAnySymbol(); - - if (attributeTypeSymbol != null) - { - foreach (var nonRouteMetadataType in wellKnownTypes.NonRouteMetadataTypes) - { - if (attributeTypeSymbol.ContainingSymbol is ITypeSymbol typeSymbol && - typeSymbol.Implements(nonRouteMetadataType)) - { - return true; - } - } - if (SymbolEqualityComparer.Default.Equals(attributeTypeSymbol.ContainingSymbol, wellKnownTypes.AsParametersAttribute)) - { - return true; - } - } - } - } - } - - return false; - } - - private static bool IsCurrentParameterNotBindable(SyntaxToken token, SemanticModel semanticModel, WellKnownTypes wellKnownTypes, CancellationToken cancellationToken) - { - if (token.Parent.IsKind(SyntaxKind.PredefinedType)) - { - return false; - } - - var parameterTypeSymbol = semanticModel.GetSymbolInfo(token.Parent, cancellationToken).GetAnySymbol(); - if (parameterTypeSymbol is INamedTypeSymbol typeSymbol) - { - // Check if the parameter type is a minimal API special type. e.g. HttpContext. - foreach (var specialType in wellKnownTypes.ParameterSpecialTypes) - { - if (typeSymbol.IsType(specialType)) - { - return true; - } - } - } - else if (parameterTypeSymbol is IMethodSymbol) - { - // If the parameter type is a method then the method is bound to the minimal API. - return true; - } - - return false; - } - - private static ImmutableArray<string> GetExistingParameterNames(SyntaxNode node) - { - var builder = ImmutableArray.CreateBuilder<string>(); - - if (node is TupleExpressionSyntax tupleExpression) - { - foreach (var argument in tupleExpression.Arguments) - { - if (argument.Expression is DeclarationExpressionSyntax declarationExpression && - declarationExpression.Designation is SingleVariableDesignationSyntax variableDesignationSyntax && - variableDesignationSyntax.Identifier is { } identifer && - !identifer.IsMissing) - { - builder.Add(identifer.ValueText); - } - } - } - else - { - var parameterList = node switch - { - ParenthesizedLambdaExpressionSyntax parenthesizedLambdaExpression => parenthesizedLambdaExpression.ParameterList, - MethodDeclarationSyntax methodDeclaration => methodDeclaration.ParameterList, - _ => null - }; - - if (parameterList != null) - { - foreach (var p in parameterList.Parameters) - { - if (p is ParameterSyntax parameter && - parameter.Identifier is { } identifer && !identifer.IsMissing) - { - builder.Add(identifer.ValueText); - } - } - } - } - - return builder.ToImmutable(); - } - - private static void ProvideCompletions(EmbeddedCompletionContext context, SyntaxToken? parentOpt) - { - foreach (var parameterSymbol in context.Tree.RouteParameters) - { - context.AddIfMissing(parameterSymbol.Key, suffix: null, description: "(Route parameter)", WellKnownTags.Parameter, parentOpt); - } - } - - private (RoutePatternNode parent, RoutePatternToken Token)? FindToken(RoutePatternNode parent, VirtualChar ch) - { - foreach (var child in parent) - { - if (child.IsNode) - { - var result = FindToken(child.Node, ch); - if (result != null) - { - return result; - } - } - else - { - if (child.Token.VirtualChars.Contains(ch)) - { - return (parent, child.Token); - } - } - } - - return null; - } - - private readonly struct RoutePatternItem - { - public readonly string DisplayText; - public readonly string InlineDescription; - public readonly string FullDescription; - public readonly string Glyph; - public readonly CompletionChange Change; - - public RoutePatternItem( - string displayText, string inlineDescription, string fullDescription, string glyph, CompletionChange change) - { - DisplayText = displayText; - InlineDescription = inlineDescription; - FullDescription = fullDescription; - Glyph = glyph; - Change = change; - } - } - - private readonly struct EmbeddedCompletionContext - { - private readonly CompletionContext _context; - private readonly HashSet<string> _names = new(StringComparer.OrdinalIgnoreCase); - - public readonly RoutePatternTree Tree; - public readonly WellKnownTypes WellKnownTypes; - public readonly CancellationToken CancellationToken; - public readonly int Position; - public readonly CompletionTrigger Trigger; - public readonly List<RoutePatternItem> Items = new(); - public readonly CompletionListSpanContainer CompletionListSpan = new(); - - internal class CompletionListSpanContainer - { - public TextSpan? Value { get; set; } - } - - public EmbeddedCompletionContext( - CompletionContext context, - RoutePatternTree tree, - WellKnownTypes wellKnownTypes) - { - _context = context; - Tree = tree; - WellKnownTypes = wellKnownTypes; - Position = _context.Position; - Trigger = _context.Trigger; - CancellationToken = _context.CancellationToken; - } - - public void AddUsedParameterName(string name) - { - _names.Add(name); - } - - public void AddIfMissing( - string displayText, string suffix, string description, string glyph, - SyntaxToken? parentOpt, int? positionOffset = null, string insertionText = null) - { - var replacementStart = parentOpt != null - ? parentOpt.Value.GetLocation().SourceSpan.Start - : Position; - var replacementEnd = parentOpt != null - ? parentOpt.Value.GetLocation().SourceSpan.End - : Position; - - var replacementSpan = TextSpan.FromBounds(replacementStart, replacementEnd); - var newPosition = replacementStart + positionOffset; - - insertionText ??= displayText; - - AddIfMissing(new RoutePatternItem( - displayText, suffix, description, glyph, - CompletionChange.Create( - new TextChange(replacementSpan, insertionText), - newPosition))); - } - - public void AddIfMissing(RoutePatternItem item) - { - if (_names.Add(item.DisplayText)) - { - Items.Add(item); - } - } - - public static string EscapeText(string text, SyntaxToken token) - { - // This function is called when Completion needs to escape something its going to - // insert into the user's string token. This means that we only have to escape - // things that completion could insert. In this case, the only regex character - // that is relevant is the \ character, and it's only relevant if we insert into - // a normal string and not a verbatim string. There are no other regex characters - // that completion will produce that need any escaping. - Debug.Assert(token.IsKind(SyntaxKind.StringLiteralToken)); - return token.IsVerbatimStringLiteral() - ? text - : text.Replace(@"\", @"\\"); - } - } -} diff --git a/src/Framework/AspNetCoreAnalyzers/test/RouteEmbeddedLanguage/FrameworkParametersCompletionProviderTests.cs b/src/Framework/AspNetCoreAnalyzers/test/RouteEmbeddedLanguage/FrameworkParametersCompletionProviderTests.cs deleted file mode 100644 index 3812915e67..0000000000 --- a/src/Framework/AspNetCoreAnalyzers/test/RouteEmbeddedLanguage/FrameworkParametersCompletionProviderTests.cs +++ /dev/null @@ -1,857 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO.Pipelines; -using System.Security.Claims; -using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure; -using Microsoft.AspNetCore.Http; -using Microsoft.CodeAnalysis.Completion; - -namespace Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage; - -public partial class FrameworkParametersCompletionProviderTests -{ - private TestDiagnosticAnalyzerRunner Runner { get; } = new(new RoutePatternAnalyzer()); - - [Fact] - public async Task Insertion_Space_Int_EndpointMapGet_HasDelegate_ReturnRouteParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", (int $$ - } -} -"); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - - var change = await result.Service.GetChangeAsync(result.Document, result.Completions.ItemsList[0]); - Assert.Equal("id", change.TextChange.NewText); - Assert.Equal(result.CompletionListSpan, change.TextChange.Span); - } - - [Fact] - public async Task Insertion_Space_DateTime_EndpointMapGet_HasDelegate_ReturnRouteParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", (DateTime $$ - } -} -"); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - - var change = await result.Service.GetChangeAsync(result.Document, result.Completions.ItemsList[0]); - Assert.Equal("id", change.TextChange.NewText); - Assert.Equal(result.CompletionListSpan, change.TextChange.Span); - } - - [Fact] - public async Task Insertion_Space_NullableInt_CloseParen_EndpointMapGet_HasDelegate_ReturnRouteParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", (int? $$) - } -} -"); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - - var change = await result.Service.GetChangeAsync(result.Document, result.Completions.ItemsList[0]); - Assert.Equal("id", change.TextChange.NewText); - Assert.Equal(result.CompletionListSpan, change.TextChange.Span); - } - - [Fact] - public async Task Insertion_Space_NullableInt_EndpointMapGet_HasDelegate_ReturnRouteParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", (int? $$ - } -} -"); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - - var change = await result.Service.GetChangeAsync(result.Document, result.Completions.ItemsList[0]); - Assert.Equal("id", change.TextChange.NewText); - Assert.Equal(result.CompletionListSpan, change.TextChange.Span); - } - - [Fact] - public async Task Insertion_Space_OutInt_EndpointMapGet_HasDelegate_ReturnRouteParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", (out int $$ - } -} -"); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - - var change = await result.Service.GetChangeAsync(result.Document, result.Completions.ItemsList[0]); - Assert.Equal("id", change.TextChange.NewText); - Assert.Equal(result.CompletionListSpan, change.TextChange.Span); - } - - [Fact] - public async Task Insertion_Space_Generic_EndpointMapGet_HasDelegate_ReturnRouteParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", (Nullable<int> $$ - } -} -"); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - - var change = await result.Service.GetChangeAsync(result.Document, result.Completions.ItemsList[0]); - Assert.Equal("id", change.TextChange.NewText); - Assert.Equal(result.CompletionListSpan, change.TextChange.Span); - } - - [Fact] - public async Task Invoke_Space_Generic_EndpointMapGet_HasDelegate_HasText_ReturnRouteParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", (int [|i|]$$ - } -} -", CompletionTrigger.Invoke); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - - var change = await result.Service.GetChangeAsync(result.Document, result.Completions.ItemsList[0]); - Assert.Equal("id", change.TextChange.NewText); - Assert.Equal(result.CompletionListSpan, change.TextChange.Span); - } - - [Fact] - public async Task Invoke_Space_Generic_EndpointMapGet_HasDelegate_InText_ReturnRouteParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", (int [|i$$d|] - } -} -", CompletionTrigger.Invoke); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - - var change = await result.Service.GetChangeAsync(result.Document, result.Completions.ItemsList[0]); - Assert.Equal("id", change.TextChange.NewText); - Assert.Equal(result.CompletionListSpan, change.TextChange.Span); - } - - [Fact] - public async Task Invoke_Space_Generic_EndpointMapGet_HasCompleteDelegate_InText_ReturnRouteParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{ids}"", (int [|i$$d|]) => {}); - } -} -", CompletionTrigger.Invoke); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("ids", i.DisplayText)); - - var change = await result.Service.GetChangeAsync(result.Document, result.Completions.ItemsList[0]); - Assert.Equal("ids", change.TextChange.NewText); - Assert.Equal(result.CompletionListSpan, change.TextChange.Span); - } - - [Fact] - public async Task Insertion_FirstArgument_SpaceAfterIdentifer_EndpointMapGet_HasDelegate_NoItems() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", (int i $$ - } -} -"); - - // Assert - Assert.Empty(result.Completions.ItemsList); - } - - [Fact] - public async Task Insertion_SecondArgument_SpaceAfterIdentifer_EndpointMapGet_HasDelegate_NoItems() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", (int o, string i $$ - } -} -"); - - // Assert - Assert.Empty(result.Completions.ItemsList); - } - - [Fact] - public async Task Insertion_Space_MultipleArgs_EndpointMapGet_HasDelegate_ReturnRouteParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.IO.Pipelines; -using System.Security.Claims; -using System.Threading; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", (HttpContext context, int $$ - } -} -"); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - } - - [Fact] - public async Task Insertion_Space_MultipleArgs_ParameterAlreadyUsed_EndpointMapGet_HasDelegate_NoItems() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.IO.Pipelines; -using System.Security.Claims; -using System.Threading; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", (string id, int $$ - } -} -"); - - // Assert - Assert.Empty(result.Completions.ItemsList); - } - - [Fact] - public async Task Insertion_Space_MultipleArgs_ParameterAlreadyUsed_EndpointMapGet_HasCompleteDelegate_NoItems() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.IO.Pipelines; -using System.Security.Claims; -using System.Threading; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", (string id, int $$) => { }); - } -} -"); - - // Assert - Assert.Empty(result.Completions.ItemsList); - } - - [Fact] - public async Task Insertion_Space_MultipleArgs_ParameterAlreadyUsed_DifferentCase_EndpointMapGet_HasCompleteDelegate_NoItems() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.IO.Pipelines; -using System.Security.Claims; -using System.Threading; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{ID}"", (string id, int $$) => { }); - } -} -"); - - // Assert - Assert.Empty(result.Completions.ItemsList); - } - - [Theory] - [InlineData("HttpContext")] - [InlineData("CancellationToken")] - [InlineData("HttpRequest")] - [InlineData("HttpResponse")] - [InlineData("ClaimsPrincipal")] - [InlineData("IFormFileCollection")] - [InlineData("IFormFile")] - [InlineData("Stream")] - [InlineData("PipeReader")] - public async Task Insertion_Space_SpecialType_EndpointMapGet_HasDelegate_NoItems(string parameterType) - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.IO.Pipelines; -using System.Security.Claims; -using System.Threading; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", (" + parameterType + @" $$ - } -} -"); - - // Assert - Assert.Empty(result.Completions.ItemsList); - } - - [Fact] - public async Task Insertion_Space_EndpointMapGet_HasMethod_NoItems() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", ExecuteGet $$); - } - - static string ExecuteGet(string id) - { - return """"; - } -} -"); - - // Assert - Assert.Empty(result.Completions.ItemsList); - } - - [Fact] - public async Task Insertion_Space_EndpointMapGet_HasMethod_NamedParameters_ReturnDelegateParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(pattern: @""{id}"", endpoints: null, handler: (string blah, int $$) - } -} -"); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - } - - [Theory] - [InlineData("AsParameters")] - [InlineData("FromQuery")] - [InlineData("FromForm")] - [InlineData("FromHeader")] - [InlineData("FromServices")] - public async Task Insertion_Space_EndpointMapGet_AsParameters_NoItem(string attributeName) - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", ([" + attributeName + @"] int $$) => {}); - } -} -"); - - // Assert - Assert.Empty(result.Completions.ItemsList); - } - - [Fact] - public async Task Insertion_Space_EndpointMapGet_UnknownAttribute_ReturnItems() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", ([PurpleMonkeyDishwasher] int $$) => {}); - } -} -"); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - } - - [Fact] - public async Task Insertion_Space_EndpointMapGet_NullDelegate_NoResults() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", null $$ - } -} -"); - - // Assert - Assert.Empty(result.Completions.ItemsList); - } - - [Fact] - public async Task Insertion_Space_EndpointMapGet_Incomplete_NoResults() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; - -class Program -{ - static void Main() - { - EndpointRouteBuilderExtensions.MapGet(null, @""{id}"", $$ - } -} -"); - - // Assert - var item = result.Completions.ItemsList.FirstOrDefault(i => i.DisplayText == "id"); - Assert.Null(item); - } - - [Fact] - public async Task Insertion_Space_CustomMapGet_ReturnDelegateParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Routing; - -class Program -{ - static void Main() - { - MapCustomThing(null, @""{id}"", (string $$) => ""); - } - - static void MapCustomThing(IEndpointRouteBuilder endpoints, [StringSyntax(""Route"")] string pattern, Delegate delegate) - { - } -} -"); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - } - - [Fact] - public async Task Insertion_Space_CustomMapGet_NoRouteSyntax_NoItems() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" -using System; -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Routing; - -class Program -{ - static void Main() - { - MapCustomThing(null, @""{id}"", (string $$) => ""); - } - - static void MapCustomThing(IEndpointRouteBuilder endpoints, string pattern, Delegate delegate) - { - } -} -"); - - // Assert - Assert.Empty(result.Completions.ItemsList); - } - - [Fact] - public async Task Insertion_Space_ControllerAction_HasParameter_ReturnActionParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" - using System; - using System.Diagnostics.CodeAnalysis; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Mvc; - - class Program - { - static void Main() - { - } - } - - public class TestController - { - [HttpGet(@""{id}"")] - public object TestAction(int $$) - { - return null; - } - } - "); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - } - - [Fact] - public async Task Insertion_Space_ControllerAction_HasParameter_Incomplete_ReturnActionParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" - using System; - using System.Diagnostics.CodeAnalysis; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Mvc; - - class Program - { - static void Main() - { - } - } - - public class TestController - { - [HttpGet(@""{id}"")] - public object TestAction(int $$ - } - "); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - } - - [Fact] - public async Task Invoke_ControllerAction_HasParameter_Incomplete_ReturnActionParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" - using System; - using System.Diagnostics.CodeAnalysis; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Mvc; - - class Program - { - static void Main() - { - } - } - - public class TestController - { - [HttpGet(@""{id}"")] - public object TestAction(int [|i|]$$ - } - ", CompletionTrigger.Invoke); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - - var change = await result.Service.GetChangeAsync(result.Document, result.Completions.ItemsList[0]); - Assert.Equal("id", change.TextChange.NewText); - Assert.Equal(result.CompletionListSpan, change.TextChange.Span); - } - - [Fact] - public async Task Insertion_ControllerAction_HasParameter_Incomplete_NoItems() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" - using System; - using System.Diagnostics.CodeAnalysis; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Mvc; - - class Program - { - static void Main() - { - } - } - - public class TestController - { - [HttpGet(@""{id}"")] - public object TestAction(int i $$ - } - "); - - // Assert - Assert.Empty(result.Completions.ItemsList); - } - - [Fact] - public async Task Insertion_Space_ControllerAction_HasParameter_BeforeComma_ReturnActionParameterItem() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" - using System; - using System.Diagnostics.CodeAnalysis; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Mvc; - - class Program - { - static void Main() - { - } - } - - public class TestController - { - [HttpGet(@""{id}"")] - public object TestAction(int $$, string blah) - { - return null; - } - } - "); - - // Assert - Assert.Collection( - result.Completions.ItemsList, - i => Assert.Equal("id", i.DisplayText)); - } - - [Fact] - public async Task Insertion_Space_NonControllerAction_HasParameter_NoItems() - { - // Arrange & Act - var result = await GetCompletionsAndServiceAsync(@" - using System; - using System.Diagnostics.CodeAnalysis; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Mvc; - - class Program - { - static void Main() - { - } - } - - public class TestController - { - [HttpGet(@""{id}"")] - internal object TestAction(int $$) - { - return null; - } - } - "); - - // Assert - Assert.Empty(result.Completions.ItemsList); - } - - private Task<CompletionResult> GetCompletionsAndServiceAsync(string source, CompletionTrigger? completionTrigger = null) - { - return CompletionTestHelpers.GetCompletionsAndServiceAsync(Runner, source, completionTrigger); - } -} diff --git a/src/Framework/AspNetCoreAnalyzers/test/RouteEmbeddedLanguage/RoutePatternParserTests.cs b/src/Framework/AspNetCoreAnalyzers/test/RouteEmbeddedLanguage/RoutePatternParserTests.cs index 3bbd8a8a8d..db5fe029e8 100644 --- a/src/Framework/AspNetCoreAnalyzers/test/RouteEmbeddedLanguage/RoutePatternParserTests.cs +++ b/src/Framework/AspNetCoreAnalyzers/test/RouteEmbeddedLanguage/RoutePatternParserTests.cs @@ -80,7 +80,7 @@ public partial class RoutePatternParserTests _outputHelper.WriteLine(actual); if (expected != null) { - Assert.Equal(expected.Replace("\"", DoubleQuoteEscaping), actual); + Assert.Equal(expected.Replace("\"", DoubleQuoteEscaping), actual, ignoreLineEndingDifferences: true); } return tree; |