Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions private/buf/buflsp/completion_cel.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,12 @@ func celMacroCompletionItems(celEnv *cel.Env) []protocol.CompletionItem {
continue
}
seen[name] = true
// Skip macros whose function name is an operator display symbol.
// The "in" membership operator is registered as a CEL macro but is
// an infix binary operator ("value in list"), not a callable function.
if _, ok := operators.Find(name); ok {
continue
}

items = append(items, protocol.CompletionItem{
Label: name,
Expand Down Expand Up @@ -813,9 +819,15 @@ func celKeywordCompletionItems(celEnv *cel.Env, expectedType *types.Type) []prot
// celIsOperatorOrInternal returns true if name represents an operator or internal
// function that should not appear as a user-visible completion item.
func celIsOperatorOrInternal(name string) bool {
// Check internal operator names (e.g. "@in", "_&&_").
if _, ok := operators.FindReverse(name); ok {
return true
}
// Check operator display names (e.g. "in"). Some CEL environments register
// the in operator under its display name as well as its internal "@in" name.
if _, ok := operators.Find(name); ok {
return true
}
return strings.HasPrefix(name, "@") || strings.HasPrefix(name, "_")
}

Expand Down
10 changes: 10 additions & 0 deletions private/buf/buflsp/completion_cel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func TestCELCompletion(t *testing.T) {
// 206: ` expression: "this.items[0]."` (IndexAccessHolder — indexed into list, yields element fields)
// 210: ` expression: "this.locations[\"key\"]."` (IndexAccessHolder — indexed into map, yields value fields)
// 222: ` expression: "this.items.filter(item, item.zip_code > 0).all(addr, addr."` (ChainedComprehensionHolder)
// 232: ` expression: "in"` (InOperatorHolder — "in" is an operator, not a function)
tests := []struct {
name string
line uint32
Expand Down Expand Up @@ -450,6 +451,15 @@ func TestCELCompletion(t *testing.T) {
},
expectedNotContains: []string{"size", "all", "true", "false", "null"},
},
{
// Cursor at closing `"` of `"in"` — prefix "in".
// `in` is a CEL binary membership operator ("value in list"), not a
// callable function. It must NOT appear as a function completion "in()".
name: "in_operator_not_function_completion",
line: 232,
character: 19, // closing `"` after `in`
expectedNotContains: []string{"in"},
},
}

for _, tt := range tests {
Expand Down
10 changes: 10 additions & 0 deletions private/buf/buflsp/testdata/hover/cel_completion.proto
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,13 @@ message ChainedComprehensionHolder {
expression: "this.items.filter(item, item.zip_code > 0).all(addr, addr."
};
}

// InOperatorHolder verifies that the "in" membership operator does not appear
// as a callable function completion ("in()") when typed as a prefix.
// "in" is a binary infix operator ("value in list"), not a function call.
message InOperatorHolder {
option (buf.validate.message).cel = {
id: "in.operator"
expression: "in"
};
}