-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Fix circular reference issue with Callable.ts * Only send changes to diagnostics to client. * Removed exclusive test
- Loading branch information
1 parent
6247a5b
commit 314d85d
Showing
4 changed files
with
251 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import { BsDiagnostic } from '.'; | ||
import { DiagnosticCollection } from './DiagnosticCollection'; | ||
import { Workspace } from './LanguageServer'; | ||
import { ProgramBuilder } from './ProgramBuilder'; | ||
import { File } from './interfaces'; | ||
import util from './util'; | ||
import { expect } from 'chai'; | ||
|
||
describe('DiagnosticCollection', () => { | ||
let collection: DiagnosticCollection; | ||
let diagnostics: BsDiagnostic[]; | ||
let workspaces: Workspace[]; | ||
beforeEach(() => { | ||
collection = new DiagnosticCollection(); | ||
diagnostics = []; | ||
//make simple mock of workspace to pass tests | ||
workspaces = [{ | ||
firstRunPromise: Promise.resolve(), | ||
builder: { | ||
getDiagnostics: () => diagnostics | ||
} as ProgramBuilder | ||
}] as Workspace[]; | ||
}); | ||
|
||
async function testPatch(expected: { [filePath: string]: string[] }) { | ||
const patch = await collection.getPatch(workspaces); | ||
//convert the patch into our test structure | ||
const actual = {}; | ||
for (const filePath in patch) { | ||
actual[filePath] = patch[filePath].map(x => x.message); | ||
} | ||
|
||
expect(actual).to.eql(expected); | ||
} | ||
|
||
it('returns full list of diagnostics on first call, and nothing on second call', async () => { | ||
addDiagnostics('file1.brs', ['message1', 'message2']); | ||
addDiagnostics('file2.brs', ['message3', 'message4']); | ||
//first patch should return all | ||
await testPatch({ | ||
'file1.brs': ['message1', 'message2'], | ||
'file2.brs': ['message3', 'message4'] | ||
}); | ||
|
||
//second patch should return empty (because nothing has changed) | ||
await testPatch({}); | ||
}); | ||
|
||
it('removes diagnostics in patch', async () => { | ||
addDiagnostics('file1.brs', ['message1', 'message2']); | ||
addDiagnostics('file2.brs', ['message3', 'message4']); | ||
//first patch should return all | ||
await testPatch({ | ||
'file1.brs': ['message1', 'message2'], | ||
'file2.brs': ['message3', 'message4'] | ||
}); | ||
removeDiagnostic('file1.brs', 'message1'); | ||
removeDiagnostic('file1.brs', 'message2'); | ||
await testPatch({ | ||
'file1.brs': [] | ||
}); | ||
}); | ||
|
||
it('adds diagnostics in patch', async () => { | ||
addDiagnostics('file1.brs', ['message1', 'message2']); | ||
await testPatch({ | ||
'file1.brs': ['message1', 'message2'] | ||
}); | ||
|
||
addDiagnostics('file2.brs', ['message3', 'message4']); | ||
await testPatch({ | ||
'file2.brs': ['message3', 'message4'] | ||
}); | ||
}); | ||
|
||
it('sends full list when file diagnostics have changed', async () => { | ||
addDiagnostics('file1.brs', ['message1', 'message2']); | ||
await testPatch({ | ||
'file1.brs': ['message1', 'message2'] | ||
}); | ||
addDiagnostics('file1.brs', ['message3', 'message4']); | ||
await testPatch({ | ||
'file1.brs': ['message1', 'message2', 'message3', 'message4'] | ||
}); | ||
}); | ||
|
||
function removeDiagnostic(filePath: string, message: string) { | ||
for (let i = 0; i < diagnostics.length; i++) { | ||
const diagnostic = diagnostics[i]; | ||
if (diagnostic.file.pathAbsolute === filePath && diagnostic.message === message) { | ||
diagnostics.splice(i, 1); | ||
return; | ||
} | ||
} | ||
throw new Error(`Cannot find diagnostic ${filePath}:${message}`); | ||
} | ||
|
||
function addDiagnostics(filePath: string, messages: string[]) { | ||
for (const message of messages) { | ||
diagnostics.push({ | ||
file: { | ||
pathAbsolute: filePath | ||
} as File, | ||
range: util.createRange(0, 0, 0, 0), | ||
//the code doesn't matter as long as the messages are different, so just enforce unique messages for this test files | ||
code: 123, | ||
message: message | ||
}); | ||
} | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import { BsDiagnostic } from './interfaces'; | ||
import { Workspace } from './LanguageServer'; | ||
|
||
export class DiagnosticCollection { | ||
private previousDiagnosticsByFile = {} as { [fileSrcPathLower: string]: KeyedDiagnostic[] }; | ||
|
||
public async getPatch(workspaces: Workspace[]): Promise<{ [fileSrcPathLower: string]: KeyedDiagnostic[] }> { | ||
const diagnosticsByFile = await this.getDiagnosticsByFileFromWorkspaces(workspaces); | ||
|
||
const patch = { | ||
...this.getRemovedPatch(diagnosticsByFile), | ||
...this.getModifiedPatch(diagnosticsByFile), | ||
...this.getAddedPatch(diagnosticsByFile) | ||
}; | ||
|
||
//save the new list of diagnostics | ||
this.previousDiagnosticsByFile = diagnosticsByFile; | ||
return patch; | ||
} | ||
|
||
private async getDiagnosticsByFileFromWorkspaces(workspaces: Workspace[]) { | ||
const result = {} as { [fileSrcPathLower: string]: KeyedDiagnostic[] }; | ||
|
||
//wait for all programs to finish running. This ensures the `Program` exists. | ||
await Promise.all( | ||
workspaces.map(x => x.firstRunPromise) | ||
); | ||
|
||
//get all diagnostics for all workspaces | ||
let diagnostics = Array.prototype.concat.apply([] as KeyedDiagnostic[], | ||
workspaces.map((x) => x.builder.getDiagnostics()) | ||
) as KeyedDiagnostic[]; | ||
|
||
const keys = {}; | ||
//build the full current set of diagnostics by file | ||
for (let diagnostic of diagnostics) { | ||
const filePath = diagnostic.file.pathAbsolute; | ||
//ensure the file entry exists | ||
if (!result[filePath]) { | ||
result[filePath] = []; | ||
} | ||
const diagnosticMap = result[filePath]; | ||
|
||
diagnostic.key = | ||
diagnostic.file.pathAbsolute.toLowerCase() + '-' + | ||
diagnostic.code + '-' + | ||
diagnostic.range.start.line + '-' + | ||
diagnostic.range.start.character + '-' + | ||
diagnostic.range.end.line + '-' + | ||
diagnostic.range.end.character + | ||
diagnostic.message; | ||
|
||
//don't include duplicates | ||
if (!keys[diagnostic.key]) { | ||
keys[diagnostic.key] = true; | ||
diagnosticMap.push(diagnostic); | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
/** | ||
* Get a patch for all the files that have been removed since last time | ||
*/ | ||
private getRemovedPatch(currentDiagnosticsByFile: { [fileSrcPathLower: string]: KeyedDiagnostic[] }) { | ||
const result = {} as { [fileSrcPathLower: string]: KeyedDiagnostic[] }; | ||
for (const filePath in this.previousDiagnosticsByFile) { | ||
if (!currentDiagnosticsByFile[filePath]) { | ||
result[filePath] = []; | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
/** | ||
* Get all files whose diagnostics have changed since last time | ||
*/ | ||
private getModifiedPatch(currentDiagnosticsByFile: { [fileSrcPathLower: string]: KeyedDiagnostic[] }) { | ||
const result = {} as { [fileSrcPathLower: string]: KeyedDiagnostic[] }; | ||
for (const filePath in currentDiagnosticsByFile) { | ||
//for this file, if there were diagnostics last time AND there are diagnostics this time, and the lists are different | ||
if (this.previousDiagnosticsByFile[filePath] && !this.diagnosticListsAreIdentical(this.previousDiagnosticsByFile[filePath], currentDiagnosticsByFile[filePath])) { | ||
result[filePath] = currentDiagnosticsByFile[filePath]; | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
/** | ||
* Determine if two diagnostic lists are identical | ||
*/ | ||
private diagnosticListsAreIdentical(list1: KeyedDiagnostic[], list2: KeyedDiagnostic[]) { | ||
//skip all checks if the lists are not the same size | ||
if (list1.length !== list2.length) { | ||
return false; | ||
} | ||
for (let i = 0; i < list1.length; i++) { | ||
if (list1[i].key !== list2[i].key) { | ||
return false; | ||
} | ||
} | ||
|
||
//if we made it here, the lists are identical | ||
return true; | ||
} | ||
|
||
/** | ||
* Get diagnostics for all new files not seen since last time | ||
*/ | ||
private getAddedPatch(currentDiagnosticsByFile: { [fileSrcPathLower: string]: KeyedDiagnostic[] }) { | ||
const result = {} as { [fileSrcPathLower: string]: KeyedDiagnostic[] }; | ||
for (const filePath in currentDiagnosticsByFile) { | ||
if (!this.previousDiagnosticsByFile[filePath]) { | ||
result[filePath] = currentDiagnosticsByFile[filePath]; | ||
} | ||
} | ||
return result; | ||
} | ||
} | ||
|
||
interface KeyedDiagnostic extends BsDiagnostic { | ||
key: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters