in src/Bicep.LangServer/Completions/BicepCompletionContext.cs [435:504]
private static bool IsResourceTypeFollowerContext(List<SyntaxBase> matchingNodes, int offset) =>
// resource foo '...' |
// OR
// resource foo '...' | = {
SyntaxMatcher.IsTailMatch<ResourceDeclarationSyntax>(matchingNodes, resource => offset > resource.Type.GetEndPosition() && offset <= resource.Assignment.Span.Position) ||
// resource foo '...' e|
// OR
// resource foo '...' e| = {
SyntaxMatcher.IsTailMatch<ResourceDeclarationSyntax, SkippedTriviaSyntax, Token>(matchingNodes, (resource, skipped, token) => resource.Assignment == skipped && token.Type == TokenType.Identifier) ||
// resource foo '...' |=
SyntaxMatcher.IsTailMatch<ResourceDeclarationSyntax, Token>(matchingNodes, (resource, token) => resource.Assignment == token && token.Type == TokenType.Assignment && offset == token.Span.Position);
private static bool IsVariableNameFollowerContext(List<SyntaxBase> matchingNodes, int offset) =>
// var foo |
SyntaxMatcher.IsTailMatch<VariableDeclarationSyntax>(matchingNodes, variable =>
offset > variable.Name.GetEndPosition() &&
variable.Type is null &&
variable.Assignment is SkippedTriviaSyntax &&
offset <= variable.Assignment.Span.Position);
private static bool IsTargetScopeContext(List<SyntaxBase> matchingNodes, int offset) =>
SyntaxMatcher.IsTailMatch<TargetScopeSyntax>(matchingNodes, targetScope =>
!targetScope.Assignment.Span.ContainsInclusive(offset) &&
targetScope.Value is SkippedTriviaSyntax && offset == targetScope.Value.Span.Position) ||
SyntaxMatcher.IsTailMatch<TargetScopeSyntax, Token>(matchingNodes, (targetScope, token) =>
token.Type == TokenType.Assignment &&
ReferenceEquals(targetScope.Assignment, token));
private static bool IsTopLevelDeclarationStartContext(List<SyntaxBase> matchingNodes, int offset)
{
if (matchingNodes.Count == 1 && matchingNodes[0] is ProgramSyntax)
{
// the file is empty and the AST has a ProgramSyntax with 0 children and an EOF
// because we picked the left node as winner, the only matching node is the ProgramSyntax node
return true;
}
if (matchingNodes.Count >= 2 && matchingNodes[^1] is Token token)
{
// we have at least 2 matching nodes in the "stack" and the last one is a token
var node = matchingNodes[^2];
switch (node)
{
case ProgramSyntax programSyntax:
// the token at current position is inside a program node
// we're in a declaration if one of the following conditions is met:
// 1. the token is EOF
// 2. the token is a newline and we can insert at the offset
return token.Type == TokenType.EndOfFile ||
(token.Type == TokenType.NewLine && CanInsertChildNodeAtOffset(programSyntax, offset));
case SkippedTriviaSyntax _ when matchingNodes.Count >= 3:
// we are in a line that has a partial declaration keyword (for example "resour" or "modu")
// if the token at current position is an identifier, assume declaration context
// (completions will be filtered by the text that is present, so we don't have to be 100% right)
return token.Type == TokenType.Identifier && matchingNodes[^3] is ProgramSyntax;
case ITopLevelNamedDeclarationSyntax declaration:
// we are in a partially parsed declaration which only contains a keyword
// whether we are in a declaration context depends on whether our offset is within the keyword token
// (by using exclusive span containment, the cursor position at the end of a keyword token
// result counts as being outside of the declaration context)
return declaration.Name.IdentifierName.Equals(LanguageConstants.MissingName, LanguageConstants.IdentifierComparison) &&
declaration.Keyword.Span.Contains(offset);
}
}
return false;
}