Skip to content

Commit

Permalink
Moves syntax errors to the semantic analysis worker (#317)
Browse files Browse the repository at this point in the history
  • Loading branch information
ncordon authored Jan 9, 2025
1 parent 09b7676 commit 043d766
Show file tree
Hide file tree
Showing 20 changed files with 132,604 additions and 131,978 deletions.
5 changes: 5 additions & 0 deletions .changeset/silly-bugs-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@neo4j-cypher/language-support': patch
---

Moves the syntax errors to the semantic analysis web worker
12 changes: 5 additions & 7 deletions packages/language-server/src/lintWorker.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { validateSemantics } from '@neo4j-cypher/language-support';
import { lintCypherQuery } from '@neo4j-cypher/language-support';
import workerpool from 'workerpool';

workerpool.worker({ validateSemantics });
workerpool.worker({ lintCypherQuery });

type LinterArgs = Parameters<typeof validateSemantics>;
type LinterArgs = Parameters<typeof lintCypherQuery>;

export type LinterTask = workerpool.Promise<
ReturnType<typeof validateSemantics>
>;
export type LinterTask = workerpool.Promise<ReturnType<typeof lintCypherQuery>>;

export type LintWorker = {
validateSemantics: (...args: LinterArgs) => LinterTask;
lintCypherQuery: (...args: LinterArgs) => LinterTask;
};
29 changes: 11 additions & 18 deletions packages/language-server/src/linting.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { validateSyntax } from '@neo4j-cypher/language-support';
import { Neo4jSchemaPoller } from '@neo4j-cypher/schema-poller';
import debounce from 'lodash.debounce';
import { join } from 'path';
Expand Down Expand Up @@ -26,25 +25,19 @@ async function rawLintDocument(
}

const dbSchema = neo4j.metadata?.dbSchema ?? {};
const syntaxErrors = validateSyntax(query, dbSchema);

sendDiagnostics(syntaxErrors);

if (syntaxErrors.length === 0) {
try {
if (lastSemanticJob !== undefined && !lastSemanticJob.resolved) {
void lastSemanticJob.cancel();
}
try {
if (lastSemanticJob !== undefined && !lastSemanticJob.resolved) {
void lastSemanticJob.cancel();
}

const proxyWorker = (await pool.proxy()) as unknown as LintWorker;
lastSemanticJob = proxyWorker.validateSemantics(query, dbSchema);
const result = await lastSemanticJob;
const proxyWorker = (await pool.proxy()) as unknown as LintWorker;
lastSemanticJob = proxyWorker.lintCypherQuery(query, dbSchema);
const result = await lastSemanticJob;

sendDiagnostics(result);
} catch (err) {
if (!(err instanceof workerpool.Promise.CancellationError)) {
console.error(err);
}
sendDiagnostics(result);
} catch (err) {
if (!(err instanceof workerpool.Promise.CancellationError)) {
console.error(err);
}
}
}
Expand Down
8 changes: 2 additions & 6 deletions packages/language-support/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,8 @@ export {
syntaxColouringLegend,
} from './syntaxColouring/syntaxColouring';
export type { ParsedCypherToken } from './syntaxColouring/syntaxColouringHelpers';
export {
lintCypherQuery,
validateSemantics,
validateSyntax,
} from './syntaxValidation/syntaxValidation';
export type { SyntaxDiagnostic } from './syntaxValidation/syntaxValidationHelpers';
export { lintCypherQuery } from './syntaxValidation/syntaxValidation';
export type { SyntaxDiagnostic } from './syntaxValidation/syntaxValidation';
export { testData } from './tests/testData';
export { textMateGrammar } from './textMateGrammar';
export type { CompletionItem, Neo4jFunction, Neo4jProcedure } from './types';
Expand Down
32 changes: 19 additions & 13 deletions packages/language-support/src/parserWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ import {
rulesDefiningOrUsingVariables,
splitIntoStatements,
} from './helpers';
import {
SyntaxDiagnostic,
SyntaxErrorsListener,
} from './syntaxValidation/syntaxValidationHelpers';
import { SyntaxDiagnostic } from './syntaxValidation/syntaxValidation';
import { SyntaxErrorsListener } from './syntaxValidation/syntaxValidationHelpers';

export interface ParsedStatement {
command: ParsedCommand;
Expand Down Expand Up @@ -185,8 +183,11 @@ export function createParsingResult(query: string): ParsingResult {
tokens.find(
(t) => t.text !== '<EOF>' && t.type !== CypherLexer.SPACE,
) === undefined;
const syntaxErrors = isEmptyStatement ? [] : errorListener.errors;
const collectedCommand = parseToCommand(ctx, isEmptyStatement);
const collectedCommand = parseToCommand(ctx, tokens, isEmptyStatement);
const syntaxErrors =
collectedCommand.type !== 'cypher' && !isEmptyStatement
? errorListener.errors
: [];

if (!_internalFeatureFlags.consoleCommands) {
syntaxErrors.push(...errorOnNonCypherCommands(collectedCommand));
Expand Down Expand Up @@ -431,23 +432,27 @@ export type ParsedCommand = ParsedCommandNoPosition & RuleTokens;

function parseToCommand(
stmts: StatementsOrCommandsContext,
tokens: Token[],
isEmptyStatement: boolean,
): ParsedCommand {
const stmt = stmts.statementOrCommand_list().at(0);

if (stmt) {
const { start, stop } = stmt;
const start = stmts.start;
let stop = stmts.stop;

if (stop && stop.type === CypherLexer.SEMICOLON) {
stop = tokens[stop.tokenIndex - 1];
}
const inputstream = start.getInputStream();
const cypherStmt = stmt.preparsedStatement()?.statement();
if (cypherStmt) {
// we get the original text input to preserve whitespace
// stripping the preparser part of it
const inputstream = start.getInputStream();
const stmtStart = cypherStmt.start;
const stmtStop = cypherStmt.stop;
const statement = inputstream.getText(stmtStart.start, stmtStop.stop);
const statement = inputstream.getText(stmtStart.start, stop.stop);

return { type: 'cypher', statement, start: stmtStart, stop: stmtStop };
return { type: 'cypher', statement, start: stmtStart, stop: stop };
}

if (isEmptyStatement) {
Expand Down Expand Up @@ -567,7 +572,8 @@ function parseToCommand(

return { type: 'parse-error', start, stop };
}
return { type: 'parse-error', start, stop };
const statement = inputstream.getText(start.start, stop.stop);
return { type: 'cypher', statement, start: start, stop: stop };
}
return { type: 'parse-error', start: stmts.start, stop: stmts.stop };
}
Expand All @@ -589,7 +595,7 @@ function translateTokensToRange(
}
function errorOnNonCypherCommands(command: ParsedCommand): SyntaxDiagnostic[] {
return [command]
.filter((cmd) => cmd.type !== 'cypher' && cmd.type !== 'parse-error')
.filter((cmd) => cmd.type !== 'cypher')
.map(
({ start, stop }): SyntaxDiagnostic => ({
message: 'Console commands are unsupported in this environment.',
Expand Down
Loading

0 comments on commit 043d766

Please sign in to comment.