Skip to content

Commit f8ab4d6

Browse files
daniel-lxshannesrudolph
authored andcommitted
Handle directory URI on diagnostics (#3457)
1 parent ba5aaf3 commit f8ab4d6

File tree

2 files changed

+397
-5
lines changed

2 files changed

+397
-5
lines changed
Lines changed: 376 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,376 @@
1+
import * as vscode from "vscode"
2+
import { diagnosticsToProblemsString } from ".."
3+
4+
// Mock path module
5+
jest.mock("path", () => ({
6+
relative: jest.fn((cwd, fullPath) => {
7+
// Handle the specific case already present
8+
if (cwd === "/project/root" && fullPath === "/project/root/src/utils/file.ts") {
9+
return "src/utils/file.ts"
10+
}
11+
// Handle the test cases with /path/to as cwd
12+
if (cwd === "/path/to") {
13+
// Simple relative path calculation for the test cases
14+
return fullPath.replace(cwd + "/", "")
15+
}
16+
// Fallback for other cases (can be adjusted if needed)
17+
return fullPath
18+
}),
19+
}))
20+
21+
// Mock vscode module
22+
jest.mock("vscode", () => ({
23+
Uri: {
24+
file: jest.fn((path) => ({
25+
fsPath: path,
26+
toString: jest.fn(() => path),
27+
})),
28+
},
29+
Diagnostic: jest.fn().mockImplementation((range, message, severity) => ({
30+
range,
31+
message,
32+
severity,
33+
source: "test",
34+
})),
35+
Range: jest.fn().mockImplementation((startLine, startChar, endLine, endChar) => ({
36+
start: { line: startLine, character: startChar },
37+
end: { line: endLine, character: endChar },
38+
})),
39+
DiagnosticSeverity: {
40+
Error: 0,
41+
Warning: 1,
42+
Information: 2,
43+
Hint: 3,
44+
},
45+
FileType: {
46+
Unknown: 0,
47+
File: 1,
48+
Directory: 2,
49+
SymbolicLink: 64,
50+
},
51+
workspace: {
52+
fs: {
53+
stat: jest.fn(),
54+
},
55+
openTextDocument: jest.fn(),
56+
},
57+
}))
58+
59+
describe("diagnosticsToProblemsString", () => {
60+
beforeEach(() => {
61+
jest.clearAllMocks()
62+
})
63+
64+
it("should filter diagnostics by severity and include correct labels", async () => {
65+
// Mock file URI
66+
const fileUri = vscode.Uri.file("/path/to/file.ts")
67+
68+
// Create diagnostics with different severities
69+
const diagnostics = [
70+
new vscode.Diagnostic(new vscode.Range(0, 0, 0, 10), "Error message", vscode.DiagnosticSeverity.Error),
71+
new vscode.Diagnostic(new vscode.Range(1, 0, 1, 10), "Warning message", vscode.DiagnosticSeverity.Warning),
72+
new vscode.Diagnostic(new vscode.Range(2, 0, 2, 10), "Info message", vscode.DiagnosticSeverity.Information),
73+
new vscode.Diagnostic(new vscode.Range(3, 0, 3, 10), "Hint message", vscode.DiagnosticSeverity.Hint),
74+
]
75+
76+
// Mock fs.stat to return file type
77+
const mockStat = {
78+
type: vscode.FileType.File,
79+
}
80+
vscode.workspace.fs.stat = jest.fn().mockResolvedValue(mockStat)
81+
82+
// Mock document content
83+
const mockDocument = {
84+
lineAt: jest.fn((line) => ({
85+
text: `Line ${line + 1} content`,
86+
})),
87+
}
88+
vscode.workspace.openTextDocument = jest.fn().mockResolvedValue(mockDocument)
89+
90+
// Test with Error and Warning severities only
91+
const result = await diagnosticsToProblemsString(
92+
[[fileUri, diagnostics]],
93+
[vscode.DiagnosticSeverity.Error, vscode.DiagnosticSeverity.Warning],
94+
"/path/to",
95+
)
96+
97+
// Verify only Error and Warning diagnostics are included
98+
expect(result).toContain("Error message")
99+
expect(result).toContain("Warning message")
100+
expect(result).not.toContain("Info message")
101+
expect(result).not.toContain("Hint message")
102+
103+
// Verify correct severity labels are used
104+
expect(result).toContain("[test Error]")
105+
expect(result).toContain("[test Warning]")
106+
expect(result).not.toContain("[test Information]")
107+
expect(result).not.toContain("[test Hint]")
108+
109+
// Verify line content is included
110+
expect(result).toContain("Line 1 content")
111+
expect(result).toContain("Line 2 content")
112+
})
113+
114+
it("should handle directory URIs correctly without attempting to open as document", async () => {
115+
// Mock directory URI
116+
const dirUri = vscode.Uri.file("/path/to/directory/")
117+
118+
// Mock diagnostic for directory
119+
const diagnostic = new vscode.Diagnostic(
120+
new vscode.Range(0, 0, 0, 10),
121+
"Directory diagnostic message",
122+
vscode.DiagnosticSeverity.Error,
123+
)
124+
125+
// Mock fs.stat to return directory type
126+
const mockStat = {
127+
type: vscode.FileType.Directory,
128+
}
129+
vscode.workspace.fs.stat = jest.fn().mockResolvedValue(mockStat)
130+
131+
// Mock openTextDocument to ensure it's not called
132+
vscode.workspace.openTextDocument = jest.fn()
133+
134+
// Call the function
135+
const result = await diagnosticsToProblemsString(
136+
[[dirUri, [diagnostic]]],
137+
[vscode.DiagnosticSeverity.Error],
138+
"/path/to",
139+
)
140+
141+
// Verify fs.stat was called with the directory URI
142+
expect(vscode.workspace.fs.stat).toHaveBeenCalledWith(dirUri)
143+
144+
// Verify openTextDocument was not called
145+
expect(vscode.workspace.openTextDocument).not.toHaveBeenCalled()
146+
147+
// Verify the output contains the expected directory indicator
148+
expect(result).toContain("(directory)")
149+
expect(result).toContain("Directory diagnostic message")
150+
expect(result).toMatch(/directory\/\n- \[test Error\] 1 \| \(directory\) : Directory diagnostic message/)
151+
})
152+
153+
it("should correctly handle multiple diagnostics for the same file", async () => {
154+
// Mock file URI
155+
const fileUri = vscode.Uri.file("/path/to/file.ts")
156+
157+
// Create multiple diagnostics for the same file
158+
const diagnostics = [
159+
new vscode.Diagnostic(new vscode.Range(4, 0, 4, 10), "Later line error", vscode.DiagnosticSeverity.Error),
160+
new vscode.Diagnostic(
161+
new vscode.Range(0, 0, 0, 10),
162+
"First line warning",
163+
vscode.DiagnosticSeverity.Warning,
164+
),
165+
new vscode.Diagnostic(
166+
new vscode.Range(2, 0, 2, 10),
167+
"Middle line info",
168+
vscode.DiagnosticSeverity.Information,
169+
),
170+
]
171+
172+
// Mock fs.stat to return file type
173+
const mockStat = {
174+
type: vscode.FileType.File,
175+
}
176+
vscode.workspace.fs.stat = jest.fn().mockResolvedValue(mockStat)
177+
178+
// Mock document content with specific line texts for each test case
179+
const mockDocument = {
180+
lineAt: jest.fn((line: number) => {
181+
const lineTexts: Record<number, string> = {
182+
0: "Line 0 content for warning",
183+
2: "Line 2 content for info",
184+
4: "Line 4 content for error",
185+
}
186+
return { text: lineTexts[line] }
187+
}),
188+
}
189+
vscode.workspace.openTextDocument = jest.fn().mockResolvedValue(mockDocument)
190+
191+
// Call the function with all severities
192+
const result = await diagnosticsToProblemsString(
193+
[[fileUri, diagnostics]],
194+
[vscode.DiagnosticSeverity.Error, vscode.DiagnosticSeverity.Warning, vscode.DiagnosticSeverity.Information],
195+
"/path/to",
196+
)
197+
198+
// Verify all diagnostics are included in the output
199+
expect(result).toContain("First line warning")
200+
expect(result).toContain("Middle line info")
201+
expect(result).toContain("Later line error")
202+
203+
// Verify line content is included for each diagnostic and matches the test case
204+
expect(result).toContain("Line 0 content for warning")
205+
expect(result).toContain("Line 2 content for info")
206+
expect(result).toContain("Line 4 content for error")
207+
208+
// Verify the output contains all severity labels
209+
expect(result).toContain("[test Warning]")
210+
expect(result).toContain("[test Information]")
211+
expect(result).toContain("[test Error]")
212+
213+
// Verify diagnostics appear in line number order (even though input wasn't sorted)
214+
// Verify exact output format
215+
expect(result).toBe(
216+
"file.ts\n" +
217+
"- [test Warning] 1 | Line 0 content for warning : First line warning\n" +
218+
"- [test Information] 3 | Line 2 content for info : Middle line info\n" +
219+
"- [test Error] 5 | Line 4 content for error : Later line error",
220+
)
221+
})
222+
223+
it("should correctly handle diagnostics from multiple files", async () => {
224+
// Mock URIs for different files
225+
const fileUri1 = vscode.Uri.file("/path/to/file1.ts")
226+
const fileUri2 = vscode.Uri.file("/path/to/subdir/file2.ts")
227+
228+
// Create diagnostics for each file
229+
const diagnostics1 = [
230+
new vscode.Diagnostic(new vscode.Range(0, 0, 0, 10), "File1 error", vscode.DiagnosticSeverity.Error),
231+
]
232+
233+
const diagnostics2 = [
234+
new vscode.Diagnostic(new vscode.Range(1, 0, 1, 10), "File2 warning", vscode.DiagnosticSeverity.Warning),
235+
new vscode.Diagnostic(new vscode.Range(2, 0, 2, 10), "File2 info", vscode.DiagnosticSeverity.Information),
236+
]
237+
238+
// Mock fs.stat to return file type for both files
239+
const mockStat = {
240+
type: vscode.FileType.File,
241+
}
242+
vscode.workspace.fs.stat = jest.fn().mockResolvedValue(mockStat)
243+
244+
// Mock document content with specific line texts for each test case
245+
const mockDocument1 = {
246+
lineAt: jest.fn((_line) => ({
247+
text: "Line 1 content for error",
248+
})),
249+
}
250+
const mockDocument2 = {
251+
lineAt: jest.fn((line) => {
252+
const lineTexts = ["Line 1 content", "Line 2 content for warning", "Line 3 content for info"]
253+
return { text: lineTexts[line] }
254+
}),
255+
}
256+
vscode.workspace.openTextDocument = jest
257+
.fn()
258+
.mockResolvedValueOnce(mockDocument1)
259+
.mockResolvedValueOnce(mockDocument2)
260+
261+
// Call the function with all severities
262+
const result = await diagnosticsToProblemsString(
263+
[
264+
[fileUri1, diagnostics1],
265+
[fileUri2, diagnostics2],
266+
],
267+
[vscode.DiagnosticSeverity.Error, vscode.DiagnosticSeverity.Warning, vscode.DiagnosticSeverity.Information],
268+
"/path/to",
269+
)
270+
271+
// Verify file paths are correctly shown with relative paths
272+
expect(result).toContain("file1.ts")
273+
expect(result).toContain("subdir/file2.ts")
274+
275+
// Verify diagnostics are grouped under their respective files
276+
const file1Section = result.split("file1.ts")[1]
277+
expect(file1Section).toContain("File1 error")
278+
expect(file1Section).toContain("Line 1 content for error")
279+
280+
const file2Section = result.split("subdir/file2.ts")[1]
281+
expect(file2Section).toContain("File2 warning")
282+
expect(file2Section).toContain("Line 2 content for warning")
283+
expect(file2Section).toContain("File2 info")
284+
expect(file2Section).toContain("Line 3 content for info")
285+
286+
// Verify exact output format
287+
expect(result).toBe(
288+
"file1.ts\n" +
289+
"- [test Error] 1 | Line 1 content for error : File1 error\n\n" +
290+
"subdir/file2.ts\n" +
291+
"- [test Warning] 2 | Line 2 content for warning : File2 warning\n" +
292+
"- [test Information] 3 | Line 3 content for info : File2 info",
293+
)
294+
})
295+
296+
it("should return empty string when no diagnostics match the severity filter", async () => {
297+
// Mock file URI
298+
const fileUri = vscode.Uri.file("/path/to/file.ts")
299+
300+
// Create diagnostics with Error and Warning severities
301+
const diagnostics = [
302+
new vscode.Diagnostic(new vscode.Range(0, 0, 0, 10), "Error message", vscode.DiagnosticSeverity.Error),
303+
new vscode.Diagnostic(new vscode.Range(1, 0, 1, 10), "Warning message", vscode.DiagnosticSeverity.Warning),
304+
]
305+
306+
// Mock fs.stat to return file type
307+
const mockStat = {
308+
type: vscode.FileType.File,
309+
}
310+
vscode.workspace.fs.stat = jest.fn().mockResolvedValue(mockStat)
311+
312+
// Mock document content (though it shouldn't be accessed in this case)
313+
const mockDocument = {
314+
lineAt: jest.fn((line) => ({
315+
text: `Line ${line + 1} content`,
316+
})),
317+
}
318+
vscode.workspace.openTextDocument = jest.fn().mockResolvedValue(mockDocument)
319+
320+
// Test with Information and Hint severities only (which don't match our diagnostics)
321+
const result = await diagnosticsToProblemsString(
322+
[[fileUri, diagnostics]],
323+
[vscode.DiagnosticSeverity.Information, vscode.DiagnosticSeverity.Hint],
324+
"/path/to",
325+
)
326+
327+
// Verify empty string is returned
328+
expect(result).toBe("")
329+
330+
// Verify no unnecessary calls were made
331+
expect(vscode.workspace.fs.stat).not.toHaveBeenCalled()
332+
expect(vscode.workspace.openTextDocument).not.toHaveBeenCalled()
333+
})
334+
335+
it("should correctly handle cwd parameter for relative file paths", async () => {
336+
// Mock file URI in a subdirectory
337+
const fileUri = vscode.Uri.file("/project/root/src/utils/file.ts")
338+
339+
// Create a diagnostic for the file
340+
const diagnostic = new vscode.Diagnostic(
341+
new vscode.Range(4, 0, 4, 10),
342+
"Relative path test error",
343+
vscode.DiagnosticSeverity.Error,
344+
)
345+
346+
// Mock fs.stat to return file type
347+
const mockStat = {
348+
type: vscode.FileType.File,
349+
}
350+
vscode.workspace.fs.stat = jest.fn().mockResolvedValue(mockStat)
351+
352+
// Mock document content matching test assertion
353+
const mockDocument = {
354+
lineAt: jest.fn((line) => ({
355+
text: `Line ${line + 1} content for error`,
356+
})),
357+
}
358+
vscode.workspace.openTextDocument = jest.fn().mockResolvedValue(mockDocument)
359+
360+
// Call the function with cwd set to the project root
361+
const result = await diagnosticsToProblemsString(
362+
[[fileUri, [diagnostic]]],
363+
[vscode.DiagnosticSeverity.Error],
364+
"/project/root",
365+
)
366+
367+
// Verify exact output format
368+
expect(result).toBe(
369+
"src/utils/file.ts\n" + "- [test Error] 5 | Line 5 content for error : Relative path test error",
370+
)
371+
372+
// Verify fs.stat and openTextDocument were called
373+
expect(vscode.workspace.fs.stat).toHaveBeenCalledWith(fileUri)
374+
expect(vscode.workspace.openTextDocument).toHaveBeenCalledWith(fileUri)
375+
})
376+
})

0 commit comments

Comments
 (0)