Skip to content

Commit

Permalink
Do not show completions inside comments (#974)
Browse files Browse the repository at this point in the history
  • Loading branch information
miqm authored Nov 23, 2020
1 parent 24500c3 commit 5b0314c
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 3 deletions.
59 changes: 59 additions & 0 deletions src/Bicep.Core/Navigation/SyntaxTriviaExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Bicep.Core.Syntax;

namespace Bicep.Core.Navigation
{
public static class SyntaxTriviaExtensions
{
public static SyntaxTrivia? TryFindMostSpecificTriviaInclusive(this SyntaxBase root, int offset, Func<SyntaxTrivia, bool> predicate) =>
TryFindMostSpecificTriviaInternal(root, offset, predicate, inclusive: true);

public static SyntaxTrivia? TryFindMostSpecificTriviaExclusive(this SyntaxBase root, int offset, Func<SyntaxTrivia, bool> predicate) =>
TryFindMostSpecificTriviaInternal(root, offset, predicate, inclusive: false);

private static SyntaxTrivia? TryFindMostSpecificTriviaInternal(SyntaxBase root, int offset, Func<SyntaxTrivia, bool> predicate, bool inclusive)
{
var visitor = new NavigationSearchVisitor(offset, predicate, inclusive);
visitor.Visit(root);

return visitor.Result;
}

private sealed class NavigationSearchVisitor : SyntaxVisitor
{
private readonly int offset;
private readonly Func<SyntaxTrivia, bool> predicate;
private readonly bool inclusive;

public NavigationSearchVisitor(int offset, Func<SyntaxTrivia, bool> predicate, bool inclusive)
{
this.offset = offset;
this.predicate = predicate;
this.inclusive = inclusive;
}

public SyntaxTrivia? Result { get; private set; }

public override void VisitSyntaxTrivia(SyntaxTrivia node)
{
// check if offset is inside the node's span
if (CheckNodeContainsOffset(node))
{
// the node span contains the offset
// check the predicate
if (this.predicate(node))
{
// store the potential result
this.Result = node;
}
}
}

private bool CheckNodeContainsOffset(SyntaxTrivia node) => this.inclusive
? node.Span.ContainsInclusive(offset)
: node.Span.Contains(offset);
}
}
}
22 changes: 22 additions & 0 deletions src/Bicep.LangServer.UnitTests/BicepCompletionProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,28 @@ public void ParameterTypeContextShouldReturnDeclarationTypeCompletions()
});
}

[DataTestMethod]
[DataRow("// |")]
[DataRow("/* |")]
[DataRow("param foo // |")]
[DataRow("param foo /* |")]
[DataRow("param /*| */ foo")]
[DataRow(@"/*
*
* |
*/")]
public void CommentShouldNotGiveAnyCompletions(string codeFragment)
{
var grouping = SyntaxFactory.CreateFromText(codeFragment);
var compilation = new Compilation(TestResourceTypeProvider.Create(), grouping);
var provider = new BicepCompletionProvider();

var offset = codeFragment.IndexOf('|');

var completions = provider.GetFilteredCompletions(compilation, BicepCompletionContext.Create(grouping.EntryPoint, offset));

completions.Should().BeEmpty();
}
private static void AssertExpectedDeclarationTypeCompletions(List<CompletionItem> completions)
{
completions.Should().SatisfyRespectively(
Expand Down
22 changes: 19 additions & 3 deletions src/Bicep.LangServer/Completions/BicepCompletionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ public static BicepCompletionContext Create(SyntaxTree syntaxTree, int offset)
// this indicates a bug
throw new ArgumentException($"The specified offset {offset} is outside the span of the specified {nameof(ProgramSyntax)} node.");
}

// the check at the beginning guarantees we have at least 1 node
var replacementRange = GetReplacementRange(syntaxTree, matchingNodes[^1], offset);

var matchingTriviaType = FindTriviaMatchingOffset(syntaxTree.ProgramSyntax, offset)?.Type;
if (matchingTriviaType is not null && (matchingTriviaType == SyntaxTriviaType.MultiLineComment || matchingTriviaType == SyntaxTriviaType.SingleLineComment)) {
//we're in a comment, no hints here
return new BicepCompletionContext(BicepCompletionContextKind.None, replacementRange, null, null, null, null, null, null, null);
}

var declarationInfo = FindLastNodeOfType<INamedDeclarationSyntax, SyntaxBase>(matchingNodes);
var objectInfo = FindLastNodeOfType<ObjectSyntax, ObjectSyntax>(matchingNodes);
Expand Down Expand Up @@ -102,9 +111,6 @@ public static BicepCompletionContext Create(SyntaxTree syntaxTree, int offset)
kind |= ConvertFlag(IsInnerExpressionContext(matchingNodes), BicepCompletionContextKind.Expression);
}

// the check at the beginning guarantees we have at least 1 node
var replacementRange = GetReplacementRange(syntaxTree, matchingNodes[^1], offset);

return new BicepCompletionContext(kind, replacementRange, declarationInfo.node, objectInfo.node, propertyInfo.node, arrayInfo.node, propertyAccessInfo.node, arrayAccessInfo.node, targetScopeInfo.node);
}

Expand Down Expand Up @@ -133,6 +139,16 @@ private static List<SyntaxBase> FindNodesMatchingOffset(ProgramSyntax syntax, in
return nodes;
}

/// <summary>
/// Returnes trivia which span contains the specified offset.
/// </summary>
/// <param name="syntax">The program node</param>
/// <param name="offset">The offset</param>
private static SyntaxTrivia? FindTriviaMatchingOffset(ProgramSyntax syntax, int offset)
{
return syntax.TryFindMostSpecificTriviaInclusive(offset, current => true);
}

private static BicepCompletionContextKind ConvertFlag(bool value, BicepCompletionContextKind flag) => value ? flag : BicepCompletionContextKind.None;

private static BicepCompletionContextKind GetDeclarationTypeFlags(IList<SyntaxBase> matchingNodes, int offset)
Expand Down

0 comments on commit 5b0314c

Please sign in to comment.