Skip to content

Commit 5b0314c

Browse files
authored
Do not show completions inside comments (#974)
1 parent 24500c3 commit 5b0314c

File tree

3 files changed

+100
-3
lines changed

3 files changed

+100
-3
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
using System;
4+
using Bicep.Core.Syntax;
5+
6+
namespace Bicep.Core.Navigation
7+
{
8+
public static class SyntaxTriviaExtensions
9+
{
10+
public static SyntaxTrivia? TryFindMostSpecificTriviaInclusive(this SyntaxBase root, int offset, Func<SyntaxTrivia, bool> predicate) =>
11+
TryFindMostSpecificTriviaInternal(root, offset, predicate, inclusive: true);
12+
13+
public static SyntaxTrivia? TryFindMostSpecificTriviaExclusive(this SyntaxBase root, int offset, Func<SyntaxTrivia, bool> predicate) =>
14+
TryFindMostSpecificTriviaInternal(root, offset, predicate, inclusive: false);
15+
16+
private static SyntaxTrivia? TryFindMostSpecificTriviaInternal(SyntaxBase root, int offset, Func<SyntaxTrivia, bool> predicate, bool inclusive)
17+
{
18+
var visitor = new NavigationSearchVisitor(offset, predicate, inclusive);
19+
visitor.Visit(root);
20+
21+
return visitor.Result;
22+
}
23+
24+
private sealed class NavigationSearchVisitor : SyntaxVisitor
25+
{
26+
private readonly int offset;
27+
private readonly Func<SyntaxTrivia, bool> predicate;
28+
private readonly bool inclusive;
29+
30+
public NavigationSearchVisitor(int offset, Func<SyntaxTrivia, bool> predicate, bool inclusive)
31+
{
32+
this.offset = offset;
33+
this.predicate = predicate;
34+
this.inclusive = inclusive;
35+
}
36+
37+
public SyntaxTrivia? Result { get; private set; }
38+
39+
public override void VisitSyntaxTrivia(SyntaxTrivia node)
40+
{
41+
// check if offset is inside the node's span
42+
if (CheckNodeContainsOffset(node))
43+
{
44+
// the node span contains the offset
45+
// check the predicate
46+
if (this.predicate(node))
47+
{
48+
// store the potential result
49+
this.Result = node;
50+
}
51+
}
52+
}
53+
54+
private bool CheckNodeContainsOffset(SyntaxTrivia node) => this.inclusive
55+
? node.Span.ContainsInclusive(offset)
56+
: node.Span.Contains(offset);
57+
}
58+
}
59+
}

src/Bicep.LangServer.UnitTests/BicepCompletionProviderTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,28 @@ public void ParameterTypeContextShouldReturnDeclarationTypeCompletions()
369369
});
370370
}
371371

372+
[DataTestMethod]
373+
[DataRow("// |")]
374+
[DataRow("/* |")]
375+
[DataRow("param foo // |")]
376+
[DataRow("param foo /* |")]
377+
[DataRow("param /*| */ foo")]
378+
[DataRow(@"/*
379+
*
380+
* |
381+
*/")]
382+
public void CommentShouldNotGiveAnyCompletions(string codeFragment)
383+
{
384+
var grouping = SyntaxFactory.CreateFromText(codeFragment);
385+
var compilation = new Compilation(TestResourceTypeProvider.Create(), grouping);
386+
var provider = new BicepCompletionProvider();
387+
388+
var offset = codeFragment.IndexOf('|');
389+
390+
var completions = provider.GetFilteredCompletions(compilation, BicepCompletionContext.Create(grouping.EntryPoint, offset));
391+
392+
completions.Should().BeEmpty();
393+
}
372394
private static void AssertExpectedDeclarationTypeCompletions(List<CompletionItem> completions)
373395
{
374396
completions.Should().SatisfyRespectively(

src/Bicep.LangServer/Completions/BicepCompletionContext.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ public static BicepCompletionContext Create(SyntaxTree syntaxTree, int offset)
7474
// this indicates a bug
7575
throw new ArgumentException($"The specified offset {offset} is outside the span of the specified {nameof(ProgramSyntax)} node.");
7676
}
77+
78+
// the check at the beginning guarantees we have at least 1 node
79+
var replacementRange = GetReplacementRange(syntaxTree, matchingNodes[^1], offset);
80+
81+
var matchingTriviaType = FindTriviaMatchingOffset(syntaxTree.ProgramSyntax, offset)?.Type;
82+
if (matchingTriviaType is not null && (matchingTriviaType == SyntaxTriviaType.MultiLineComment || matchingTriviaType == SyntaxTriviaType.SingleLineComment)) {
83+
//we're in a comment, no hints here
84+
return new BicepCompletionContext(BicepCompletionContextKind.None, replacementRange, null, null, null, null, null, null, null);
85+
}
7786

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

105-
// the check at the beginning guarantees we have at least 1 node
106-
var replacementRange = GetReplacementRange(syntaxTree, matchingNodes[^1], offset);
107-
108114
return new BicepCompletionContext(kind, replacementRange, declarationInfo.node, objectInfo.node, propertyInfo.node, arrayInfo.node, propertyAccessInfo.node, arrayAccessInfo.node, targetScopeInfo.node);
109115
}
110116

@@ -133,6 +139,16 @@ private static List<SyntaxBase> FindNodesMatchingOffset(ProgramSyntax syntax, in
133139
return nodes;
134140
}
135141

142+
/// <summary>
143+
/// Returnes trivia which span contains the specified offset.
144+
/// </summary>
145+
/// <param name="syntax">The program node</param>
146+
/// <param name="offset">The offset</param>
147+
private static SyntaxTrivia? FindTriviaMatchingOffset(ProgramSyntax syntax, int offset)
148+
{
149+
return syntax.TryFindMostSpecificTriviaInclusive(offset, current => true);
150+
}
151+
136152
private static BicepCompletionContextKind ConvertFlag(bool value, BicepCompletionContextKind flag) => value ? flag : BicepCompletionContextKind.None;
137153

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

0 commit comments

Comments
 (0)