Something went wrong. Try again.
A Thrift language server, formatter, and linter.
lsp linter helix vim vscode thrift
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312package analyzers
import ( "testing"
"github.com/stretchr/testify/assert"
"github.com/karitham/thrift-ls/analyzertest" "github.com/karitham/thrift-ls/sema")
func Test_DuplicateCheck_Diagnostic(t *testing.T) { tests := []struct { name string content string want []analyzertest.Diag }{ { name: "distinct names and values are clean", content: `struct User {}enum Color { RED = 0, GREEN = 2,}service S { void f(1: i32 x),}`, want: nil, }, { name: "duplicate top-level definitions", content: `struct A {}struct A {}`, want: []analyzertest.Diag{ { StartLine: 1 + 1, StartCol: 7 + 1, EndLine: 1 + 1, EndCol: 8 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateDef, Message: "duplicate struct A", }, }, }, { name: "duplicate enum member names", content: `enum E { A, A,}`, want: []analyzertest.Diag{ { StartLine: 2 + 1, StartCol: 2 + 1, EndLine: 2 + 1, EndCol: 3 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateDef, Message: "duplicate member A", }, }, }, { name: "duplicate field names", content: `struct S { 1: i32 a, 2: i32 a,}`, want: []analyzertest.Diag{ { StartLine: 2 + 1, StartCol: 9 + 1, EndLine: 2 + 1, EndCol: 10 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateDef, Message: "duplicate field a", }, }, }, { name: "duplicate function names", content: `service S { void f(), void f(),}`, want: []analyzertest.Diag{ { StartLine: 2 + 1, StartCol: 7 + 1, EndLine: 2 + 1, EndCol: 8 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateDef, Message: "duplicate function f", }, }, }, { name: "duplicate argument names", content: `service S { void f(1: i32 x, 2: i32 x),}`, want: []analyzertest.Diag{ { StartLine: 1 + 1, StartCol: 26 + 1, EndLine: 1 + 1, EndCol: 27 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateDef, Message: "duplicate argument x", }, }, }, { name: "definitions of different kinds share one scope", content: `struct User {}enum User {}`, want: []analyzertest.Diag{ { StartLine: 1 + 1, StartCol: 5 + 1, EndLine: 1 + 1, EndCol: 9 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateDef, Message: "duplicate enum User", }, }, }, { name: "explicit duplicate enum values", content: `enum E { A = 1, B = 1,}`, want: []analyzertest.Diag{ { StartLine: 2 + 1, StartCol: 2 + 1, EndLine: 2 + 1, EndCol: 3 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateEnumVal, Message: "enum value 1 duplicates A", }, }, }, { name: "implicit member collides with explicit value", content: `enum E { A, B = 0,}`, want: []analyzertest.Diag{ { StartLine: 2 + 1, StartCol: 2 + 1, EndLine: 2 + 1, EndCol: 3 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateEnumVal, Message: "enum value 0 duplicates A", }, }, }, { name: "unparseable constant skips the value chain", content: `enum E { A = 08, B, C = 0,}`, want: nil, }, { name: "multiple duplicate enum values", content: `enum E { A = 0, B = 0, C = 0,}`, want: []analyzertest.Diag{ { StartLine: 2 + 1, StartCol: 2 + 1, EndLine: 2 + 1, EndCol: 3 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateEnumVal, Message: "enum value 0 duplicates A", }, { StartLine: 3 + 1, StartCol: 2 + 1, EndLine: 3 + 1, EndCol: 3 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateEnumVal, Message: "enum value 0 duplicates A", }, }, }, { name: "hex and decimal enum values collide", content: `enum E { A = 0x10, B = 16,}`, want: []analyzertest.Diag{ { StartLine: 2 + 1, StartCol: 2 + 1, EndLine: 2 + 1, EndCol: 3 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateEnumVal, Message: "enum value 16 duplicates A", }, }, }, { name: "duplicate map string keys", content: `const map<string, i32> M = { "a": 1, "a": 2,}`, want: []analyzertest.Diag{ { StartLine: 2 + 1, StartCol: 2 + 1, EndLine: 2 + 1, EndCol: 5 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateValue, Message: `duplicate map key "a"`, }, }, }, { name: "map keys collide by numeric value", content: `const map<i32, i32> M = { 1: 1, 0x1: 2,}`, want: []analyzertest.Diag{ { StartLine: 2 + 1, StartCol: 2 + 1, EndLine: 2 + 1, EndCol: 5 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateValue, Message: "duplicate map key 0x1", }, }, }, { name: "duplicate set values", content: `const set<i32> S = [1, 2, 1]`, want: []analyzertest.Diag{ { StartLine: 0 + 1, StartCol: 26 + 1, EndLine: 0 + 1, EndCol: 27 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateValue, Message: "duplicate set value 1", }, }, }, { name: "list keeps duplicate values", content: `const list<i32> L = [1, 1]`, want: nil, }, { name: "nested set in map value", content: `const map<string, set<i32>> M = { "a": [1, 2, 1],}`, want: []analyzertest.Diag{ { StartLine: 1 + 1, StartCol: 14 + 1, EndLine: 1 + 1, EndCol: 15 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateValue, Message: "duplicate set value 1", }, }, }, { name: "field default with duplicate map key", content: `struct Foo { 1: map<string, i32> m = {"a": 1, "a": 2},}`, want: []analyzertest.Diag{ { StartLine: 1 + 1, StartCol: 35 + 1, EndLine: 1 + 1, EndCol: 38 + 1, Severity: sema.SeverityError, Code: sema.CodeDuplicateValue, Message: `duplicate map key "a"`, }, }, }, }
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { report := analyzertest.Run(t, sema.EachFile(&DuplicateCheck{}), map[string]string{ "user.thrift": tt.content, }, "user.thrift")
assert.Equal(t, tt.want, analyzertest.Simplify(report[analyzertest.URI("user.thrift")])) }) }}