Skip to content

Commit 3b2a850

Browse files
authored
Calculate the symbol table separately from debounced linting (#584)
1 parent ee3c4d5 commit 3b2a850

8 files changed

Lines changed: 194 additions & 15 deletions

File tree

packages/language-server/src/linting.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
_internalFeatureFlags,
33
clampUnsafePositions,
44
parserWrapper,
5+
SymbolTable,
56
} from '@neo4j-cypher/language-support';
67
import { Neo4jSchemaPoller } from '@neo4j-cypher/query-tools';
78
import debounce from 'lodash.debounce';
@@ -19,7 +20,7 @@ const defaultWorkerPath = join(__dirname, 'lintWorker.cjs');
1920

2021
let pool = workerpool.pool(defaultWorkerPath, {
2122
minWorkers: 2,
22-
workerTerminateTimeout: 2000,
23+
workerTerminateTimeout: 0,
2324
});
2425
export let workerPath = defaultWorkerPath;
2526
let linterVersion: string | undefined = undefined;
@@ -37,14 +38,15 @@ export async function setLintWorker(
3738
linterVersion = linter;
3839
pool = workerpool.pool(workerPath, {
3940
minWorkers: 2,
40-
workerTerminateTimeout: 2000,
41+
workerTerminateTimeout: 0,
4142
});
4243
}
4344
}
4445

