in src/Bicep.LangServer/Completions/BicepCompletionContext.cs [772:1005]
private static bool IsParameterDefaultValueContext(List<SyntaxBase> matchingNodes, int offset) =>
// | below indicates cursor position
// param foo type = |
SyntaxMatcher.IsTailMatch<ParameterDeclarationSyntax, ParameterDefaultValueSyntax>(matchingNodes, (_, @default) => offset >= @default.AssignmentToken.GetEndPosition()) ||
// param foo type =|
SyntaxMatcher.IsTailMatch<ParameterDeclarationSyntax, ParameterDefaultValueSyntax, Token>(matchingNodes, (_, @default, token) => @default.AssignmentToken == token && offset == token.GetEndPosition()) ||
// param foo type = a|
SyntaxMatcher.IsTailMatch<ParameterDeclarationSyntax, ParameterDefaultValueSyntax, VariableAccessSyntax, IdentifierSyntax, Token>(matchingNodes, (_, _, _, _, token) => token.Type == TokenType.Identifier);
private static bool IsOutputValueContext(List<SyntaxBase> matchingNodes, int offset) =>
// | below indicates cursor position
// output foo type = |
SyntaxMatcher.IsTailMatch<OutputDeclarationSyntax>(matchingNodes, output => output.Assignment is not SkippedTriviaSyntax && offset >= output.Assignment.GetEndPosition()) ||
// output foo type =|
SyntaxMatcher.IsTailMatch<OutputDeclarationSyntax, Token>(matchingNodes, (output, token) => output.Assignment == token && token.Type == TokenType.Assignment && offset == token.GetEndPosition()) ||
// output foo type = a|
SyntaxMatcher.IsTailMatch<OutputDeclarationSyntax, VariableAccessSyntax, IdentifierSyntax, Token>(matchingNodes, (output, _, _, token) => output.Assignment is Token assignmentToken && offset > assignmentToken.GetEndPosition() && token.Type == TokenType.Identifier);
private static bool IsOutputTypeFollowerContext(List<SyntaxBase> matchingNodes, int offset) =>
// | below indicates cursor position
// output foo type |
SyntaxMatcher.IsTailMatch<OutputDeclarationSyntax>(matchingNodes, output => offset > output.Type.GetEndPosition() && offset <= output.Assignment.Span.Position);
private static bool IsAfterSpreadTokenContext(List<SyntaxBase> matchingNodes, int offset) =>
// ... |
SyntaxMatcher.IsTailMatch<SpreadExpressionSyntax>(matchingNodes, spread => spread.Expression is not SkippedTriviaSyntax && offset >= spread.Expression.GetEndPosition()) ||
// ...|
SyntaxMatcher.IsTailMatch<SpreadExpressionSyntax, Token>(matchingNodes, (spread, token) => spread.Ellipsis == token && offset == token.GetEndPosition());
private static bool IsVariableValueContext(List<SyntaxBase> matchingNodes, int offset) =>
// | below indicates cursor position
// var foo = |
SyntaxMatcher.IsTailMatch<VariableDeclarationSyntax>(matchingNodes, variable => variable.Assignment is not SkippedTriviaSyntax && offset >= variable.Assignment.GetEndPosition()) ||
// var foo =|
SyntaxMatcher.IsTailMatch<VariableDeclarationSyntax, Token>(matchingNodes, (variable, token) => variable.Assignment == token && token.Type == TokenType.Assignment && offset == token.GetEndPosition()) ||
// var foo = a|
SyntaxMatcher.IsTailMatch<VariableDeclarationSyntax, VariableAccessSyntax, IdentifierSyntax, Token>(matchingNodes, (_, _, _, token) => token.Type == TokenType.Identifier);
private static bool IsAssertValueContext(List<SyntaxBase> matchingNodes, int offset) =>
// | below indicates cursor position
// assert foo = |
SyntaxMatcher.IsTailMatch<AssertDeclarationSyntax>(matchingNodes, assert => assert.Assignment is not SkippedTriviaSyntax && offset >= assert.Assignment.GetEndPosition()) ||
// assert foo =|
SyntaxMatcher.IsTailMatch<AssertDeclarationSyntax, Token>(matchingNodes, (assert, token) => assert.Assignment == token && token.Type == TokenType.Assignment && offset == token.GetEndPosition()) ||
// assert foo = a|
SyntaxMatcher.IsTailMatch<AssertDeclarationSyntax, VariableAccessSyntax, IdentifierSyntax, Token>(matchingNodes, (assert, _, _, token) => token.Type == TokenType.Identifier && assert.Assignment is Token assignmentToken && offset > assignmentToken.GetEndPosition());
private static bool IsImportIdentifierContext(List<SyntaxBase> matchingNodes, int offset) =>
// import |
SyntaxMatcher.IsTailMatch<CompileTimeImportDeclarationSyntax>(matchingNodes, declaration => declaration.ImportExpression is SkippedTriviaSyntax &&
declaration.ImportExpression.Span.ContainsInclusive(offset) &&
declaration.FromClause is SkippedTriviaSyntax &&
declaration.FromClause.Span.Length == 0);
private static bool IsImportedSymbolListItemContext(List<SyntaxBase> matchingNodes, int offset) =>
SyntaxMatcher.IsTailMatch<ImportedSymbolsListItemSyntax, IdentifierSyntax, Token>(matchingNodes, (_, _, token) => token.Type == TokenType.Identifier) ||
SyntaxMatcher.IsTailMatch<ImportedSymbolsListSyntax, Token>(matchingNodes) ||
SyntaxMatcher.IsTailMatch<ImportedSymbolsListSyntax>(matchingNodes, list => IsBetweenNodes(offset, list.OpenBrace, list.CloseBrace));
private static bool ExpectingContextualAsKeyword(List<SyntaxBase> matchingNodes, int offset) =>
// import {} | or import * |
SyntaxMatcher.IsTailMatch<CompileTimeImportDeclarationSyntax>(matchingNodes, statement => statement.ImportExpression is SkippedTriviaSyntax importExpressionTrivia &&
statement.FromClause.Span.Length == 0 &&
importExpressionTrivia.Elements.Length == 1 &&
importExpressionTrivia.Elements[0] is Token importToken &&
importToken.Type == TokenType.Asterisk);
private static bool ExpectingContextualFromKeyword(List<SyntaxBase> matchingNodes, int offset) =>
// import {} | or import * as foo |
SyntaxMatcher.IsTailMatch<CompileTimeImportDeclarationSyntax>(matchingNodes, statement => statement.ImportExpression is not SkippedTriviaSyntax &&
statement.FromClause is SkippedTriviaSyntax &&
statement.FromClause.Span.ContainsInclusive(offset));
private static bool IsBetweenNodes(int offset, IPositionable first, IPositionable second)
=> first.Span.Length > 0 && first.IsBefore(offset) && second.IsOnOrAfter(offset);
private static bool IsImportPathContext(List<SyntaxBase> matchingNodes, int offset) =>
// import {} from |
SyntaxMatcher.IsTailMatch<CompileTimeImportFromClauseSyntax>(matchingNodes, (fromClause) => IsBetweenNodes(offset, fromClause.Keyword, fromClause.Path)) ||
// import {} from 'f|oo'
SyntaxMatcher.IsTailMatch<CompileTimeImportFromClauseSyntax, StringSyntax, Token>(matchingNodes, (fromClause, @string, _) => fromClause.Path == @string) ||
// import {} from fo|o
SyntaxMatcher.IsTailMatch<CompileTimeImportFromClauseSyntax, SkippedTriviaSyntax, Token>(matchingNodes, (fromClause, skipped, _) => fromClause.Path == skipped);
private static bool IsModulePathContext(IList<SyntaxBase> matchingNodes, int offset) =>
// module foo | =
SyntaxMatcher.IsTailMatch<ModuleDeclarationSyntax>(matchingNodes, module => IsBetweenNodes(offset, module.Name, module.Path)) ||
// module foo 'f|oo'
SyntaxMatcher.IsTailMatch<ModuleDeclarationSyntax, StringSyntax, Token>(matchingNodes, (module, @string, _) => module.Path == @string) ||
// module foo fo|o
SyntaxMatcher.IsTailMatch<ModuleDeclarationSyntax, SkippedTriviaSyntax, Token>(matchingNodes, (module, skipped, _) => module.Path == skipped);
private static bool IsResourceTypeContext(IList<SyntaxBase> matchingNodes, int offset) =>
// resource foo | =
SyntaxMatcher.IsTailMatch<ResourceDeclarationSyntax>(matchingNodes, resource => IsBetweenNodes(offset, resource.Name, resource.Type)) ||
// resource foo 'f|oo'
SyntaxMatcher.IsTailMatch<ResourceDeclarationSyntax, StringSyntax, Token>(matchingNodes, (resource, @string, _) => resource.Type == @string) ||
// resource foo fo|o
SyntaxMatcher.IsTailMatch<ResourceDeclarationSyntax, SkippedTriviaSyntax, Token>(matchingNodes, (resource, skipped, _) => resource.Type == skipped);
private static bool IsTestPathContext(IList<SyntaxBase> matchingNodes, int offset) =>
// test foo | =
SyntaxMatcher.IsTailMatch<TestDeclarationSyntax>(matchingNodes, test => IsBetweenNodes(offset, test.Name, test.Path)) ||
// test foo 'f|oo'
SyntaxMatcher.IsTailMatch<TestDeclarationSyntax, StringSyntax, Token>(matchingNodes, (test, @string, _) => test.Path == @string) ||
// test foo fo|o
SyntaxMatcher.IsTailMatch<TestDeclarationSyntax, SkippedTriviaSyntax, Token>(matchingNodes, (test, skipped, _) => test.Path == skipped);
private static bool IsUsingPathContext(IList<SyntaxBase> matchingNodes, int offset) =>
// using |
SyntaxMatcher.IsTailMatch<UsingDeclarationSyntax>(matchingNodes, usingClause => usingClause.Keyword.IsBefore(offset)) ||
// using 'f|oo'
SyntaxMatcher.IsTailMatch<UsingDeclarationSyntax, StringSyntax, Token>(matchingNodes, (@using, @string, _) => @using.Path == @string) ||
// using fo|o
SyntaxMatcher.IsTailMatch<UsingDeclarationSyntax, SkippedTriviaSyntax, Token>(matchingNodes, (@using, skipped, _) => @using.Path == skipped);
private static bool IsExtendsPathContext(IList<SyntaxBase> matchingNodes, int offset) =>
// extends |
SyntaxMatcher.IsTailMatch<ExtendsDeclarationSyntax>(matchingNodes, extendsClause => extendsClause.Keyword.IsBefore(offset)) ||
// extends 'f|oo'
SyntaxMatcher.IsTailMatch<ExtendsDeclarationSyntax, StringSyntax, Token>(matchingNodes, (@extends, @string, _) => @extends.Path == @string) ||
// extends fo|o
SyntaxMatcher.IsTailMatch<ExtendsDeclarationSyntax, SkippedTriviaSyntax, Token>(matchingNodes, (@extends, skipped, _) => @extends.Path == skipped);
private static bool IsResourceBodyContext(List<SyntaxBase> matchingNodes, int offset) =>
// resources only allow {} as the body so we don't need to worry about
// providing completions for a partially-typed identifier
SyntaxMatcher.IsTailMatch<ResourceDeclarationSyntax>(matchingNodes, resource =>
!resource.Name.Span.ContainsInclusive(offset) &&
!resource.Type.Span.ContainsInclusive(offset) &&
!resource.Assignment.Span.ContainsInclusive(offset) &&
resource.Value is SkippedTriviaSyntax && offset == resource.Value.Span.Position) ||
// cursor is after the = token
SyntaxMatcher.IsTailMatch<ResourceDeclarationSyntax, Token>(matchingNodes, (_, token) => token.Type == TokenType.Assignment && offset == token.GetEndPosition()) ||
// [for x in y: |]
SyntaxMatcher.IsTailMatch<ResourceDeclarationSyntax, ForSyntax, SkippedTriviaSyntax>(matchingNodes, (resource, @for, skipped) => resource.Value == @for && @for.Body == skipped) ||
// [for x in y: | ];
SyntaxMatcher.IsTailMatch<ResourceDeclarationSyntax, ForSyntax>(matchingNodes, (resource, @for) => resource.Value == @for) ||
// [for x in y:|]
SyntaxMatcher.IsTailMatch<ResourceDeclarationSyntax, ForSyntax, Token>(matchingNodes, (resource, @for, token) => resource.Value == @for && @for.Colon == token && token.Type == TokenType.Colon && offset == token.Span.GetEndPosition());
private static bool IsModuleBodyContext(List<SyntaxBase> matchingNodes, int offset) =>
// modules only allow {} as the body so we don't need to worry about
// providing completions for a partially-typed identifier
SyntaxMatcher.IsTailMatch<ModuleDeclarationSyntax>(matchingNodes, module =>
!module.Name.Span.ContainsInclusive(offset) &&
!module.Path.Span.ContainsInclusive(offset) &&
!module.Assignment.Span.ContainsInclusive(offset) &&
module.Value is SkippedTriviaSyntax && offset == module.Value.Span.Position) ||
// cursor is after the = token
SyntaxMatcher.IsTailMatch<ModuleDeclarationSyntax, Token>(matchingNodes, (_, token) => token.Type == TokenType.Assignment && offset == token.GetEndPosition()) ||
// [for x in y: |]
SyntaxMatcher.IsTailMatch<ModuleDeclarationSyntax, ForSyntax, SkippedTriviaSyntax>(matchingNodes, (module, @for, skipped) => module.Value == @for && @for.Body == skipped) ||
// [for x in y: | ];
SyntaxMatcher.IsTailMatch<ModuleDeclarationSyntax, ForSyntax>(matchingNodes, (module, @for) => module.Value == @for) ||
// [for x in y:|]
SyntaxMatcher.IsTailMatch<ModuleDeclarationSyntax, ForSyntax, Token>(matchingNodes, (module, @for, token) => module.Value == @for && @for.Colon == token && token.Type == TokenType.Colon && offset == token.Span.GetEndPosition());
private static bool IsTestBodyContext(List<SyntaxBase> matchingNodes, int offset) =>
// tests only allow {} as the body so we don't need to worry about
// providing completions for a partially-typed identifier
SyntaxMatcher.IsTailMatch<TestDeclarationSyntax>(matchingNodes, test =>
!test.Path.Span.ContainsInclusive(offset) &&
!test.Assignment.Span.ContainsInclusive(offset) &&
test.Value is SkippedTriviaSyntax && offset == test.Value.Span.Position) ||
// cursor is after the = token
SyntaxMatcher.IsTailMatch<TestDeclarationSyntax, Token>(matchingNodes, (_, token) => token.Type == TokenType.Assignment && offset == token.GetEndPosition());
private static bool IsDecoratorNameContext(List<SyntaxBase> matchingNodes, int offset) =>
SyntaxMatcher.IsTailMatch<DecoratorSyntax, VariableAccessSyntax, IdentifierSyntax, Token>(matchingNodes, (_, _, _, token) => token.Type == TokenType.Identifier) ||
SyntaxMatcher.IsTailMatch<DecoratorSyntax, Token>(matchingNodes, (_, token) => token.Type == TokenType.At) ||
SyntaxMatcher.IsTailMatch<DecoratorSyntax>(matchingNodes, decoratorSyntax => offset > decoratorSyntax.At.Span.Position) ||
SyntaxMatcher.IsTailMatch<DecoratorSyntax, PropertyAccessSyntax, IdentifierSyntax, Token>(matchingNodes, (_, _, _, token) => token.Type == TokenType.Identifier) ||
SyntaxMatcher.IsTailMatch<DecoratorSyntax, PropertyAccessSyntax, Token>(matchingNodes, (_, _, token) => token.Type == TokenType.Dot) ||
SyntaxMatcher.IsTailMatch<DecoratorSyntax, PropertyAccessSyntax>(matchingNodes, (_, propertyAccessSyntax) => offset > propertyAccessSyntax.Dot.Span.Position);
private static bool IsObjectTypePropertyValueContext(List<SyntaxBase> matchingNodes, int offset) =>
SyntaxMatcher.IsTailMatch<ObjectTypePropertySyntax>(matchingNodes, typePropertySyntax => typePropertySyntax.Colon is not SkippedTriviaSyntax && offset > typePropertySyntax.Colon.Span.Position) ||
SyntaxMatcher.IsTailMatch<ObjectTypePropertySyntax, TypeVariableAccessSyntax, IdentifierSyntax, Token>(matchingNodes, (_, _, _, token) => token.Type == TokenType.Identifier);
private static bool IsUnionTypeMemberContext(List<SyntaxBase> matchingNodes, int offset) =>
SyntaxMatcher.IsTailMatch<UnionTypeSyntax, Token>(matchingNodes, (_, token) => token.Type == TokenType.Pipe) ||
SyntaxMatcher.IsTailMatch<UnionTypeSyntax>(matchingNodes, union => union.Children.LastOrDefault() is SkippedTriviaSyntax) ||
SyntaxMatcher.IsTailMatch<UnionTypeSyntax, UnionTypeMemberSyntax, TypeVariableAccessSyntax, IdentifierSyntax, Token>(matchingNodes, (_, _, _, _, token) => token.Type == TokenType.Identifier);
private static IndexedSyntaxContext<FunctionCallSyntaxBase>? TryGetFunctionArgumentContext(List<SyntaxBase> matchingNodes, int offset)
{
// someFunc(|)
// abc.someFunc(|)
if (SyntaxMatcher.IsTailMatch<FunctionCallSyntaxBase, Token>(matchingNodes, (func, token) => token == func.OpenParen))
{
return new(Syntax: (FunctionCallSyntaxBase)matchingNodes[^2], ArgumentIndex: 0);
}
// someFunc(x, |)
// abc.someFunc(x, |)
if (SyntaxMatcher.IsTailMatch<FunctionCallSyntaxBase, FunctionArgumentSyntax>(matchingNodes, (func, _) => true))
{
var function = (FunctionCallSyntaxBase)matchingNodes[^2];
return new(Syntax: function, ArgumentIndex: function.Arguments.IndexOf((FunctionArgumentSyntax)matchingNodes[^1]));
}
// someFunc(x,|)
// abc.someFunc(x,|)
if (SyntaxMatcher.IsTailMatch<FunctionCallSyntaxBase, Token>(matchingNodes, (func, _) => true))
{
var function = (FunctionCallSyntaxBase)matchingNodes[^2];
var previousArg = function.Arguments.LastOrDefault(x => x.Span.Position < offset);
return new(Syntax: function, ArgumentIndex: previousArg is null ? 0 : (function.Arguments.IndexOf(previousArg) + 1));
}
// someFunc(x, 'a|bc')
// abc.someFunc(x, 'de|f')
if (SyntaxMatcher.IsTailMatch<FunctionCallSyntaxBase, FunctionArgumentSyntax, StringSyntax, Token>(matchingNodes, (_, _, _, token) => token.Type == TokenType.StringComplete))
{
var function = (FunctionCallSyntaxBase)matchingNodes[^4];
return new(Syntax: function, ArgumentIndex: function.Arguments.IndexOf((FunctionArgumentSyntax)matchingNodes[^3]));
}
// someFunc(x, ab|c)
// abc.someFunc(x, de|f)
if (SyntaxMatcher.IsTailMatch<FunctionCallSyntaxBase, FunctionArgumentSyntax, VariableAccessSyntax, IdentifierSyntax, Token>(matchingNodes, (_, _, _, _, token) => token.Type == TokenType.Identifier))
{
var function = (FunctionCallSyntaxBase)matchingNodes[^5];
return new(Syntax: function, ArgumentIndex: function.Arguments.IndexOf((FunctionArgumentSyntax)matchingNodes[^4]));
}
return null;
}