| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package analysis_test |
| |
| import ( |
| "testing" |
| |
| "fidl-lsp/analysis" |
| "fidl-lsp/state" |
| ) |
| |
| type TestFile struct { |
| name state.FileID |
| text string |
| } |
| |
| func TestReferencesToSymbol(t *testing.T) { |
| cases := []struct { |
| name string |
| files []TestFile |
| symbol state.Symbol |
| expRefs []state.Location |
| }{ |
| { |
| name: "Reference to library declaration", |
| files: []TestFile{ |
| { |
| "test.fidl", |
| `library fuchsia.test;`, |
| }, |
| { |
| "other.fidl", |
| ` |
| library other; |
| using fuchsia.test;`, |
| }, |
| }, |
| symbol: state.Symbol{ |
| Name: "fuchsia.test", |
| Location: state.Location{ |
| FileID: state.FileID("test.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 0, Character: 6}, |
| End: state.Position{Line: 0, Character: 10}, |
| }, |
| }, |
| }, |
| expRefs: []state.Location{ |
| { |
| FileID: state.FileID("other.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 2, Character: 6}, |
| End: state.Position{Line: 2, Character: 18}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "Reference to library import", |
| files: []TestFile{ |
| { |
| "test.fidl", |
| `library fuchsia.test;`, |
| }, |
| { |
| "other.fidl", |
| ` |
| library other; |
| using fuchsia.test;`, |
| }, |
| }, |
| symbol: state.Symbol{ |
| Name: "fuchsia.test", |
| Location: state.Location{ |
| FileID: state.FileID("test.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 2, Character: 6}, |
| End: state.Position{Line: 2, Character: 18}, |
| }, |
| }, |
| }, |
| expRefs: []state.Location{ |
| { |
| FileID: state.FileID("other.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 2, Character: 6}, |
| End: state.Position{Line: 2, Character: 18}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "Reference to symbol in same file", |
| files: []TestFile{{ |
| "test.fidl", |
| ` |
| library test; |
| |
| struct Foo {}; |
| |
| protocol Bar { |
| Method(Foo foo); |
| // ~~~ |
| }; |
| `, |
| }}, |
| symbol: state.Symbol{ |
| Name: "Foo", |
| Location: state.Location{ |
| FileID: state.FileID("test.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 3, Character: 7}, |
| End: state.Position{Line: 3, Character: 10}, |
| }, |
| }, |
| }, |
| expRefs: []state.Location{ |
| { |
| FileID: state.FileID("test.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 6, Character: 12}, |
| End: state.Position{Line: 6, Character: 15}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "Reference to symbol in other file in same library", |
| files: []TestFile{ |
| { |
| "test1.fidl", |
| ` |
| library test; |
| |
| struct Foo {}; |
| `, |
| }, |
| { |
| "test2.fidl", |
| ` |
| library test; |
| |
| protocol Bar { |
| Method(Foo foo); |
| // ~~~ |
| }; |
| `, |
| }, |
| }, |
| symbol: state.Symbol{ |
| Name: "Foo", |
| Location: state.Location{ |
| FileID: state.FileID("test1.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 3, Character: 7}, |
| End: state.Position{Line: 3, Character: 10}, |
| }, |
| }, |
| }, |
| expRefs: []state.Location{ |
| { |
| FileID: state.FileID("test2.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 4, Character: 12}, |
| End: state.Position{Line: 4, Character: 15}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "References to symbol in other library", |
| files: []TestFile{ |
| { |
| "imported.fidl", |
| ` |
| library fuchsia.imported; |
| |
| struct Foo {}; |
| `, |
| }, |
| { |
| "test.fidl", |
| ` |
| library test; |
| using fuchsia.imported; |
| |
| protocol Bar { |
| Method(fuchsia.imported.Foo foo); |
| // ~~~ |
| }; |
| `, |
| }, |
| }, |
| symbol: state.Symbol{ |
| Name: "Foo", |
| Location: state.Location{ |
| FileID: state.FileID("imported.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 3, Character: 7}, |
| End: state.Position{Line: 3, Character: 10}, |
| }, |
| }, |
| }, |
| expRefs: []state.Location{ |
| { |
| FileID: state.FileID("test.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 5, Character: 29}, |
| End: state.Position{Line: 5, Character: 32}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "No references to symbol", |
| files: []TestFile{{ |
| "test.fidl", |
| ` |
| library test; |
| |
| struct Foo {}; |
| `, |
| }}, |
| symbol: state.Symbol{ |
| Name: "Foo", |
| Location: state.Location{ |
| FileID: state.FileID("test.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 3, Character: 7}, |
| End: state.Position{Line: 3, Character: 10}, |
| }, |
| }, |
| }, |
| expRefs: []state.Location{}, |
| }, |
| { |
| name: "References to type alias", |
| files: []TestFile{ |
| { |
| "zx.fidl", |
| ` |
| library zx; |
| |
| using status = int32; |
| `, |
| }, |
| { |
| |
| "test.fidl", |
| ` |
| library test; |
| using zx; |
| |
| protocol Foo { |
| Method() -> (zx.status status); |
| // ~~~~~~ |
| }; |
| `, |
| }, |
| }, |
| symbol: state.Symbol{ |
| Name: "status", |
| Location: state.Location{ |
| FileID: state.FileID("zx.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 3, Character: 7}, |
| End: state.Position{Line: 3, Character: 10}, |
| }, |
| }, |
| }, |
| expRefs: []state.Location{ |
| { |
| FileID: state.FileID("test.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 5, Character: 24}, |
| End: state.Position{Line: 5, Character: 30}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "References in type parameters", |
| files: []TestFile{{ |
| "test.fidl", |
| ` |
| library test; |
| |
| table Foo {}; |
| |
| protocol Bar { |
| Method(vector<Foo> items); |
| // ~~~~~ |
| }; |
| `, |
| }}, |
| symbol: state.Symbol{ |
| Name: "Foo", |
| Location: state.Location{ |
| FileID: state.FileID("test.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 3, Character: 6}, |
| End: state.Position{Line: 3, Character: 9}, |
| }, |
| }, |
| }, |
| expRefs: []state.Location{ |
| { |
| FileID: state.FileID("test.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 6, Character: 20}, |
| End: state.Position{Line: 6, Character: 25}, |
| }, |
| }, |
| }, |
| }, |
| } |
| |
| for _, ex := range cases { |
| analyzer := analysis.NewAnalyzer(defaultConfig()) |
| defer analyzer.Cleanup() |
| fs := state.NewFileSystem() |
| for _, file := range ex.files { |
| fs.NewFile(file.name, file.text) |
| } |
| for _, file := range ex.files { |
| if err := analyzer.Analyze(fs, file.name); err != nil { |
| t.Fatalf("case `%s`: failed to analyze file `%s`: %s", ex.name, file, err) |
| } |
| } |
| |
| refs, err := analyzer.ReferencesToSymbol(fs, ex.symbol) |
| if err != nil { |
| t.Fatalf("case `%s`: error getting references to symbol: %s", ex.name, err) |
| } |
| if len(refs) != len(ex.expRefs) { |
| t.Fatalf("case `%s`: expected %d references, got %d", ex.name, len(ex.expRefs), len(refs)) |
| } |
| for _, ref := range refs { |
| found := false |
| for _, expRef := range ex.expRefs { |
| if ref == expRef { |
| found = true |
| break |
| } |
| } |
| if !found { |
| t.Errorf( |
| "case `%s`: got unexpected reference %v to symbol %s", |
| ex.name, |
| ref, |
| ex.symbol.Name, |
| ) |
| } |
| } |
| for _, expRef := range ex.expRefs { |
| found := false |
| for _, ref := range refs { |
| if ref == expRef { |
| found = true |
| break |
| } |
| } |
| if !found { |
| t.Errorf( |
| "case `%s`: expected but did not find reference %v to symbol %s", |
| ex.name, |
| expRef, |
| ex.symbol.Name, |
| ) |
| } |
| } |
| } |
| } |