| // 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 ( |
| "os" |
| "testing" |
| |
| "fidl-lsp/analysis" |
| "fidl-lsp/state" |
| ) |
| |
| var ( |
| fuchsiaDir = os.Getenv("FUCHSIA_DIR") |
| toolsPath = fuchsiaDir + "/out/default.zircon/tools/" |
| fidlcPath = toolsPath + "fidlc" |
| fidlLintPath = toolsPath + "fidl-lint" |
| fidlFormatPath = toolsPath + "fidl-format" |
| ) |
| |
| func defaultConfig() analysis.Config { |
| return analysis.Config{ |
| BuildRootDir: fuchsiaDir, |
| FidlcPath: fidlcPath, |
| FidlLintPath: fidlLintPath, |
| FidlFormatPath: fidlFormatPath, |
| } |
| } |
| |
| func TestDefinitionOfLibrary(t *testing.T) { |
| analyzer := analysis.NewAnalyzer(defaultConfig(), analysis.CompiledLibraries{}) |
| defer analyzer.Cleanup() |
| fs := state.NewFileSystem() |
| fs.NewFile( |
| "file.fidl", |
| `library test;`, |
| ) |
| if err := analyzer.Analyze(fs, "file.fidl"); err != nil { |
| t.Fatalf("failed to analyze file: %s", err) |
| } |
| |
| locs, err := analyzer.DefinitionOfSymbol(fs, state.Symbol{ |
| Name: "test", |
| Location: state.Location{ |
| FileID: state.FileID("file.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 0, Character: 8}, |
| End: state.Position{Line: 0, Character: 12}, |
| }, |
| }, |
| }) |
| if err != nil { |
| t.Fatalf("error getting definition of symbol: %s", err) |
| } |
| |
| expLoc := state.Location{ |
| FileID: state.FileID("file.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 0, Character: 8}, |
| End: state.Position{Line: 0, Character: 12}, |
| }, |
| } |
| if len(locs) != 1 { |
| t.Fatalf("expected 1 definition location") |
| } |
| if locs[0] != expLoc { |
| t.Errorf("unexpected definition of symbol: expected %v, got %v", expLoc, locs[0]) |
| } |
| } |
| |
| func TestDefinitionOfLibraryMultipleFiles(t *testing.T) { |
| analyzer := analysis.NewAnalyzer(defaultConfig(), analysis.CompiledLibraries{}) |
| defer analyzer.Cleanup() |
| fs := state.NewFileSystem() |
| fs.NewFile( |
| "file1.fidl", |
| `library test;`, |
| ) |
| fs.NewFile( |
| "file2.fidl", |
| `library test;`, |
| ) |
| if err := analyzer.Analyze(fs, "file1.fidl"); err != nil { |
| t.Fatalf("failed to analyze file: %s", err) |
| } |
| if err := analyzer.Analyze(fs, "file2.fidl"); err != nil { |
| t.Fatalf("failed to analyze file: %s", err) |
| } |
| |
| locs, err := analyzer.DefinitionOfSymbol(fs, state.Symbol{ |
| Name: "test", |
| Location: state.Location{ |
| FileID: state.FileID("file1.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 0, Character: 8}, |
| End: state.Position{Line: 0, Character: 12}, |
| }, |
| }, |
| }) |
| if err != nil { |
| t.Fatalf("error getting definition of symbol: %s", err) |
| } |
| |
| expLocs := []state.Location{ |
| { |
| FileID: state.FileID("file1.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 0, Character: 8}, |
| End: state.Position{Line: 0, Character: 12}, |
| }, |
| }, |
| { |
| FileID: state.FileID("file2.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 0, Character: 8}, |
| End: state.Position{Line: 0, Character: 12}, |
| }, |
| }, |
| } |
| if len(locs) != len(expLocs) { |
| t.Fatalf("expected %d definition locations", len(expLocs)) |
| } |
| for _, expLoc := range expLocs { |
| found := false |
| for _, loc := range locs { |
| if expLoc == loc { |
| found = true |
| break |
| } |
| } |
| if !found { |
| t.Errorf("expected but did not find definition of symbol: %v", expLoc) |
| } |
| } |
| } |
| |
| func TestDefinitionOfLibraryImport(t *testing.T) { |
| analyzer := analysis.NewAnalyzer(defaultConfig(), analysis.CompiledLibraries{}) |
| defer analyzer.Cleanup() |
| fs := state.NewFileSystem() |
| fs.NewFile( |
| "import.fidl", |
| ` |
| library fuchsia.import; |
| |
| const uint8 FOO = 0; |
| `, |
| ) |
| fs.NewFile( |
| "test.fidl", |
| ` |
| library test; |
| using fuchsia.import; |
| |
| const uint8 BAR = fuchsia.import.FOO; |
| `, |
| ) |
| if err := analyzer.Analyze(fs, "import.fidl"); err != nil { |
| t.Fatalf("failed to analyze file: %s", err) |
| } |
| if err := analyzer.Analyze(fs, "test.fidl"); err != nil { |
| t.Fatalf("failed to analyze file: %s", err) |
| } |
| |
| locs, err := analyzer.DefinitionOfSymbol(fs, state.Symbol{ |
| Name: "fuchsia.import", |
| Location: state.Location{ |
| FileID: state.FileID("test.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 3, Character: 6}, |
| End: state.Position{Line: 3, Character: 20}, |
| }, |
| }, |
| }) |
| if err != nil { |
| t.Fatalf("error getting definition of symbol: %s", err) |
| } |
| |
| expLoc := state.Location{ |
| FileID: state.FileID("import.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 1, Character: 8}, |
| End: state.Position{Line: 1, Character: 22}, |
| }, |
| } |
| if len(locs) != 1 { |
| t.Fatalf("expected 1 definition location") |
| } |
| if locs[0] != expLoc { |
| t.Errorf("unexpected definition of symbol: expected %v, got %v", expLoc, locs[0]) |
| } |
| } |
| |
| func TestDefinitionOfFullyQualifiedSymbol(t *testing.T) { |
| analyzer := analysis.NewAnalyzer(defaultConfig(), analysis.CompiledLibraries{}) |
| defer analyzer.Cleanup() |
| fs := state.NewFileSystem() |
| fs.NewFile( |
| "import.fidl", |
| ` |
| library fuchsia.import; |
| |
| struct Foo {}; |
| `, |
| ) |
| fs.NewFile( |
| "test.fidl", |
| ` |
| library test; |
| using fuchsia.import; |
| |
| protocol Baz { |
| Method(fuchsia.import.Foo f); |
| }; |
| `, |
| ) |
| if err := analyzer.Analyze(fs, "import.fidl"); err != nil { |
| t.Fatalf("failed to analyze file: %s", err) |
| } |
| if err := analyzer.Analyze(fs, "test.fidl"); err != nil { |
| t.Fatalf("failed to analyze file: %s", err) |
| } |
| |
| locs, err := analyzer.DefinitionOfSymbol(fs, state.Symbol{ |
| Name: "fuchsia.import.Foo", |
| Location: state.Location{ |
| FileID: state.FileID("test.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 5, Character: 11}, |
| End: state.Position{Line: 5, Character: 29}, |
| }, |
| }, |
| }) |
| if err != nil { |
| t.Fatalf("error getting definition of symbol: %s", err) |
| } |
| |
| expLoc := state.Location{ |
| FileID: state.FileID("import.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 3, Character: 7}, |
| End: state.Position{Line: 3, Character: 10}, |
| }, |
| } |
| if len(locs) != 1 { |
| t.Fatalf("expected 1 definition location") |
| } |
| if locs[0] != expLoc { |
| t.Errorf("unexpected definition of symbol: expected %v, got %v", expLoc, locs[0]) |
| } |
| } |
| |
| func TestDefinitionOfLocalSymbol(t *testing.T) { |
| analyzer := analysis.NewAnalyzer(defaultConfig(), analysis.CompiledLibraries{}) |
| defer analyzer.Cleanup() |
| fs := state.NewFileSystem() |
| fs.NewFile( |
| "test.fidl", |
| ` |
| library test; |
| |
| struct Foo {}; |
| |
| protocol Baz { |
| Method(Foo f); |
| }; |
| `, |
| ) |
| if err := analyzer.Analyze(fs, "test.fidl"); err != nil { |
| t.Fatalf("failed to analyze file: %s", err) |
| } |
| |
| locs, err := analyzer.DefinitionOfSymbol(fs, state.Symbol{ |
| Name: "Foo", |
| Location: state.Location{ |
| FileID: state.FileID("test.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 6, Character: 11}, |
| End: state.Position{Line: 6, Character: 14}, |
| }, |
| }, |
| }) |
| if err != nil { |
| t.Fatalf("error getting definition of symbol: %s", err) |
| } |
| |
| expLoc := state.Location{ |
| FileID: state.FileID("test.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 3, Character: 7}, |
| End: state.Position{Line: 3, Character: 10}, |
| }, |
| } |
| if len(locs) != 1 { |
| t.Fatalf("expected 1 definition location") |
| } |
| if locs[0] != expLoc { |
| t.Errorf("unexpected definition of symbol: expected %v, got %v", expLoc, locs[0]) |
| } |
| } |
| |
| func TestDefinitionOfSymbolInSameLibraryDifferentFile(t *testing.T) { |
| analyzer := analysis.NewAnalyzer(defaultConfig(), analysis.CompiledLibraries{}) |
| defer analyzer.Cleanup() |
| fs := state.NewFileSystem() |
| fs.NewFile( |
| "test1.fidl", |
| ` |
| library test; |
| |
| struct Foo {}; |
| `, |
| ) |
| fs.NewFile( |
| "test2.fidl", |
| ` |
| library test; |
| |
| protocol Baz { |
| Method(Foo f); |
| }; |
| `, |
| ) |
| if err := analyzer.Analyze(fs, "test1.fidl"); err != nil { |
| t.Fatalf("failed to analyze file: %s", err) |
| } |
| if err := analyzer.Analyze(fs, "test2.fidl"); err != nil { |
| t.Fatalf("failed to analyze file: %s", err) |
| } |
| |
| locs, err := analyzer.DefinitionOfSymbol(fs, state.Symbol{ |
| Name: "Foo", |
| Location: state.Location{ |
| FileID: state.FileID("test2.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 5, Character: 11}, |
| End: state.Position{Line: 5, Character: 14}, |
| }, |
| }, |
| }) |
| if err != nil { |
| t.Fatalf("error getting definition of symbol: %s", err) |
| } |
| |
| expLoc := state.Location{ |
| FileID: state.FileID("test1.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 3, Character: 7}, |
| End: state.Position{Line: 3, Character: 10}, |
| }, |
| } |
| if len(locs) != 1 { |
| t.Fatalf("expected 1 definition location") |
| } |
| if locs[0] != expLoc { |
| t.Errorf("unexpected definition of symbol: expected %v, got %v", expLoc, locs[0]) |
| } |
| } |
| |
| func TestTwoLibrariesWithSameName(t *testing.T) { |
| analyzer := analysis.NewAnalyzer( |
| defaultConfig(), |
| // Two distinct libraries with the same name |
| analysis.CompiledLibraries{ |
| { |
| Name: "fuchsia.test", |
| Files: []string{"test1.fidl"}, |
| }, |
| { |
| Name: "fuchsia.test", |
| Files: []string{"test2.fidl"}, |
| }, |
| }, |
| ) |
| defer analyzer.Cleanup() |
| |
| fs := state.NewFileSystem() |
| fs.NewFile( |
| "test1.fidl", |
| ` |
| library fuchsia.test; |
| |
| struct Foo {}; |
| union Bar { |
| 1: Foo foo; |
| }; |
| `, |
| ) |
| fs.NewFile( |
| "test2.fidl", |
| ` |
| library fuchsia.test; |
| |
| struct Foo {}; |
| union Bar { |
| 1: Foo foo; |
| }; |
| `, |
| ) |
| if err := analyzer.Analyze(fs, "test1.fidl"); err != nil { |
| t.Fatalf("failed to analyze file: %s", err) |
| } |
| if err := analyzer.Analyze(fs, "test2.fidl"); err != nil { |
| t.Fatalf("failed to analyze file: %s", err) |
| } |
| |
| locs, err := analyzer.DefinitionOfSymbol(fs, state.Symbol{ |
| Name: "Foo", |
| Location: state.Location{ |
| FileID: state.FileID("test1.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 5, Character: 7}, |
| End: state.Position{Line: 5, Character: 10}, |
| }, |
| }, |
| }) |
| if err != nil { |
| t.Fatalf("error getting definition of symbol: %s", err) |
| } |
| |
| expLoc := state.Location{ |
| FileID: state.FileID("test1.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 3, Character: 7}, |
| End: state.Position{Line: 3, Character: 10}, |
| }, |
| } |
| if len(locs) != 1 { |
| t.Fatalf("expected 1 definition location") |
| } |
| if locs[0] != expLoc { |
| t.Errorf("unexpected definition of symbol: expected %v, got %v", expLoc, locs[0]) |
| } |
| } |
| |
| func TestDefinitionOfReservedKeyword(t *testing.T) { |
| analyzer := analysis.NewAnalyzer(defaultConfig(), analysis.CompiledLibraries{}) |
| defer analyzer.Cleanup() |
| fs := state.NewFileSystem() |
| fs.NewFile( |
| "test.fidl", |
| ` |
| library test; |
| |
| struct Foo {}; |
| `, |
| ) |
| if err := analyzer.Analyze(fs, "test.fidl"); err != nil { |
| t.Fatalf("failed to analyze file: %s", err) |
| } |
| |
| if _, err := analyzer.DefinitionOfSymbol(fs, state.Symbol{ |
| Name: "struct", |
| Location: state.Location{ |
| FileID: state.FileID("test.fidl"), |
| Range: state.Range{ |
| Start: state.Position{Line: 3, Character: 0}, |
| End: state.Position{Line: 3, Character: 6}, |
| }, |
| }, |
| }); err == nil { |
| t.Fatalf("expect error on definition of reserved keyword") |
| } |
| } |