|
| 1 | +package ls_test |
| 2 | + |
| 3 | +import ( |
| 4 | + "strings" |
| 5 | + "testing" |
| 6 | + |
| 7 | + "github.com/microsoft/typescript-go/internal/bundled" |
| 8 | + "github.com/microsoft/typescript-go/internal/ls" |
| 9 | + "github.com/microsoft/typescript-go/internal/lsp/lsproto" |
| 10 | + "github.com/microsoft/typescript-go/internal/testutil/projecttestutil" |
| 11 | + "github.com/microsoft/typescript-go/internal/tspath" |
| 12 | + "gotest.tools/v3/assert" |
| 13 | +) |
| 14 | + |
| 15 | +func TestUntitledReferences(t *testing.T) { |
| 16 | + t.Parallel() |
| 17 | + if !bundled.Embedded { |
| 18 | + t.Skip("bundled files are not embedded") |
| 19 | + } |
| 20 | + |
| 21 | + // First test the URI conversion functions to understand the issue |
| 22 | + untitledURI := lsproto.DocumentUri("untitled:Untitled-2") |
| 23 | + convertedFileName := ls.DocumentURIToFileName(untitledURI) |
| 24 | + t.Logf("URI '%s' converts to filename '%s'", untitledURI, convertedFileName) |
| 25 | + |
| 26 | + backToURI := ls.FileNameToDocumentURI(convertedFileName) |
| 27 | + t.Logf("Filename '%s' converts back to URI '%s'", convertedFileName, backToURI) |
| 28 | + |
| 29 | + if string(backToURI) != string(untitledURI) { |
| 30 | + t.Errorf("Round-trip conversion failed: '%s' -> '%s' -> '%s'", untitledURI, convertedFileName, backToURI) |
| 31 | + } |
| 32 | + |
| 33 | + // Create a test case that simulates how untitled files should work |
| 34 | + testContent := `let x = 42; |
| 35 | +
|
| 36 | +x |
| 37 | +
|
| 38 | +x++;` |
| 39 | + |
| 40 | + // Use the converted filename that DocumentURIToFileName would produce |
| 41 | + untitledFileName := convertedFileName // "^/untitled/ts-nul-authority/Untitled-2" |
| 42 | + t.Logf("Would use untitled filename: %s", untitledFileName) |
| 43 | + |
| 44 | + // Set up the file system with an untitled file - |
| 45 | + // But use a regular file first to see the current behavior |
| 46 | + files := map[string]string{ |
| 47 | + "/Untitled-2.ts": testContent, |
| 48 | + } |
| 49 | + |
| 50 | + ctx := projecttestutil.WithRequestID(t.Context()) |
| 51 | + service, done := createLanguageService(ctx, "/Untitled-2.ts", files) |
| 52 | + defer done() |
| 53 | + |
| 54 | + // Test the filename that the source file reports |
| 55 | + program := service.GetProgram() |
| 56 | + sourceFile := program.GetSourceFile("/Untitled-2.ts") |
| 57 | + t.Logf("SourceFile.FileName() returns: '%s'", sourceFile.FileName()) |
| 58 | + |
| 59 | + // Calculate position of 'x' on line 3 (zero-indexed line 2, character 0) |
| 60 | + position := 13 // After "let x = 42;\n\n" |
| 61 | + |
| 62 | + // Call ProvideReferences using the test method |
| 63 | + refs := service.TestProvideReferences("/Untitled-2.ts", position) |
| 64 | + |
| 65 | + // Log the results |
| 66 | + t.Logf("Input file name: %s", "/Untitled-2.ts") |
| 67 | + t.Logf("Number of references found: %d", len(refs)) |
| 68 | + for i, ref := range refs { |
| 69 | + t.Logf("Reference %d: URI=%s, Range=%+v", i+1, ref.Uri, ref.Range) |
| 70 | + } |
| 71 | + |
| 72 | + // We expect to find 3 references |
| 73 | + assert.Assert(t, len(refs) == 3, "Expected 3 references, got %d", len(refs)) |
| 74 | + |
| 75 | + // Also test definition using ProvideDefinition |
| 76 | + uri := ls.FileNameToDocumentURI("/Untitled-2.ts") |
| 77 | + lspPosition := lsproto.Position{Line: 2, Character: 0} |
| 78 | + definition, err := service.ProvideDefinition(t.Context(), uri, lspPosition) |
| 79 | + assert.NilError(t, err) |
| 80 | + if definition != nil && definition.Locations != nil { |
| 81 | + t.Logf("Definition found: %d locations", len(*definition.Locations)) |
| 82 | + for i, loc := range *definition.Locations { |
| 83 | + t.Logf("Definition %d: URI=%s, Range=%+v", i+1, loc.Uri, loc.Range) |
| 84 | + } |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +func TestUntitledFileNameDebugging(t *testing.T) { |
| 89 | + t.Parallel() |
| 90 | + if !bundled.Embedded { |
| 91 | + t.Skip("bundled files are not embedded") |
| 92 | + } |
| 93 | + |
| 94 | + // Test the URI conversion flow |
| 95 | + untitledURI := lsproto.DocumentUri("untitled:Untitled-2") |
| 96 | + convertedFileName := ls.DocumentURIToFileName(untitledURI) |
| 97 | + t.Logf("1. URI '%s' converts to filename '%s'", untitledURI, convertedFileName) |
| 98 | + |
| 99 | + // Test the path handling |
| 100 | + currentDir := "/home/daniel/TypeScript" |
| 101 | + path := tspath.ToPath(convertedFileName, currentDir, true) |
| 102 | + t.Logf("2. ToPath('%s', '%s') returns: '%s'", convertedFileName, currentDir, string(path)) |
| 103 | + |
| 104 | + // Verify the path is NOT resolved against current directory |
| 105 | + if strings.HasPrefix(string(path), currentDir) { |
| 106 | + t.Errorf("Path was incorrectly resolved against current directory: %s", string(path)) |
| 107 | + } |
| 108 | + |
| 109 | + // Test converting back to URI |
| 110 | + backToURI := ls.FileNameToDocumentURI(string(path)) |
| 111 | + t.Logf("3. Path '%s' converts back to URI '%s'", string(path), backToURI) |
| 112 | + |
| 113 | + if string(backToURI) != string(untitledURI) { |
| 114 | + t.Errorf("Round-trip conversion failed: '%s' -> '%s' -> '%s'", untitledURI, string(path), backToURI) |
| 115 | + } |
| 116 | + |
| 117 | + t.Logf("✅ Fix working: untitled paths are not resolved against current directory") |
| 118 | +} |
| 119 | + |
| 120 | +func TestUntitledFileIntegration(t *testing.T) { |
| 121 | + t.Parallel() |
| 122 | + if !bundled.Embedded { |
| 123 | + t.Skip("bundled files are not embedded") |
| 124 | + } |
| 125 | + |
| 126 | + // This test simulates the exact scenario from the issue: |
| 127 | + // 1. VS Code sends untitled:Untitled-2 URI |
| 128 | + // 2. References/definitions should return untitled:Untitled-2 URIs, not file:// URIs |
| 129 | + |
| 130 | + // Simulate exactly what happens in the LSP flow |
| 131 | + originalURI := lsproto.DocumentUri("untitled:Untitled-2") |
| 132 | + |
| 133 | + // Step 1: URI gets converted to filename when file is opened |
| 134 | + fileName := ls.DocumentURIToFileName(originalURI) |
| 135 | + t.Logf("1. Opening file: URI '%s' -> fileName '%s'", originalURI, fileName) |
| 136 | + |
| 137 | + // Step 2: fileName gets processed through ToPath in project service |
| 138 | + currentDir := "/home/daniel/TypeScript" // Current directory from the original issue |
| 139 | + path := tspath.ToPath(fileName, currentDir, true) |
| 140 | + t.Logf("2. Project service processes: fileName '%s' -> path '%s'", fileName, string(path)) |
| 141 | + |
| 142 | + // Step 3: Verify path is NOT corrupted by current directory resolution |
| 143 | + if strings.HasPrefix(string(path), currentDir) { |
| 144 | + t.Fatalf("❌ BUG: Path was incorrectly resolved against current directory: %s", string(path)) |
| 145 | + } |
| 146 | + |
| 147 | + // Step 4: When references are found, the path gets converted back to URI |
| 148 | + resultURI := ls.FileNameToDocumentURI(string(path)) |
| 149 | + t.Logf("3. References return: path '%s' -> URI '%s'", string(path), resultURI) |
| 150 | + |
| 151 | + // Step 5: Verify the round-trip conversion works |
| 152 | + if string(resultURI) != string(originalURI) { |
| 153 | + t.Fatalf("❌ Round-trip failed: %s != %s", originalURI, resultURI) |
| 154 | + } |
| 155 | + |
| 156 | + t.Logf("✅ SUCCESS: Untitled file URIs are preserved correctly") |
| 157 | + t.Logf(" Original URI: %s", originalURI) |
| 158 | + t.Logf(" Final URI: %s", resultURI) |
| 159 | +} |
0 commit comments