Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
Prev Previous commit
Next Next commit
source
  • Loading branch information
atkin-mc committed Feb 10, 2026
commit 9f641097304cc26c3a66f635e03d929874a612e8
4 changes: 3 additions & 1 deletion source/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ func (o *SwitchCaseExprOption) Clone() *SwitchCaseExprOption {
}
return &SwitchCaseExprOption{
Idx: o.Idx,
Def: o.Def.Clone(),
If: o.If,
By: o.By,
}
Expand All @@ -542,6 +543,7 @@ func (o *SwitchDefaultExprOption) Clone() *SwitchDefaultExprOption {
return nil
}
return &SwitchDefaultExprOption{
By: o.By,
Def: o.Def.Clone(),
By: o.By,
}
}
104 changes: 95 additions & 9 deletions source/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,12 +594,17 @@ func (f *File) findDefByPos(builder *VariableDefinitionOptionBuilder, pos Positi
if !ok {
return nil
}
switchBuilder := builder.WithSwitch()
if found := f.findSwitchExprByPos(
builder.WithSwitch(),
switchBuilder,
pos, value,
); found != nil {
return found
}
// Position is on "switch" field name or inside switch block but not in a case/default
if f.containsPos(elem, pos) {
return switchBuilder.Location()
}
case "validation":
value, ok := elem.Val.(*ast.MessageLiteralNode)
if !ok {
Expand Down Expand Up @@ -678,27 +683,92 @@ func (f *File) findEnumExprByPos(builder *EnumExprOptionBuilder, pos Position, n
}

func (f *File) findSwitchExprByPos(builder *SwitchExprOptionBuilder, pos Position, node *ast.MessageLiteralNode) *Location {
caseCount := 0
var cases []*ast.MessageLiteralNode
for _, elem := range node.Elements {
fieldName := elem.Name.Name.AsIdentifier()
switch fieldName {
case "case":
if f.containsPos(elem.Val, pos) {
return builder.WithCase(caseCount).Location()
}
caseCount++
cases = append(cases, f.getMessageListFromNode(elem.Val)...)
case "default":
if f.containsPos(elem.Val, pos) {
return builder.WithDefault().Location()
value, ok := elem.Val.(*ast.MessageLiteralNode)
if !ok {
return nil
}
if found := f.findSwitchDefaultExprByPos(
builder.WithDefault(),
pos, value,
); found != nil {
return found
}
}
}
for idx, n := range cases {
if found := f.findSwitchCaseExprByPos(
builder.WithCase(idx),
pos, n,
); found != nil {
return found
}
}

if f.containsPos(node, pos) {
return builder.Location()
}
return nil
}

func (f *File) findSwitchCaseExprByPos(builder *SwitchCaseExprOptionBuilder, pos Position, node *ast.MessageLiteralNode) *Location {
var defs []*ast.MessageLiteralNode
for _, elem := range node.Elements {
fieldName := elem.Name.Name.AsIdentifier()
switch fieldName {
case "def":
defs = append(defs, f.getMessageListFromNode(elem.Val)...)
case "if":
if f.containsPos(elem.Val, pos) {
return builder.WithIf().Location()
}
case "by":
if f.containsPos(elem.Val, pos) {
return builder.WithBy().Location()
}
}
}
for idx, n := range defs {
if found := f.findDefByPos(
builder.WithDef(idx),
pos, n,
); found != nil {
return found
}
}
return nil
}

func (f *File) findSwitchDefaultExprByPos(builder *SwitchDefaultExprOptionBuilder, pos Position, node *ast.MessageLiteralNode) *Location {
var defs []*ast.MessageLiteralNode
for _, elem := range node.Elements {
fieldName := elem.Name.Name.AsIdentifier()
switch fieldName {
case "def":
defs = append(defs, f.getMessageListFromNode(elem.Val)...)
case "by":
if f.containsPos(elem.Val, pos) {
return builder.WithBy().Location()
}
}
}
for idx, n := range defs {
if found := f.findDefByPos(
builder.WithDef(idx),
pos, n,
); found != nil {
return found
}
}
return nil
}

func (f *File) findMessageArgumentByPos(builder *ArgumentOptionBuilder, pos Position, node *ast.MessageLiteralNode) *Location {
for _, elem := range node.Elements {
fieldName := elem.Name.Name.AsIdentifier()
Expand Down Expand Up @@ -1167,12 +1237,16 @@ func (f *File) findServiceVariableByPos(builder *ServiceVariableBuilder, pos Pos
if !ok {
return nil
}
switchBuilder := builder.WithSwitch()
if found := f.findSwitchExprByPos(
builder.WithSwitch(),
switchBuilder,
pos, value,
); found != nil {
return found
}
if f.containsPos(elem, pos) {
return switchBuilder.Location()
}
case "validation":
value, ok := elem.Val.(*ast.MessageLiteralNode)
if !ok {
Expand Down Expand Up @@ -2168,9 +2242,12 @@ func (f *File) nodeInfoBySwitchExpr(node *ast.MessageLiteralNode, opt *SwitchExp
}

func (f *File) nodeInfoBySwitchCaseExpr(node *ast.MessageLiteralNode, opt *SwitchCaseExprOption) *ast.NodeInfo {
var defs []*ast.MessageLiteralNode
for _, elem := range node.Elements {
fieldName := elem.Name.Name.AsIdentifier()
switch {
case opt.Def != nil && fieldName == "def":
defs = append(defs, f.getMessageListFromNode(elem.Val)...)
case opt.If && fieldName == "if":
value, ok := elem.Val.(*ast.StringLiteralNode)
if !ok {
Expand All @@ -2185,13 +2262,19 @@ func (f *File) nodeInfoBySwitchCaseExpr(node *ast.MessageLiteralNode, opt *Switc
return f.nodeInfo(value)
}
}
if len(defs) != 0 {
return f.nodeInfoByDef(defs, opt.Def)
}
return f.nodeInfo(node)
}

func (f *File) nodeInfoBySwitchDefaultExpr(node *ast.MessageLiteralNode, opt *SwitchDefaultExprOption) *ast.NodeInfo {
var defs []*ast.MessageLiteralNode
for _, elem := range node.Elements {
fieldName := elem.Name.Name.AsIdentifier()
switch {
case opt.Def != nil && fieldName == "def":
defs = append(defs, f.getMessageListFromNode(elem.Val)...)
case opt.By && fieldName == "by":
value, ok := elem.Val.(*ast.StringLiteralNode)
if !ok {
Expand All @@ -2200,6 +2283,9 @@ func (f *File) nodeInfoBySwitchDefaultExpr(node *ast.MessageLiteralNode, opt *Sw
return f.nodeInfo(value)
}
}
if len(defs) != 0 {
return f.nodeInfoByDef(defs, opt.Def)
}
return f.nodeInfo(node)
}

Expand Down
144 changes: 144 additions & 0 deletions source/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,150 @@ func TestFile_FindLocationByPos(t *testing.T) {
},
},
},
// Switch expression option test cases (using testdata/switch.proto)
{
desc: "find def with switch (switch keyword)",
file: "switch.proto",
pos: source.Position{
Line: 22,
Col: 7,
},
expected: &source.Location{
FileName: "testdata/switch.proto",
Message: &source.Message{
Name: "GetPostResponse",
Option: &source.MessageOption{
Def: &source.VariableDefinitionOption{
Idx: 0,
Switch: &source.SwitchExprOption{},
},
},
},
},
},
{
desc: "find first case if condition",
file: "switch.proto",
pos: source.Position{
Line: 25,
Col: 16,
},
expected: &source.Location{
FileName: "testdata/switch.proto",
Message: &source.Message{
Name: "GetPostResponse",
Option: &source.MessageOption{
Def: &source.VariableDefinitionOption{
Idx: 0,
Switch: &source.SwitchExprOption{
Case: &source.SwitchCaseExprOption{
Idx: 0,
If: true,
},
},
},
},
},
},
},
{
desc: "find first case by expression",
file: "switch.proto",
pos: source.Position{
Line: 25,
Col: 36,
},
expected: &source.Location{
FileName: "testdata/switch.proto",
Message: &source.Message{
Name: "GetPostResponse",
Option: &source.MessageOption{
Def: &source.VariableDefinitionOption{
Idx: 0,
Switch: &source.SwitchExprOption{
Case: &source.SwitchCaseExprOption{
Idx: 0,
By: true,
},
},
},
},
},
},
},
{
desc: "find first case def",
file: "switch.proto",
pos: source.Position{
Line: 24,
Col: 18,
},
expected: &source.Location{
FileName: "testdata/switch.proto",
Message: &source.Message{
Name: "GetPostResponse",
Option: &source.MessageOption{
Def: &source.VariableDefinitionOption{
Idx: 0,
Switch: &source.SwitchExprOption{
Case: &source.SwitchCaseExprOption{
Idx: 0,
Def: &source.VariableDefinitionOption{Idx: 0},
},
},
},
},
},
},
},
{
desc: "find default by expression",
file: "switch.proto",
pos: source.Position{
Line: 30,
Col: 15,
},
expected: &source.Location{
FileName: "testdata/switch.proto",
Message: &source.Message{
Name: "GetPostResponse",
Option: &source.MessageOption{
Def: &source.VariableDefinitionOption{
Idx: 0,
Switch: &source.SwitchExprOption{
Default: &source.SwitchDefaultExprOption{
By: true,
},
},
},
},
},
},
},
{
desc: "find default def",
file: "switch.proto",
pos: source.Position{
Line: 29,
Col: 18,
},
expected: &source.Location{
FileName: "testdata/switch.proto",
Message: &source.Message{
Name: "GetPostResponse",
Option: &source.MessageOption{
Def: &source.VariableDefinitionOption{
Idx: 0,
Switch: &source.SwitchExprOption{
Default: &source.SwitchDefaultExprOption{
Def: &source.VariableDefinitionOption{Idx: 0},
},
},
},
},
},
},
},
}

for _, tc := range tests {
Expand Down
4 changes: 3 additions & 1 deletion source/location.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,12 +364,14 @@ type SwitchExprOption struct {

type SwitchCaseExprOption struct {
Idx int
Def *VariableDefinitionOption
If bool
By bool
}

type SwitchDefaultExprOption struct {
By bool
Def *VariableDefinitionOption
By bool
}

// Position represents source position in proto file.
Expand Down
Loading