Skip to content

Commit e0be56c

Browse files
Add LSP CEL hovers for remaining extensions (#4386)
1 parent e6a261d commit e0be56c

4 files changed

Lines changed: 82 additions & 7 deletions

File tree

private/buf/buflsp/cel.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func celIsIdentChar(c byte) bool {
3535
func isCELKeyword(name string) bool {
3636
switch name {
3737
case "true", "false", "null", // literals
38-
"this": // special identifier
38+
"this", "now": // protovalidate special variables
3939
return true
4040
}
4141
return false

private/buf/buflsp/hover_cel.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,8 +557,11 @@ func formatCELHoverContent(info *celHoverInfo, celEnv *cel.Env) string {
557557
func celDocumentationLinks(kind celHoverKind, name string) string {
558558
switch kind {
559559
case celHoverKeyword:
560-
if name == "this" {
560+
switch name {
561+
case "this":
561562
return celProtovalidateLink("this")
563+
case "now":
564+
return celProtovalidateLink("now")
562565
}
563566
case celHoverOperator:
564567
switch name {
@@ -642,6 +645,14 @@ func celDocumentationLinks(kind celHoverKind, name string) string {
642645
return celProtovalidateLink("isuriref")
643646
case "isHostAndPort":
644647
return celProtovalidateLink("ishostandport")
648+
// Protovalidate double extension functions
649+
case "isNan":
650+
return celProtovalidateLink("isnan")
651+
case "isInf":
652+
return celProtovalidateLink("isinf")
653+
// Protovalidate list extension function
654+
case "unique":
655+
return celProtovalidateLink("unique")
645656
}
646657
case celHoverType:
647658
switch name {
@@ -797,10 +808,13 @@ func getCELTypeString(t *types.Type) string {
797808
func getCELKeywordDocs(keyword string) string {
798809
// Note: true, false, and null are ConstExpr nodes in the CEL AST (not IdentExpr),
799810
// so only "this" can reach this function via celHoverKeyword.
800-
if keyword == "this" {
811+
switch keyword {
812+
case "this":
801813
return "**Special variable**\n\nRefers to the current message or field being validated.\n\n" +
802814
"In field-level rules, `this` refers to the field value.\n" +
803815
"In message-level rules, `this` refers to the entire message."
816+
case "now":
817+
return "**Special variable**\n\nThe current timestamp at the time of validation.\n\n**Type**: `google.protobuf.Timestamp`"
804818
}
805819
return ""
806820
}

private/buf/buflsp/hover_cel_test.go

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -558,32 +558,70 @@ func TestCELHover(t *testing.T) {
558558
expected: "**Operator**: `?`\n\nThe ternary operator tests a boolean predicate and returns the left-hand side (truthy) expression if true, or the right-hand side (falsy) expression if false\n\n**Overloads**:\n- `bool ? <T> : <T> -> <T>`\n\n[CEL by Example](https://celbyexample.com/ternary/)",
559559
},
560560

561+
// Protovalidate special variable: now
562+
// Line 372 (0-indexed): ` expression: "this < now"`
563+
// ^--- now at column 24
564+
{
565+
name: "variable: now",
566+
line: 372,
567+
char: 24,
568+
expected: "**Special variable**\n\nThe current timestamp at the time of validation.\n\n**Type**: `google.protobuf.Timestamp`\n\n[Protovalidate CEL Extensions](https://protovalidate.com/reference/cel_extensions/#now)",
569+
},
570+
571+
// Protovalidate double extension functions
572+
// Line 378 (0-indexed): ` expression: "this.isNan()"`
573+
// ^--- isNan at column 22
574+
{
575+
name: "function: isNan",
576+
line: 378,
577+
char: 22,
578+
expected: "`isNan`\n\n**Overloads**:\n- `double.isNan() -> bool`\n\n[Protovalidate CEL Extensions](https://protovalidate.com/reference/cel_extensions/#isnan)",
579+
},
580+
// Line 383 (0-indexed): ` expression: "this.isInf()"`
581+
// ^--- isInf at column 22
582+
{
583+
name: "function: isInf",
584+
line: 383,
585+
char: 22,
586+
expected: "`isInf`\n\n**Overloads**:\n- `double.isInf() -> bool`\n- `double.isInf(int) -> bool`\n\n[Protovalidate CEL Extensions](https://protovalidate.com/reference/cel_extensions/#isinf)",
587+
},
588+
589+
// Protovalidate list extension function
590+
// Line 389 (0-indexed): ` expression: "this.unique()"`
591+
// ^--- unique at column 22
592+
{
593+
name: "function: unique",
594+
line: 389,
595+
char: 22,
596+
expected: "`unique`\n\n**Overloads**:\n- `list(bool).unique() -> bool`\n- `list(int).unique() -> bool`\n- `list(uint).unique() -> bool`\n- `list(double).unique() -> bool`\n- `list(string).unique() -> bool`\n- `list(bytes).unique() -> bool`\n\n[Protovalidate CEL Extensions](https://protovalidate.com/reference/cel_extensions/#unique)",
597+
},
598+
561599
// Message-level CEL rules
562600
{
563601
name: "message-level: this",
564-
line: 377,
602+
line: 400,
565603
char: 17,
566604
expected: "**Special variable**\n\nRefers to the current message or field being validated.\n\nIn field-level rules, `this` refers to the field value.\nIn message-level rules, `this` refers to the entire message.\n\n[Protovalidate CEL Extensions](https://protovalidate.com/reference/cel_extensions/#this)",
567605
},
568606
{
569607
name: "message-level: &&",
570-
line: 377,
608+
line: 400,
571609
char: 33,
572610
expected: "**Operator**: `&&`\n\nlogically AND two boolean values. Errors and unknown values\nare valid inputs and will not halt evaluation.\n\n**Overloads**:\n- `bool && bool -> bool`\n\n[CEL by Example](https://celbyexample.com/logical-operators/#and)",
573611
},
574612
{
575613
// `this.name` in the message-level expression; hovering over `name` should
576614
// resolve to CELMessageTest.name (string, field 1).
577615
name: "message-level: field access name",
578-
line: 377,
616+
line: 400,
579617
char: 22, // `n` of `name` in `this.name != ''`
580618
expected: "**Field**: `name`\n\n**Proto Field**: `test.cel.v1.CELMessageTest.name`\n\n**Field Number**: 1\n\n**Proto Type**: `string`\n\nencoded (hex): `0A` (1 byte)",
581619
},
582620
{
583621
// `this.value` in the message-level expression; hovering over `value` should
584622
// resolve to CELMessageTest.value (int32, field 2).
585623
name: "message-level: field access value",
586-
line: 377,
624+
line: 400,
587625
char: 41, // `v` of `value` in `this.value > 0`
588626
expected: "**Field**: `value`\n\n**Proto Field**: `test.cel.v1.CELMessageTest.value`\n\n**Field Number**: 2\n\n**Proto Type**: `int32`\n\nencoded (hex): `10` (1 byte)",
589627
},

private/buf/buflsp/testdata/hover/cel_comprehensive.proto

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,29 @@ message CELTest {
366366
id: "test.escape"
367367
expression: "true\n? this : false"
368368
}];
369+
370+
// Protovalidate `now` variable (timestamp comparison)
371+
google.protobuf.Timestamp test_now = 205 [(buf.validate.field).cel = {
372+
id: "test.now"
373+
expression: "this < now"
374+
}];
375+
376+
// Protovalidate double extension functions
377+
double test_isnan = 206 [(buf.validate.field).cel = {
378+
id: "test.isnan"
379+
expression: "this.isNan()"
380+
}];
381+
382+
double test_isinf = 207 [(buf.validate.field).cel = {
383+
id: "test.isinf"
384+
expression: "this.isInf()"
385+
}];
386+
387+
// Protovalidate list extension function
388+
repeated double test_unique = 208 [(buf.validate.field).cel = {
389+
id: "test.unique"
390+
expression: "this.unique()"
391+
}];
369392
}
370393

371394
// Message-level CEL validation test

0 commit comments

Comments
 (0)