Skip to content

Commit 039d1c5

Browse files
authored
Makes the semantic analysis work with partial queries (#519)
1 parent b9410f1 commit 039d1c5

File tree

7 files changed

+11306
-11103
lines changed

7 files changed

+11306
-11103
lines changed

.changeset/polite-dolls-glow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@neo4j-cypher/language-support': patch
3+
---
4+
5+
Makes the semantic analysis work with partial queries

packages/language-support/src/syntaxValidation/semanticAnalysis.js

Lines changed: 11048 additions & 11074 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/language-support/src/tests/syntaxValidation/semanticValidation.test.ts

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ describe('Semantic validation spec', () => {
337337
]);
338338
});
339339

340-
test('Does not trigger semantic errors when there are syntactic errors', () => {
340+
test('Does not trigger semantic errors when there are irrecoverable syntactic errors', () => {
341341
const query = 'METCH (n) RETURN m';
342342

343343
expect(getDiagnosticsForQuery({ query })).toEqual([
@@ -406,6 +406,85 @@ describe('Semantic validation spec', () => {
406406
]);
407407
});
408408

409+
test('Shows errors for undefined variables on queries that are not completed', () => {
410+
const query = 'MATCH (n) RETURN a,b,n,c,';
411+
412+
expect(getDiagnosticsForQuery({ query })).toEqual([
413+
{
414+
message: 'Variable `a` not defined',
415+
offsets: {
416+
end: 25,
417+
start: 17,
418+
},
419+
range: {
420+
end: {
421+
character: 25,
422+
line: 0,
423+
},
424+
start: {
425+
character: 17,
426+
line: 0,
427+
},
428+
},
429+
severity: 1,
430+
},
431+
{
432+
message: 'Variable `b` not defined',
433+
offsets: {
434+
end: 20,
435+
start: 19,
436+
},
437+
range: {
438+
end: {
439+
character: 20,
440+
line: 0,
441+
},
442+
start: {
443+
character: 19,
444+
line: 0,
445+
},
446+
},
447+
severity: 1,
448+
},
449+
{
450+
message: 'Variable `c` not defined',
451+
offsets: {
452+
end: 24,
453+
start: 23,
454+
},
455+
range: {
456+
end: {
457+
character: 24,
458+
line: 0,
459+
},
460+
start: {
461+
character: 23,
462+
line: 0,
463+
},
464+
},
465+
severity: 1,
466+
},
467+
{
468+
message: "Invalid input '': expected an expression",
469+
offsets: {
470+
end: 25,
471+
start: 25,
472+
},
473+
range: {
474+
end: {
475+
character: 25,
476+
line: 0,
477+
},
478+
start: {
479+
character: 25,
480+
line: 0,
481+
},
482+
},
483+
severity: 1,
484+
},
485+
]);
486+
});
487+
409488
test('Handles multiple statements in semantic analysis', () => {
410489
const query = `MATCH (n) RETURN m;
411490
@@ -1297,31 +1376,6 @@ Attempted to access graph other`,
12971376
]);
12981377
});
12991378

1300-
test('Shows errors about semantic features not enabled yet in Cypher 25', () => {
1301-
const query = 'CYPHER 25 MATCH DIFFERENT RELATIONSHIP (n) RETURN n';
1302-
1303-
expect(getDiagnosticsForQuery({ query })).toEqual([
1304-
{
1305-
message: `Match modes such as \`DIFFERENT RELATIONSHIPS\` are not supported yet.`,
1306-
offsets: {
1307-
end: 38,
1308-
start: 16,
1309-
},
1310-
range: {
1311-
end: {
1312-
character: 38,
1313-
line: 0,
1314-
},
1315-
start: {
1316-
character: 16,
1317-
line: 0,
1318-
},
1319-
},
1320-
severity: 1,
1321-
},
1322-
]);
1323-
});
1324-
13251379
test('Shows errors for pattern selectors', () => {
13261380
const query = `MATCH
13271381
p1 = ANY 2 PATHS (a)-->*(c)-->(c),

packages/language-support/src/tests/syntaxValidation/syntacticValidation.test.ts

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,25 @@ describe('Syntactic validation spec', () => {
5050
const query = 'MATCH (n:Person) WERE';
5151

5252
expect(getDiagnosticsForQuery({ query })).toEqual([
53+
{
54+
message:
55+
'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
56+
offsets: {
57+
end: 21,
58+
start: 0,
59+
},
60+
range: {
61+
end: {
62+
character: 21,
63+
line: 0,
64+
},
65+
start: {
66+
character: 0,
67+
line: 0,
68+
},
69+
},
70+
severity: 1,
71+
},
5372
{
5473
message:
5574
"Invalid input 'WERE': expected a graph pattern, ',', 'ORDER BY', 'CALL', 'CREATE', 'LOAD CSV', 'DELETE', 'DETACH', 'FINISH', 'FOREACH', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REMOVE', 'RETURN', 'SET', 'SKIP', 'UNION', 'UNWIND', 'USE', 'USING', 'WHERE', 'WITH' or <EOF>",
@@ -76,6 +95,25 @@ describe('Syntactic validation spec', () => {
7695
const query = "MATCH (n:Person) WERE n.name = 'foo'";
7796

7897
expect(getDiagnosticsForQuery({ query })).toEqual([
98+
{
99+
message:
100+
'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
101+
offsets: {
102+
end: 21,
103+
start: 0,
104+
},
105+
range: {
106+
end: {
107+
character: 21,
108+
line: 0,
109+
},
110+
start: {
111+
character: 0,
112+
line: 0,
113+
},
114+
},
115+
severity: 1,
116+
},
79117
{
80118
message:
81119
"Invalid input 'WERE': expected a graph pattern, ',', 'ORDER BY', 'CALL', 'CREATE', 'LOAD CSV', 'DELETE', 'DETACH', 'FINISH', 'FOREACH', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REMOVE', 'RETURN', 'SET', 'SKIP', 'UNION', 'UNWIND', 'USE', 'USING', 'WHERE', 'WITH' or <EOF>",
@@ -106,6 +144,63 @@ describe('Syntactic validation spec', () => {
106144
`;
107145

108146
expect(getDiagnosticsForQuery({ query })).toEqual([
147+
{
148+
message:
149+
'CALL subquery without a variable scope clause is now deprecated. Use CALL () { ... }',
150+
offsets: {
151+
end: 114,
152+
start: 60,
153+
},
154+
range: {
155+
end: {
156+
character: 47,
157+
line: 2,
158+
},
159+
start: {
160+
character: 19,
161+
line: 1,
162+
},
163+
},
164+
severity: 2,
165+
},
166+
{
167+
message:
168+
'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
169+
offsets: {
170+
end: 112,
171+
start: 89,
172+
},
173+
range: {
174+
end: {
175+
character: 45,
176+
line: 2,
177+
},
178+
start: {
179+
character: 22,
180+
line: 2,
181+
},
182+
},
183+
severity: 1,
184+
},
185+
{
186+
message:
187+
'Variable in subquery is shadowing a variable with the same name from the outer scope. If you want to use that variable instead, it must be imported into the subquery using importing WITH clause. (the shadowing variable is: n)',
188+
offsets: {
189+
end: 97,
190+
start: 96,
191+
},
192+
range: {
193+
end: {
194+
character: 30,
195+
line: 2,
196+
},
197+
start: {
198+
character: 29,
199+
line: 2,
200+
},
201+
},
202+
severity: 2,
203+
},
109204
{
110205
message:
111206
"Invalid input 'n': expected an expression, 'ORDER BY', 'CALL', 'CREATE', 'LOAD CSV', 'DELETE', 'DETACH', 'FINISH', 'FOREACH', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REMOVE', 'RETURN', 'SET', 'SKIP', 'UNION', 'UNWIND', 'USE', 'WITH' or '}'",
@@ -728,6 +823,44 @@ describe('Syntactic validation spec', () => {
728823
query,
729824
}),
730825
).toEqual([
826+
{
827+
message:
828+
'CALL subquery without a variable scope clause is now deprecated. Use CALL () { ... }',
829+
offsets: {
830+
end: 41,
831+
start: 0,
832+
},
833+
range: {
834+
end: {
835+
character: 41,
836+
line: 0,
837+
},
838+
start: {
839+
character: 0,
840+
line: 0,
841+
},
842+
},
843+
severity: 2,
844+
},
845+
{
846+
message:
847+
'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
848+
offsets: {
849+
end: 25,
850+
start: 7,
851+
},
852+
range: {
853+
end: {
854+
character: 25,
855+
line: 0,
856+
},
857+
start: {
858+
character: 7,
859+
line: 0,
860+
},
861+
},
862+
severity: 1,
863+
},
731864
{
732865
message: "Invalid input '}': expected an expression, '*' or 'DISTINCT'",
733866
offsets: {
@@ -938,6 +1071,24 @@ describe('Syntactic validation spec', () => {
9381071
query,
9391072
}),
9401073
).toEqual([
1074+
{
1075+
message: 'No auth given for user.',
1076+
offsets: {
1077+
end: 39,
1078+
start: 0,
1079+
},
1080+
range: {
1081+
end: {
1082+
character: 22,
1083+
line: 1,
1084+
},
1085+
start: {
1086+
character: 0,
1087+
line: 0,
1088+
},
1089+
},
1090+
severity: 1,
1091+
},
9411092
{
9421093
message:
9431094
"Invalid input 'foo': expected a parameter, a string or 'CHANGE'",
@@ -997,6 +1148,25 @@ describe('Syntactic validation spec', () => {
9971148
query,
9981149
}),
9991150
).toEqual([
1151+
{
1152+
message:
1153+
'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
1154+
offsets: {
1155+
end: 41,
1156+
start: 0,
1157+
},
1158+
range: {
1159+
end: {
1160+
character: 41,
1161+
line: 0,
1162+
},
1163+
start: {
1164+
character: 0,
1165+
line: 0,
1166+
},
1167+
},
1168+
severity: 1,
1169+
},
10001170
{
10011171
message: `Invalid input '"foo"': expected '}' or an integer value`,
10021172
offsets: {

packages/react-codemirror/src/e2e_tests/e2eUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export class CypherEditorPage {
8989
the second interaction on, we won't be able to see that second element
9090
*/
9191
await this.page.mouse.move(0, 0);
92-
// Make the sure the tooltip closed
92+
// Make sure the tooltip closed
9393
await expect(
9494
this.page.locator('.cm-tooltip-hover').last(),
9595
).not.toBeVisible();

packages/vscode-extension/src/commandHandlers/params.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export function validateParamInput(
3939
);
4040
if (errors.length > 0) {
4141
return (
42-
'Value cannot be evaluated: ' + errors.map((e) => e.message).join(' ')
42+
'Value cannot be evaluated: ' + errors.map((e) => e.message).join('. ')
4343
);
4444
}
4545
return undefined;

packages/vscode-extension/tests/specs/unit/paramUnitTest.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ suite('Parameter validation spec', () => {
2727
test('Parameter validation fails for incorrect inputs', () => {
2828
assert.strictEqual(
2929
validateParamInput('datetime(', dbSchema),
30-
"Value cannot be evaluated: Invalid input '(': expected an expression, ',', 'AS', 'ORDER BY', 'CALL', 'CREATE', 'LOAD CSV', 'DELETE', 'DETACH', 'FINISH', 'FOREACH', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REMOVE', 'RETURN', 'SET', 'SKIP', 'UNION', 'UNWIND', 'USE', 'WITH' or <EOF>",
30+
"Value cannot be evaluated: Variable `datetime` not defined. Invalid input '(': expected an expression, ',', 'AS', 'ORDER BY', 'CALL', 'CREATE', 'LOAD CSV', 'DELETE', 'DETACH', 'FINISH', 'FOREACH', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REMOVE', 'RETURN', 'SET', 'SKIP', 'UNION', 'UNWIND', 'USE', 'WITH' or <EOF>",
3131
);
3232
assert.strictEqual(
3333
validateParamInput('500q', dbSchema),

0 commit comments

Comments
 (0)