4546
async function rawLintDocument(
4647
document: TextDocument,
4748
sendDiagnostics: (diagnostics: Diagnostic[]) => void,
49+
notifySymbolTableDone: (symbolTable: SymbolTable[]) => Promise<void>,
4850
neo4j: Neo4jSchemaPoller,
4951
) {
5052
const query = document.getText();
@@ -67,7 +69,6 @@ async function rawLintDocument(
6769
fixedDbSchema,
6870
_internalFeatureFlags,
6971
);
70-
7172
const result = await lastSemanticJob;
7273

7374
//marks the entire text if any position is negative
@@ -78,10 +79,13 @@ async function rawLintDocument(
7879

7980
// Pass the computed symbol tables to the parser
8081
if (result.symbolTables) {
81-
parserWrapper.setSymbolsInfo({
82-
query,
83-
symbolTables: result.symbolTables,
84-
});
82+
parserWrapper.setSymbolsInfo(
83+
{
84+
query,
85+
symbolTables: result.symbolTables,
86+
},
87+
notifySymbolTableDone,
88+
);
8589
}
8690

8791
sendDiagnostics(positionSafeResult);

packages/language-server/src/server.ts

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ import {
1111
} from 'vscode-languageserver/node';
1212

1313
import { TextDocument } from 'vscode-languageserver-textdocument';
14-
import { syntaxColouringLegend } from '@neo4j-cypher/language-support';
14+
import {
15+
DbSchema,
16+
parserWrapper,
17+
SymbolTable,
18+
syntaxColouringLegend,
19+
} from '@neo4j-cypher/language-support';
1520
import { Neo4jSchemaPoller } from '@neo4j-cypher/query-tools';
1621
import { doAutoCompletion } from './autocompletion';
1722
import { formatDocument } from './formatting';
@@ -24,15 +29,82 @@ import {
2429
Neo4jParameters,
2530
Neo4jSettings,
2631
} from './types';
32+
import workerpool from 'workerpool';
33+
import { join } from 'path';
34+
import { LintWorker } from '@neo4j-cypher/lint-worker';
35+
36+
class SymbolFetcher {
37+
private processing = false;
38+
private nextJob: {
39+
query: string;
40+
uri: string;
41+
schema: DbSchema;
42+
};
43+
private defaultWorkerPath = join(__dirname, 'lintWorker.cjs');
44+
private symbolTablePool = workerpool.pool(this.defaultWorkerPath, {
45+
maxWorkers: 1,
46+
workerTerminateTimeout: 0,
47+
});
48+
49+
public queueSymbolJob(query: string, uri: string, schema: DbSchema) {
50+
this.nextJob = { query, uri, schema };
51+
if (!this.processing) {
52+
void this.processJobQueue();
53+
}
54+
}
55+
56+
private async processJobQueue() {
57+
this.processing = true;
58+
const proxyWorker =
59+
(await this.symbolTablePool.proxy()) as unknown as LintWorker;
60+
while (this.nextJob) {
61+
try {
62+
const query = this.nextJob.query;
63+
const dbSchema = this.nextJob.schema;
64+
const docUri = this.nextJob.uri;
65+
this.nextJob = undefined;
66+
67+
const result = await proxyWorker.lintCypherQuery(query, dbSchema);
68+
69+
if (
70+
//if this.nextJob has new doc, our result is no longer valid
71+
result.symbolTables &&
72+
!(this.nextJob && this.nextJob.uri != docUri)
73+
) {
74+
parserWrapper.setSymbolsInfo(
75+
{
76+
query,
77+
symbolTables: result.symbolTables,
78+
},
79+
async (symbolTables: SymbolTable[]) =>
80+
await connection.sendNotification('symbolTableDone', {
81+
symbolTables,
82+
}),
83+
);
84+
}
85+
} catch (e) {
86+
//eslint-disable-next-line
87+
console.log('Symbol table calculation failed');
88+
}
89+
}
90+
this.processing = false;
91+
}
92+
}
2793

2894
const connection = createConnection(ProposedFeatures.all);
2995
let settings: Neo4jSettings | undefined = undefined;
3096

3197
// Create a simple text document manager.
3298
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
3399
const neo4jSchemaPoller = new Neo4jSchemaPoller();
100+
const symbolFetcher = new SymbolFetcher();
34101

35102
async function lintSingleDocument(document: TextDocument): Promise<void> {
103+
symbolFetcher.queueSymbolJob(
104+
document.getText(),
105+
document.uri,
106+
neo4jSchemaPoller?.metadata?.dbSchema,
107+
);
36108
if (settings?.features?.linting) {
37109
return lintDocument(
38110
document,
@@ -42,6 +114,10 @@ async function lintSingleDocument(document: TextDocument): Promise<void> {
42114
diagnostics,
43115
});
44116
},
117+
async (symbolTables: SymbolTable[]) =>
118+
await connection.sendNotification('symbolTableDone', {
119+
symbolTables: symbolTables,
120+
}),
45121
neo4jSchemaPoller,
46122
);
47123
} else {
@@ -141,6 +217,25 @@ connection.onNotification(
141217
},
142218
);
143219

220+
connection.onNotification(
221+
'fetchSymbolTable',
222+
(params: {
223+
query: string;
224+
uri: string;
225+
version: number;
226+
schema: DbSchema;
227+
}) => {
228+
neo4jSchemaPoller.events.once(
229+
'schemaFetched',
230+
void symbolFetcher.queueSymbolJob(
231+
params.query,
232+
params.uri,
233+
params.schema,
234+
),
235+
);
236+
},
237+
);
238+
144239
connection.onNotification('updateParameters', (parameters: Neo4jParameters) => {
145240
neo4jSchemaPoller.setParameters(parameters);
146241
relintAllDocuments();

packages/language-server/src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { DbSchema } from '@neo4j-cypher/language-support';
2+
13
// These settings are defined in the package.json
24
export type Neo4jConnectionSettings = {
35
connect?: boolean;
@@ -12,6 +14,12 @@ export type LintWorkerSettings = {
1214
linterVersion: string;
1315
};
1416

17+
export type SymbolFetchingParams = {
18+
query: string;
19+
uri: string;
20+
schema: DbSchema;
21+
};
22+
1523
export type Neo4jSettings = {
1624
trace: {
1725
server: 'off' | 'messages' | 'verbose';

packages/language-support/src/parserWrapper.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
cypherVersionNumbers,
4242
allCypherVersions,
4343
SymbolsInfo,
44+
SymbolTable,
4445
} from './types';
4546

4647
export interface ParsedStatement {
@@ -820,7 +821,12 @@ class ParserWrapper {
820821
this.symbolsInfo = undefined;
821822
}
822823

823-
setSymbolsInfo(symbolsInfo: SymbolsInfo) {
824+
setSymbolsInfo(
825+
symbolsInfo: SymbolsInfo,
826+
sendMessage?: (symbolTables: SymbolTable[]) => Promise<void>,
827+
) {
828+
if (_internalFeatureFlags.schemaBasedPatternCompletion && sendMessage)
829+
void sendMessage(symbolsInfo.symbolTables);
824830
this.symbolsInfo = symbolsInfo;
825831
}
826832
}

packages/vscode-extension/src/contextService.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import {
22
LintWorkerSettings,
33
Neo4jConnectionSettings,
4+
SymbolFetchingParams,
45
} from '@neo4j-cypher/language-server/src/types';
56
import { Neo4jSchemaPoller } from '@neo4j-cypher/query-tools';
67
import { ExtensionContext } from 'vscode';
78
import CypherRunner from './cypherRunner';
9+
import { SymbolTable } from '@neo4j-cypher/language-support';
810

911
type LanguageClient = {
1012
sendNotification: (
1113
method: string,
12-
settings?: Neo4jConnectionSettings | LintWorkerSettings,
14+
params?:
15+
| Neo4jConnectionSettings
16+
| LintWorkerSettings
17+
| { symbolTable: SymbolTable }
18+
| SymbolFetchingParams,
1319
) => Promise<void>;
1420
};
1521

packages/vscode-extension/src/extension.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@ import {
1818
disconnectDatabaseConnectionOnExtensionDeactivation,
1919
reconnectDatabaseConnectionOnExtensionActivation,
2020
} from './connectionService';
21-
import { setContext } from './contextService';
21+
import { getSchemaPoller, setContext } from './contextService';
2222
import { sendParametersToLanguageServer } from './parameterService';
2323
import { registerDisposables } from './registrationService';
24+
import { SymbolTable } from '@neo4j-cypher/language-support';
25+
import { sendNotificationToLanguageClient } from './languageClientService';
2426

2527
let client: LanguageClient;
28+
let symbolTableVersion = 0;
2629

2730
export const linterStatusBarItem = window.createStatusBarItem(
2831
StatusBarAlignment.Right,
@@ -96,6 +99,48 @@ export async function activate(context: ExtensionContext) {
9699

97100
context.subscriptions.push(watcher);
98101
}
102+
103+
client.onNotification('symbolTableDone', (params) => {
104+
symbolTableVersion++;
105+
const symbolTables = (params as { symbolTables: SymbolTable[] })
106+
.symbolTables;
107+
void window.showInformationMessage(
108+
'Calculated symbol table nbr' +
109+
symbolTableVersion +
110+
'\n' +
111+
stringifySymbolTables(symbolTables),
112+
);
113+
});
114+
115+
window.onDidChangeActiveTextEditor((editor) => {
116+
const doc = editor.document;
117+
const query = doc.getText();
118+
const uri = doc.uri.fsPath;
119+
const schema = getSchemaPoller().metadata?.dbSchema;
120+
void sendNotificationToLanguageClient('fetchSymbolTable', {
121+
query,
122+
uri,
123+
schema,
124+
});
125+
});
126+
}
127+
128+
function stringifySymbolTables(symbolTables: SymbolTable[]): string {
129+
if (!symbolTables) {
130+
return '';
131+
}
132+
return symbolTables
133+
.map((symbolTable) => {
134+
if (symbolTable.length == 0) {
135+
return '';
136+
}
137+
let result = ' [';
138+
symbolTable.map((symbol) => {
139+
result += symbol.variable + ': ' + symbol.types.toString() + ', ';
140+
});
141+
return result.substring(0, result.length - 2) + ']';
142+
})
143+
.toString();
99144
}
100145

101146
export async function deactivate(): Promise<void> | undefined {
Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import {
22
LintWorkerSettings,
33
Neo4jConnectionSettings,
4+
SymbolFetchingParams,
45
} from '@neo4j-cypher/language-server/src/types';
56
import { getLanguageClient } from './contextService';
7+
import { SymbolTable } from '@neo4j-cypher/language-support';
68

79
export type MethodName =
810
| 'connectionUpdated'
911
| 'connectionDisconnected'
1012
| 'updateParameters'
11-
| 'updateLintWorker';
13+
| 'updateLintWorker'
14+
| 'symbolTableDone'
15+
| 'fetchSymbolTable';
1216

1317
/**
1418
* Communicates to the language client that a connection has been updated or disconnected and needs to take action.
@@ -17,8 +21,12 @@ export type MethodName =
1721
*/
1822
export async function sendNotificationToLanguageClient(
1923
methodName: MethodName,
20-
settings?: Neo4jConnectionSettings | LintWorkerSettings,
24+
params?:
25+
| Neo4jConnectionSettings
26+
| LintWorkerSettings
27+
| { symbolTable: SymbolTable }
28+
| SymbolFetchingParams,
2129
) {
2230
const languageClient = getLanguageClient();
23-
await languageClient.sendNotification(methodName, settings);
31+
await languageClient.sendNotification(methodName, params);
2432
}

packages/vscode-extension/tests/mocks/mockLanguageClient.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@ import {
22
LintWorkerSettings,
33
Neo4jConnectionSettings,
44
Neo4jSettings,
5+
SymbolFetchingParams,
56
} from '@neo4j-cypher/language-server/src/types';
7+
import { SymbolTable } from '@neo4j-cypher/language-support';
68

79
export class MockLanguageClient {
810
async sendNotification(
911
methodName: string,
10-
settings?: LintWorkerSettings | Neo4jConnectionSettings | Neo4jSettings,
12+
settings?:
13+
| LintWorkerSettings
14+
| Neo4jConnectionSettings
15+
| Neo4jSettings
16+
| { symbolTable: SymbolTable }
17+
| SymbolFetchingParams,
1118
): Promise<void> {
1219
if (settings) {
1320
if ('trace' in settings) {

0 commit comments

Comments
 (0)