in src/Bicep.LangServer/Completions/BicepCompletionContext.cs [343:433]
private static BicepCompletionContextKind GetDeclarationTypeFlags(IList<SyntaxBase> matchingNodes, int offset)
{
// local function
bool CheckTypeIsExpected(SyntaxBase name, SyntaxBase type) => name.Span.Length > 0 && offset > name.GetEndPosition() && offset <= type.Span.Position;
bool CheckParameterResourceTypeIsExpected(ResourceTypeSyntax type) =>
// This handles the case where we have the resource keyword but no type-string for a parameter.
// Must be inside the type
type.Span.Length > 0 &&
offset >= type.Keyword.GetEndPosition() &&
(type.Type is null || type.Type is SkippedTriviaSyntax);
bool CheckOutputResourceTypeIsExpected(OutputDeclarationSyntax output) =>
// This handles the case where we have the resource keyword but no type-string for an output.
// Must be after the type (`resource` is valid for type) and
// Before the `=` if `=` is present
output.Type is ResourceTypeSyntax type &&
offset >= type.Keyword.GetEndPosition() &&
(type.Type is null || type.Type is SkippedTriviaSyntax) &&
(output.Assignment is SkippedTriviaSyntax || (output.Assignment is Token assignment && offset <= assignment.GetPosition()));
if (SyntaxMatcher.IsTailMatch<ParameterDeclarationSyntax>(matchingNodes, parameter => CheckTypeIsExpected(parameter.Name, parameter.Type)) ||
SyntaxMatcher.IsTailMatch<ParameterDeclarationSyntax, TypeVariableAccessSyntax, IdentifierSyntax, Token>(matchingNodes, (_, _, _, token) => token.Type == TokenType.Identifier))
{
// the most specific matching node is a parameter declaration
// the declaration syntax is "param <identifier> <type> ..."
// the cursor position is on the type if we have an identifier (non-zero length span) and the offset matches the type position
// OR
// we are in a token that is inside a TypeVariableAccessSyntax node, which is inside a parameter node
return BicepCompletionContextKind.ParameterType;
}
if (SyntaxMatcher.IsTailMatch<TypeDeclarationSyntax>(matchingNodes, typeDeclaration => typeDeclaration.Assignment is not SkippedTriviaSyntax && offset > typeDeclaration.Assignment.GetEndPosition() && offset <= typeDeclaration.Value.Span.Position) ||
SyntaxMatcher.IsTailMatch<TypeDeclarationSyntax, TypeVariableAccessSyntax, IdentifierSyntax, Token>(matchingNodes, (_, _, _, token) => token.Type == TokenType.Identifier))
{
// the most specific matching node is a type declaration
// the declaration syntax is "type <identifier> = <type>"
// the cursor position is on the type if we have an identifier (non-zero length span) and the offset matches the type position
// OR
// we are in a token that is inside a TypeVariableAccessSyntax node, which is inside a type declaration node
return BicepCompletionContextKind.TypeDeclarationValue;
}
if (SyntaxMatcher.IsTailMatch<ParameterDeclarationSyntax, ResourceTypeSyntax>(matchingNodes, (parameter, type) => CheckParameterResourceTypeIsExpected(type)) ||
SyntaxMatcher.IsTailMatch<ParameterDeclarationSyntax, ResourceTypeSyntax, StringSyntax, Token>(matchingNodes, (_, _, _, token) => token.Type == TokenType.StringComplete))
{
// the most specific matching node is a parameter declaration with the resource keyword
// the declaration syntax is "param <identifier> resource ..."
// the cursor position is on the resource type if we have the resource keyword but nothing else
// OR
// we are in a token that is inside a ResourceTypeSyntax node, which is inside a parameter node
return BicepCompletionContextKind.ResourceType;
}
if (SyntaxMatcher.IsTailMatch<OutputDeclarationSyntax>(matchingNodes, output => CheckTypeIsExpected(output.Name, output.Type)) ||
SyntaxMatcher.IsTailMatch<OutputDeclarationSyntax, TypeVariableAccessSyntax, IdentifierSyntax, Token>(matchingNodes, (_, _, _, token) => token.Type == TokenType.Identifier))
{
// the most specific matching node is an output declaration
// the declaration syntax is "output <identifier> <type> ..."
// the cursor position is on the type if we have an identifier (non-zero length span) and the offset matches the type position
// OR
// we are in a token that is inside a TypeVariableAccessSyntax node, which is inside an output node
return BicepCompletionContextKind.OutputType;
}
// NOTE: this logic is different between parameters and outputs because the resource type is optional for outputs.
if (SyntaxMatcher.IsTailMatch<OutputDeclarationSyntax>(matchingNodes, output => CheckOutputResourceTypeIsExpected(output)) ||
SyntaxMatcher.IsTailMatch<OutputDeclarationSyntax, ResourceTypeSyntax, StringSyntax, Token>(matchingNodes, (_, _, _, token) => token.Type == TokenType.StringComplete))
{
// the most specific matching node is an output declaration with the resource keyword
// the declaration syntax is "output <identifier> resource ..."
// the cursor position is on the resource type if we have the resource keyword but nothing else
// OR
// we are in a token that is inside a ResourceTypeSyntax node, which is inside an output node
return BicepCompletionContextKind.ResourceType;
}
if (IsResourceTypeContext(matchingNodes, offset))
{
// the most specific matching node is a resource declaration
// the declaration syntax is "resource <identifier> '<type>' ..."
// the cursor position is on the type if we have an identifier (non-zero length span) and the offset matches the type position
// OR
// we are in a token that is inside a StringSyntax node, which is inside a resource declaration
// OR
// we have an identifier in the place of a type in a resoure (this allows us to show completions when user just types virtualMachines instead of 'virtualMachines')
return BicepCompletionContextKind.ResourceType;
}
return BicepCompletionContextKind.None;
}