private static bool IsParameterDefaultValueContext()

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;
        }