@neo4j-cypher/language-server 2.0.0-next.3 → 2.0.0-next.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +278 -1
- package/dist/cypher-language-server +2097 -68
- package/dist/server.js +204 -0
- package/package.json +38 -31
- package/src/autocompletion.ts +39 -18
- package/src/formatting.ts +34 -0
- package/src/linting.ts +105 -0
- package/src/server.ts +222 -42
- package/src/signatureHelp.ts +4 -8
- package/src/types.ts +24 -4
package/dist/server.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_1 = require("vscode-languageserver/node");
|
|
7
|
+
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
|
|
8
|
+
const language_support_1 = require("@neo4j-cypher/language-support");
|
|
9
|
+
const query_tools_1 = require("@neo4j-cypher/query-tools");
|
|
10
|
+
const autocompletion_1 = require("./autocompletion");
|
|
11
|
+
const formatting_1 = require("./formatting");
|
|
12
|
+
const linting_1 = require("./linting");
|
|
13
|
+
const signatureHelp_1 = require("./signatureHelp");
|
|
14
|
+
const syntaxColouring_1 = require("./syntaxColouring");
|
|
15
|
+
const workerpool_1 = __importDefault(require("workerpool"));
|
|
16
|
+
const lint_worker_1 = require("@neo4j-cypher/lint-worker");
|
|
17
|
+
const path_1 = require("path");
|
|
18
|
+
const defaultWorkerPath = (0, path_1.join)(__dirname, 'lintWorker.cjs');
|
|
19
|
+
let workerPath = defaultWorkerPath;
|
|
20
|
+
class SymbolFetcher {
|
|
21
|
+
processing = false;
|
|
22
|
+
nextJob;
|
|
23
|
+
symbolTablePool = workerpool_1.default.pool(defaultWorkerPath, {
|
|
24
|
+
maxWorkers: 1,
|
|
25
|
+
workerTerminateTimeout: 0,
|
|
26
|
+
});
|
|
27
|
+
linterVersion;
|
|
28
|
+
setLintWorker(lintWorkerPath = defaultWorkerPath, linterVersion) {
|
|
29
|
+
this.linterVersion = linterVersion;
|
|
30
|
+
this.symbolTablePool = workerpool_1.default.pool(lintWorkerPath, {
|
|
31
|
+
maxWorkers: 1,
|
|
32
|
+
workerTerminateTimeout: 0,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
queueSymbolJob(query, uri, schema) {
|
|
36
|
+
this.nextJob = { query, uri, schema };
|
|
37
|
+
if (!this.processing) {
|
|
38
|
+
void this.processJobQueue();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async processJobQueue() {
|
|
42
|
+
this.processing = true;
|
|
43
|
+
while (this.nextJob) {
|
|
44
|
+
try {
|
|
45
|
+
const proxyWorker = (await this.symbolTablePool.proxy());
|
|
46
|
+
const query = this.nextJob.query;
|
|
47
|
+
const dbSchema = this.nextJob.schema;
|
|
48
|
+
const docUri = this.nextJob.uri;
|
|
49
|
+
this.nextJob = undefined;
|
|
50
|
+
const fixedDbSchema = (0, lint_worker_1.convertDbSchema)(dbSchema, this.linterVersion);
|
|
51
|
+
const result = await proxyWorker.lintCypherQuery(query, fixedDbSchema);
|
|
52
|
+
if (
|
|
53
|
+
//if this.nextJob has new doc, our result is no longer valid
|
|
54
|
+
result.symbolTables &&
|
|
55
|
+
!(this.nextJob && this.nextJob.uri != docUri)) {
|
|
56
|
+
language_support_1.parserWrapper.setSymbolsInfo({
|
|
57
|
+
query,
|
|
58
|
+
symbolTables: result.symbolTables,
|
|
59
|
+
}, async (symbolTables) => await connection.sendNotification('symbolTableDone', {
|
|
60
|
+
symbolTables,
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
//eslint-disable-next-line
|
|
66
|
+
console.log('Symbol table calculation failed');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
this.processing = false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const connection = (0, node_1.createConnection)(node_1.ProposedFeatures.all);
|
|
73
|
+
let settings = undefined;
|
|
74
|
+
// Create a simple text document manager.
|
|
75
|
+
const documents = new node_1.TextDocuments(vscode_languageserver_textdocument_1.TextDocument);
|
|
76
|
+
const neo4jSchemaPoller = new query_tools_1.Neo4jSchemaPoller();
|
|
77
|
+
const symbolFetcher = new SymbolFetcher();
|
|
78
|
+
async function lintSingleDocument(document) {
|
|
79
|
+
symbolFetcher.queueSymbolJob(document.getText(), document.uri, neo4jSchemaPoller?.metadata?.dbSchema);
|
|
80
|
+
if (settings?.features?.linting) {
|
|
81
|
+
return (0, linting_1.lintDocument)(document, (diagnostics) => {
|
|
82
|
+
void connection.sendDiagnostics({
|
|
83
|
+
uri: document.uri,
|
|
84
|
+
diagnostics,
|
|
85
|
+
});
|
|
86
|
+
}, async (symbolTables) => await connection.sendNotification('symbolTableDone', {
|
|
87
|
+
symbolTables: symbolTables,
|
|
88
|
+
}), neo4jSchemaPoller);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
void connection.sendDiagnostics({
|
|
92
|
+
uri: document.uri,
|
|
93
|
+
diagnostics: [],
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function relintAllDocuments() {
|
|
98
|
+
void documents.all().map(lintSingleDocument);
|
|
99
|
+
}
|
|
100
|
+
connection.onInitialize(() => {
|
|
101
|
+
const result = {
|
|
102
|
+
capabilities: {
|
|
103
|
+
textDocumentSync: node_1.TextDocumentSyncKind.Full,
|
|
104
|
+
// Tell the client what features does the server support
|
|
105
|
+
completionProvider: {
|
|
106
|
+
resolveProvider: false,
|
|
107
|
+
triggerCharacters: ['.', ':', '{', '$', ')', ' ', ']', '-', '<'],
|
|
108
|
+
},
|
|
109
|
+
semanticTokensProvider: {
|
|
110
|
+
documentSelector: [{ language: 'cypher' }],
|
|
111
|
+
legend: language_support_1.syntaxColouringLegend,
|
|
112
|
+
range: false,
|
|
113
|
+
full: {
|
|
114
|
+
delta: false,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
signatureHelpProvider: {
|
|
118
|
+
triggerCharacters: ['(', ',', ')'],
|
|
119
|
+
},
|
|
120
|
+
documentFormattingProvider: true,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
return result;
|
|
124
|
+
});
|
|
125
|
+
connection.onInitialized(() => {
|
|
126
|
+
void connection.client.register(node_1.DidChangeConfigurationNotification.type, {
|
|
127
|
+
section: 'neo4j',
|
|
128
|
+
});
|
|
129
|
+
const registrationOptions = {
|
|
130
|
+
documentSelector: [{ language: 'cypher' }],
|
|
131
|
+
legend: language_support_1.syntaxColouringLegend,
|
|
132
|
+
range: false,
|
|
133
|
+
full: {
|
|
134
|
+
delta: false,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
void connection.client.register(node_1.SemanticTokensRegistrationType.type, registrationOptions);
|
|
138
|
+
});
|
|
139
|
+
connection.onDidChangeConfiguration((params) => {
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
141
|
+
settings = params.settings?.neo4j;
|
|
142
|
+
relintAllDocuments();
|
|
143
|
+
});
|
|
144
|
+
documents.onDidChangeContent((change) => lintSingleDocument(change.document));
|
|
145
|
+
// Trigger the syntax colouring
|
|
146
|
+
connection.languages.semanticTokens.on((0, syntaxColouring_1.applySyntaxColouringForDocument)(documents));
|
|
147
|
+
// Trigger the signature help, providing info about functions / procedures
|
|
148
|
+
connection.onSignatureHelp((0, signatureHelp_1.doSignatureHelp)(documents, neo4jSchemaPoller));
|
|
149
|
+
// Trigger the auto completion
|
|
150
|
+
connection.onCompletion((0, autocompletion_1.doAutoCompletion)(documents, neo4jSchemaPoller));
|
|
151
|
+
connection.onNotification('updateLintWorker', (linterSettings) => {
|
|
152
|
+
const lintWorkerPath = linterSettings.lintWorkerPath;
|
|
153
|
+
const linterVersion = linterSettings.linterVersion;
|
|
154
|
+
if (lintWorkerPath !== workerPath) {
|
|
155
|
+
workerPath = lintWorkerPath;
|
|
156
|
+
void (async () => {
|
|
157
|
+
symbolFetcher.setLintWorker(workerPath, linterVersion);
|
|
158
|
+
await (0, linting_1.setLintWorker)(workerPath, linterVersion);
|
|
159
|
+
relintAllDocuments();
|
|
160
|
+
})();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
connection.onNotification('connectionUpdated', (connectionSettings) => {
|
|
164
|
+
changeConnection(connectionSettings);
|
|
165
|
+
neo4jSchemaPoller.events.once('schemaFetched', relintAllDocuments);
|
|
166
|
+
});
|
|
167
|
+
connection.onNotification('fetchSymbolTable', (params) => {
|
|
168
|
+
neo4jSchemaPoller.events.once('schemaFetched',
|
|
169
|
+
// oxlint-disable-next-line typescript-eslint/no-meaningless-void-operator
|
|
170
|
+
void symbolFetcher.queueSymbolJob(params.query, params.uri, params.schema));
|
|
171
|
+
});
|
|
172
|
+
connection.onNotification('updateParameters', (parameters) => {
|
|
173
|
+
neo4jSchemaPoller.setParameters(parameters);
|
|
174
|
+
relintAllDocuments();
|
|
175
|
+
});
|
|
176
|
+
connection.onDocumentFormatting((params) => {
|
|
177
|
+
return (0, formatting_1.formatDocument)(params, documents);
|
|
178
|
+
});
|
|
179
|
+
connection.onNotification('connectionDisconnected', () => {
|
|
180
|
+
disconnect();
|
|
181
|
+
relintAllDocuments();
|
|
182
|
+
});
|
|
183
|
+
documents.listen(connection);
|
|
184
|
+
connection.listen();
|
|
185
|
+
connection.onExit(() => {
|
|
186
|
+
void (0, linting_1.cleanupWorkers)();
|
|
187
|
+
});
|
|
188
|
+
const changeConnection = (connectionSettings) => {
|
|
189
|
+
disconnect();
|
|
190
|
+
if (neo4jSchemaPoller.connection === undefined &&
|
|
191
|
+
connectionSettings.connect &&
|
|
192
|
+
connectionSettings.password &&
|
|
193
|
+
connectionSettings.connectURL &&
|
|
194
|
+
connectionSettings.user) {
|
|
195
|
+
void neo4jSchemaPoller.persistentConnect(connectionSettings.connectURL, {
|
|
196
|
+
username: connectionSettings.user,
|
|
197
|
+
password: connectionSettings.password,
|
|
198
|
+
}, { appName: 'cypher-language-server' }, connectionSettings.database);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
const disconnect = () => {
|
|
202
|
+
neo4jSchemaPoller.disconnect();
|
|
203
|
+
};
|
|
204
|
+
//# sourceMappingURL=server.js.map
|
package/package.json
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neo4j-cypher/language-server",
|
|
3
|
+
"version": "2.0.0-next.31",
|
|
3
4
|
"description": "Cypher Language Server",
|
|
4
|
-
"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cypher",
|
|
7
|
+
"language server",
|
|
8
|
+
"neo4j"
|
|
9
|
+
],
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/neo4j/cypher-language-support/issues"
|
|
12
|
+
},
|
|
5
13
|
"license": "Apache-2.0",
|
|
14
|
+
"author": "Neo4j Inc.",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git://github.com/neo4j/cypher-language-support.git"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"cypher-language-server": "./dist/cypher-language-server"
|
|
21
|
+
},
|
|
6
22
|
"files": [
|
|
7
23
|
"./dist/cypher-language-server",
|
|
8
24
|
"./src",
|
|
@@ -11,42 +27,33 @@
|
|
|
11
27
|
"LICENSE.md",
|
|
12
28
|
"CHANGELOG.md"
|
|
13
29
|
],
|
|
14
|
-
"keywords": [
|
|
15
|
-
"neo4j",
|
|
16
|
-
"cypher",
|
|
17
|
-
"language server"
|
|
18
|
-
],
|
|
19
|
-
"version": "2.0.0-next.3",
|
|
20
30
|
"main": "./dist/server.js",
|
|
21
31
|
"types": "src/server.ts",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"axios": "^1.9.0",
|
|
34
|
+
"lodash.debounce": "^4.0.8",
|
|
35
|
+
"neo4j-driver": "6.0.1",
|
|
36
|
+
"vscode-languageserver": "^8.1.0",
|
|
37
|
+
"vscode-languageserver-textdocument": "^1.0.8",
|
|
38
|
+
"workerpool": "^9.3.3",
|
|
39
|
+
"@neo4j-cypher/query-tools": "2.0.0-next.30",
|
|
40
|
+
"@neo4j-cypher/language-support": "2.0.0-next.30",
|
|
41
|
+
"@neo4j-cypher/lint-worker": "1.10.1-next.7"
|
|
25
42
|
},
|
|
26
|
-
"
|
|
27
|
-
"
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/lodash.debounce": "^4.0.9",
|
|
45
|
+
"copyfiles": "^2.4.1"
|
|
28
46
|
},
|
|
29
|
-
"engineStrict": true,
|
|
30
47
|
"engines": {
|
|
31
|
-
"node": ">=
|
|
32
|
-
},
|
|
33
|
-
"bin": {
|
|
34
|
-
"cypher-language-server": "./dist/cypher-language-server"
|
|
35
|
-
},
|
|
36
|
-
"dependencies": {
|
|
37
|
-
"@neo4j-cypher/language-support": "2.0.0-next.2",
|
|
38
|
-
"neo4j-driver": "^5.3.0",
|
|
39
|
-
"vscode-languageserver": "^8.1.0",
|
|
40
|
-
"vscode-languageserver-textdocument": "^1.0.8"
|
|
48
|
+
"node": ">=24.11.1"
|
|
41
49
|
},
|
|
50
|
+
"engineStrict": true,
|
|
42
51
|
"scripts": {
|
|
43
|
-
"build": "tsc -b &&
|
|
44
|
-
"
|
|
52
|
+
"build": "tsc -b && pnpm bundle && pnpm make-executable && pnpm copy-lint-worker",
|
|
53
|
+
"copy-lint-worker": "copyfiles -u 4 ../lint-worker/dist/cjs/lintWorker.cjs dist/",
|
|
54
|
+
"bundle": "esbuild ./src/server.ts --bundle --format=cjs --platform=node --outfile=dist/cypher-language-server.js --minify --conditions=require",
|
|
55
|
+
"dev": "tsc --watch",
|
|
45
56
|
"make-executable": "cd dist && echo '#!/usr/bin/env node' > cypher-language-server && cat cypher-language-server.js >> cypher-language-server",
|
|
46
|
-
"clean": "rm -rf dist"
|
|
47
|
-
"watch": "tsc -b -w"
|
|
48
|
-
},
|
|
49
|
-
"devDependencies": {
|
|
50
|
-
"esbuild": "^0.19.4"
|
|
57
|
+
"clean": "rm -rf {dist,tsconfig.tsbuildinfo}"
|
|
51
58
|
}
|
|
52
|
-
}
|
|
59
|
+
}
|
package/src/autocompletion.ts
CHANGED
|
@@ -1,34 +1,55 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CompletionParams,
|
|
3
|
+
CompletionTriggerKind,
|
|
2
4
|
Position,
|
|
3
|
-
TextDocumentPositionParams,
|
|
4
5
|
TextDocuments,
|
|
5
6
|
} from 'vscode-languageserver/node';
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import type { CompletionItem } from '@neo4j-cypher/language-support';
|
|
9
|
+
import {
|
|
10
|
+
autocomplete,
|
|
11
|
+
shouldAutoCompleteYield,
|
|
12
|
+
} from '@neo4j-cypher/language-support';
|
|
13
|
+
import { Neo4jSchemaPoller } from '@neo4j-cypher/query-tools';
|
|
11
14
|
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
12
15
|
|
|
13
16
|
export function doAutoCompletion(
|
|
14
17
|
documents: TextDocuments<TextDocument>,
|
|
15
18
|
neo4j: Neo4jSchemaPoller,
|
|
16
19
|
) {
|
|
17
|
-
return (
|
|
18
|
-
const textDocument = documents.get(
|
|
20
|
+
return (completionParams: CompletionParams) => {
|
|
21
|
+
const textDocument = documents.get(completionParams.textDocument.uri);
|
|
19
22
|
if (textDocument === undefined) return [];
|
|
20
23
|
|
|
21
|
-
const position: Position =
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
end: position,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
return autocomplete(
|
|
30
|
-
textDocument.getText(range),
|
|
31
|
-
neo4j.metadata?.dbSchema ?? {},
|
|
24
|
+
const position: Position = completionParams.position;
|
|
25
|
+
const offset = textDocument.offsetAt(position);
|
|
26
|
+
const yieldTriggered = shouldAutoCompleteYield(
|
|
27
|
+
textDocument.getText(),
|
|
28
|
+
offset,
|
|
32
29
|
);
|
|
30
|
+
const manualOrCharacterOrInwordTriggered =
|
|
31
|
+
completionParams.context?.triggerCharacter !== ' ';
|
|
32
|
+
if (yieldTriggered || manualOrCharacterOrInwordTriggered) {
|
|
33
|
+
const completions: CompletionItem[] = autocomplete(
|
|
34
|
+
textDocument.getText(),
|
|
35
|
+
neo4j.metadata?.dbSchema ?? {},
|
|
36
|
+
offset,
|
|
37
|
+
completionParams.context.triggerKind === CompletionTriggerKind.Invoked,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const result = completions.map((item) => {
|
|
41
|
+
if (item.signature) {
|
|
42
|
+
return {
|
|
43
|
+
...item,
|
|
44
|
+
detail: item.detail + ' ' + item.signature,
|
|
45
|
+
signature: undefined,
|
|
46
|
+
};
|
|
47
|
+
} else {
|
|
48
|
+
return item;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
33
54
|
};
|
|
34
55
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { formatQuery } from '@neo4j-cypher/language-support';
|
|
2
|
+
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
3
|
+
import {
|
|
4
|
+
DocumentFormattingParams,
|
|
5
|
+
TextDocuments,
|
|
6
|
+
TextEdit,
|
|
7
|
+
} from 'vscode-languageserver/node';
|
|
8
|
+
|
|
9
|
+
export const formatDocument = (
|
|
10
|
+
params: DocumentFormattingParams,
|
|
11
|
+
documents: TextDocuments<TextDocument>,
|
|
12
|
+
): TextEdit[] => {
|
|
13
|
+
const document = documents.get(params.textDocument.uri);
|
|
14
|
+
if (!document) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const text = document.getText();
|
|
19
|
+
const formattedText = formatQuery(text).formattedQuery;
|
|
20
|
+
|
|
21
|
+
if (text === formattedText) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
TextEdit.replace(
|
|
27
|
+
{
|
|
28
|
+
start: { line: 0, character: 0 },
|
|
29
|
+
end: { line: document.lineCount, character: 0 },
|
|
30
|
+
},
|
|
31
|
+
formattedText,
|
|
32
|
+
),
|
|
33
|
+
];
|
|
34
|
+
};
|
package/src/linting.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
_internalFeatureFlags,
|
|
3
|
+
clampUnsafePositions,
|
|
4
|
+
parserWrapper,
|
|
5
|
+
SymbolTable,
|
|
6
|
+
} from '@neo4j-cypher/language-support';
|
|
7
|
+
import { Neo4jSchemaPoller } from '@neo4j-cypher/query-tools';
|
|
8
|
+
import debounce from 'lodash.debounce';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { Diagnostic } from 'vscode-languageserver';
|
|
11
|
+
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
12
|
+
import workerpool from 'workerpool';
|
|
13
|
+
import {
|
|
14
|
+
convertDbSchema,
|
|
15
|
+
LinterTask,
|
|
16
|
+
LintWorker,
|
|
17
|
+
} from '@neo4j-cypher/lint-worker';
|
|
18
|
+
|
|
19
|
+
const defaultWorkerPath = join(__dirname, 'lintWorker.cjs');
|
|
20
|
+
|
|
21
|
+
let pool = workerpool.pool(defaultWorkerPath, {
|
|
22
|
+
minWorkers: 2,
|
|
23
|
+
workerTerminateTimeout: 0,
|
|
24
|
+
});
|
|
25
|
+
let linterVersion: string | undefined = undefined;
|
|
26
|
+
let lastSemanticJob: LinterTask | undefined;
|
|
27
|
+
|
|
28
|
+
/**Sets the lintworker to the one specified by the given path, reverting to default if the path is undefined */
|
|
29
|
+
export async function setLintWorker(
|
|
30
|
+
lintWorkerPath: string | undefined = defaultWorkerPath,
|
|
31
|
+
linter: string | undefined,
|
|
32
|
+
) {
|
|
33
|
+
await cleanupWorkers();
|
|
34
|
+
linterVersion = linter;
|
|
35
|
+
pool = workerpool.pool(lintWorkerPath, {
|
|
36
|
+
minWorkers: 2,
|
|
37
|
+
workerTerminateTimeout: 0,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function rawLintDocument(
|
|
42
|
+
document: TextDocument,
|
|
43
|
+
sendDiagnostics: (diagnostics: Diagnostic[]) => void,
|
|
44
|
+
notifySymbolTableDone: (symbolTable: SymbolTable[]) => Promise<void>,
|
|
45
|
+
neo4j: Neo4jSchemaPoller,
|
|
46
|
+
) {
|
|
47
|
+
const query = document.getText();
|
|
48
|
+
if (query.length === 0) {
|
|
49
|
+
sendDiagnostics([]);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const dbSchema = neo4j.metadata?.dbSchema ?? {};
|
|
54
|
+
try {
|
|
55
|
+
if (lastSemanticJob !== undefined && !lastSemanticJob.resolved) {
|
|
56
|
+
void lastSemanticJob.cancel();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const proxyWorker = (await pool.proxy()) as unknown as LintWorker;
|
|
60
|
+
|
|
61
|
+
const fixedDbSchema = convertDbSchema(dbSchema, linterVersion);
|
|
62
|
+
lastSemanticJob = proxyWorker.lintCypherQuery(
|
|
63
|
+
query,
|
|
64
|
+
fixedDbSchema,
|
|
65
|
+
_internalFeatureFlags,
|
|
66
|
+
);
|
|
67
|
+
const result = await lastSemanticJob;
|
|
68
|
+
|
|
69
|
+
//marks the entire text if any position is negative
|
|
70
|
+
const positionSafeResult = clampUnsafePositions(
|
|
71
|
+
result.diagnostics,
|
|
72
|
+
document,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Pass the computed symbol tables to the parser
|
|
76
|
+
if (result.symbolTables) {
|
|
77
|
+
parserWrapper.setSymbolsInfo(
|
|
78
|
+
{
|
|
79
|
+
query,
|
|
80
|
+
symbolTables: result.symbolTables,
|
|
81
|
+
},
|
|
82
|
+
notifySymbolTableDone,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
sendDiagnostics(positionSafeResult);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (!(err instanceof workerpool.Promise.CancellationError)) {
|
|
89
|
+
console.error(err);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const lintDocument: typeof rawLintDocument = debounce(
|
|
95
|
+
rawLintDocument,
|
|
96
|
+
600,
|
|
97
|
+
{
|
|
98
|
+
leading: false,
|
|
99
|
+
trailing: true,
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
export const cleanupWorkers = async () => {
|
|
104
|
+
await pool.terminate();
|
|
105
|
+
};
|