Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/two-schools-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@neo4j-cypher/language-support": patch
"@neo4j-cypher/lint-worker": patch
---

Update grammar and semantic analysis to 2026.04
1 change: 1 addition & 0 deletions .lintstagedrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
],
"vendor/antlr4-c3/index.ts": ["bash check-imports.sh"],
"*.js": ["oxlint --fix --type-aware --max-warnings 0"],
"packages/vscode-extension/**/cypher.json": [],

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

had a clash with oxfmtrc for cypher.json. This file called oxfmt on all .json files, but oxfmtrc (rightly, since it's generated) removed cypher.json, causing an "Expected at least one target file"-error.

"*.json": ["oxfmt"],
"*.yaml": ["oxfmt"],
"*.yml": ["oxfmt"]
Expand Down
8 changes: 8 additions & 0 deletions packages/language-support/src/antlr-grammar/Cypher25Lexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,10 @@ LABEL
: L A B E L
;

LABELED
: L A B E L E D
;

LABELS
: L A B E L S
;
Expand Down Expand Up @@ -996,6 +1000,10 @@ PROPERTIES
: P R O P E R T I E S
;

PROPERTY_EXISTS
: P R O P E R T Y '_' E X I S T S
;

PROPERTY
: P R O P E R T Y
;
Expand Down
13 changes: 12 additions & 1 deletion packages/language-support/src/antlr-grammar/Cypher25Parser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ clause
| withClause
| filterClause
| unwindClause
| forListClause
| letClause
| callClause
| subqueryClause
Expand Down Expand Up @@ -265,6 +266,10 @@ unwindClause
: UNWIND expression AS variable
;

forListClause
: FOR variable IN expression

@anderson4j anderson4j Apr 8, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think we should add formatter handling for this new clause. Realised that we didnt consider this for a lot of new rules in the past, so thinking maybe I skip it for now and have a look at it all at once later? (this also lets us get in the updated artifacts to upx quicker - before the browser bundling on thursday)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PS. I think it can be confusing that I made changes in formatting.ts when I here say I want to fix formatting things later. The change below is more of a fix, since the labelComparison used to have formatting handled by a visitor of it's children, but now that the child-rule changed (it's no longer using labelExpression, which we handle, instead it's a straight up labelExpression4) we need to handle it manually to avoid regression

;

letClause
: LET letItem (COMMA letItem)*
;
Expand Down Expand Up @@ -544,6 +549,7 @@ expression8

expression7
: expression6 comparisonExpression6?
| propertyExistsPredicate
;

// Making changes here? Consider looking at extendedWhen too.
Expand All @@ -558,7 +564,7 @@ comparisonExpression6
| IS NOT? NULL # NullComparison
| (IS NOT? (TYPED | COLONCOLON) | COLONCOLON) type # TypeComparison
| IS NOT? normalForm? NORMALIZED # NormalFormComparison
| labelExpression # LabelComparison
| (IS NOT? LABELED? | COLON) labelExpression4 # LabelComparison
;

normalForm
Expand Down Expand Up @@ -779,6 +785,10 @@ existsExpression
: EXISTS LCURLY (queryWithLocalDefinitions | matchMode? patternList whereClause?) RCURLY
;

propertyExistsPredicate
: PROPERTY_EXISTS LPAREN variable COMMA propertyKeyName RPAREN
;

countExpression
: COUNT LCURLY (queryWithLocalDefinitions | matchMode? patternList whereClause?) RCURLY
;
Expand Down Expand Up @@ -2288,6 +2298,7 @@ unescapedSymbolicNameString_
| JOIN
| KEY
| LABEL
| LABELED
| LABELS
| LANGUAGE
| LEADING
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,20 @@ export function completionCoreCompletion(
const topExprParent = candidateRule.ruleList[topExprIndex - 1];

if (topExprParent === undefined) {
//Changes to the grammar have added a case where we want to complete a labelExpression1 outside of a labelExpression (in comparisonExpression6)
const patternExprIndex = candidateRule.ruleList.indexOf(
CypherParser.RULE_labelExpression4,
);
const labelExpressionParent =
candidateRule.ruleList[patternExprIndex - 1];
if (
labelExpressionParent === CypherParser.RULE_comparisonExpression6
) {
return [
...allLabelCompletions(dbSchema),
...allReltypeCompletions(dbSchema),
];
}
return [];
}

Expand Down
18 changes: 18 additions & 0 deletions packages/language-support/src/formatting/formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
IndexPostfixContext,
InsertClauseContext,
KeywordLiteralContext,
LabelComparisonContext,
LabelExpression2Context,
LabelExpression3Context,
LabelExpression4Context,
Expand Down Expand Up @@ -1130,6 +1131,23 @@ export class TreePrintVisitor extends CypherCmdParserVisitor<void> {
this._visit(ctx.labelExpression4());
};

visitLabelComparison = (ctx: LabelComparisonContext) => {
this._visitTerminalRaw(ctx.COLON());
this.avoidBreakBetween();
if (ctx.IS()) {
this._visit(ctx.IS());
if (ctx.NOT()) {
this._visit(ctx.NOT());
}
if (ctx.LABELED) {
this._visit(ctx.LABELED());
}
Comment thread
alisasanib marked this conversation as resolved.
Outdated
} else {
this.avoidSpaceBetween();
}
this._visit(ctx.labelExpression4());
};

visitLabelExpression4 = (ctx: LabelExpression4Context) => {
// There is no great way to know which labels have colons before them,
// so we have to resort to manually checking the types of the children.
Expand Down
2 changes: 2 additions & 0 deletions packages/language-support/src/lexerSymbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ export const lexerKeywords = [
CypherLexer.JOIN,
CypherLexer.KEY,
CypherLexer.LABEL,
CypherLexer.LABELED,
CypherLexer.LABELS,
CypherLexer.LANGUAGE,
CypherLexer.LEADING,
Expand Down Expand Up @@ -306,6 +307,7 @@ export const lexerKeywords = [
CypherLexer.PROCEDURES,
CypherLexer.PROPERTIES,
CypherLexer.PROPERTY,
CypherLexer.PROPERTY_EXISTS,
CypherLexer.PROVIDER,
CypherLexer.PROVIDERS,
CypherLexer.RANGE,
Expand Down
26,355 changes: 13,225 additions & 13,130 deletions packages/language-support/src/syntaxValidation/semanticAnalysis.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -745,25 +745,6 @@ meaning that it expects at least 3 arguments of types NODE, STRING, ANY
dbSchema: testData.mockSchema,
}),
).toEqual([
{
severity: 1,
message:
'Procedure call inside a query does not support naming results implicitly (name explicitly using `YIELD` instead)',

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like semantic analysis got smart enough to realise the procedure doesnt yield any columns at all, so this error doesnt make sense. Added another test below with a procedure that does have return columns

range: {
start: {
line: 1,
character: 8,
},
end: {
line: 2,
character: 36,
},
},
offsets: {
start: 9,
end: 73,
},
},
{
message: 'Type mismatch: expected Integer but was String',
offsets: {
Expand Down Expand Up @@ -804,24 +785,39 @@ meaning that it expects at least 1 argument of type STRING
},
severity: 1,
},
]);
});

test('Provides "name explicitly using `YIELD` instead" error when calling procedure inside query, if call has return columns', () => {
const query = `
MATCH (n)
CALL db.schema.visualization()
RETURN n
`;
expect(
getDiagnosticsForQuery({
query,
dbSchema: testData.mockSchema,
}),
).toEqual([
{
severity: 1,
message:
'Procedure call inside a query does not support naming results implicitly (name explicitly using `YIELD` instead)',
offsets: {
end: 57,
start: 27,
},
range: {
start: {
end: {
character: 38,
line: 2,
character: 8,
},
end: {
start: {
character: 8,
line: 2,
character: 28,
},
},
offsets: {
start: 53,
end: 73,
},
severity: 1,
},
]);
});
Expand Down
2 changes: 1 addition & 1 deletion packages/lint-worker/server-version-tag.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
neo4j-2026.03
neo4j-2026.04

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not forgetting it this time!

2 changes: 1 addition & 1 deletion packages/vscode-extension/syntaxes/cypher.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"name": "keyword.operator"
},
{
"match": "(?i)\\b(ACCESS|ACTIVE|ADD|ADMIN|ADMINISTRATOR|ALIAS|ALIASES|ALL|ALLREDUCE|allShortestPaths|ALTER|AND|ANY|ARRAY|AS|ASC|ASCENDING|ASSIGN|AT|AUTH|BINDINGS|BOOL|BOOLEAN|BOOSTED|BOTH|BREAK|BUILT|BY|CALL|CASCADE|CASE|CIDR|CHANGE|COLLECT|COMMAND|COMMANDS|COMPOSITE|CONDITION|CONSTRAINT|CONSTRAINTS|CONTAINS|CONTINUE|COPY|COSINE|COUNT|CREATE|CREDENTIAL|CSV|CONCURRENT|CURRENT|DATA|DATABASE|DATABASES|DATE|DATETIME|DBMS|DEALLOCATE|DEFAULT|DEFINE|DEFINED|DELETE|DENY|DESC|DESCENDING|DESTROY|DETACH|DIFFERENT|DISTINCT|DOT|DRIVER|DROP|DRYRUN|DUMP|DURATION|EACH|EDGE|ELEMENT|ELEMENTS|ELSE|ENABLE|ENABLED|ENCRYPTED|END|ENDS|ERROR|EUCLIDEAN|EUCLIDEAN_SQUARED|EXECUTABLE|EXECUTE|EXIST|EXISTENCE|EXISTS|EXTENDED_IDENTIFIER|FAIL|FALSE|FIELDTERMINATOR|FILTER|FINISH|FLOAT|FLOAT32|FLOAT64|FOR|FOREACH|FORWARDING|FROM|FULLTEXT|FUNCTION|FUNCTIONS|GRANT|GRAPH|GRAPHS|GROUP|GROUPS|HAMMING|HEADERS|HOME|ID|IF|IMMUTABLE|IMPERSONATE|IMPLIES|IN|INDEX|INDEXES|INF|INFINITY|INSERT|INT|INT8|INT16|INT32|INT64|INTEGER|INTEGER8|INTEGER16|INTEGER32|INTEGER64|IS|JOIN|KEY|LABEL|LABELS|LANGUAGE|LEADING|LET|LIMIT|LIST|LOAD|LOCAL|LOOKUP|MANAGEMENT|MANHATTAN|MAP|MATCH|MERGE|NAME|NAMES|NAN|NEW|NEXT|NFC|NFD|NFKC|NFKD|NODE|NODETACH|NODES|NONE|NORMALIZE|NORMALIZED|NOT|NOTHING|NOWAIT|NULL|OF|OFFSET|OIDC|ON|ONLY|OPTION|OPTIONAL|OPTIONS|OR|ORDER|PASSWORD|PASSWORDS|PATH|PATHS|PLAINTEXT|POINT|POPULATED|PRIMARY|PRIMARIES|PRIVILEGE|PRIVILEGES|PROCEDURE|PROCEDURES|PROPERTIES|PROPERTY|PROVIDER|PROVIDERS|RANGE|READ|REALLOCATE|REDUCE|REL|RELATIONSHIP|RELATIONSHIPS|REMOVE|RENAME|REPEATABLE|REPLACE|REPLICA|REPLICAS|REPORT|REQUIRE|REQUIRED|RESTRICT|RETRY|RETURN|REVOKE|ROLE|ROLES|ROW|ROWS|RULE|RULES|SCAN|SCORE|SEARCH|SECONDARY|SECONDARIES|SEC|SECOND|SECONDS|SEEK|SERVER|SERVERS|SET|SETTING|SETTINGS|SHARD|SHARDS|SHORTEST|shortestPath|SHOW|SIGNED|SINGLE|SKIP|START|STARTS|STATUS|STOP|VARCHAR|STRING|SUPPORTED|SUSPENDED|TARGET|TERMINATE|TEXT|THEN|TIME|TIMESTAMP|TIMEZONE|TO|TOPOLOGY|TRAILING|TRANSACTION|TRANSACTIONS|TRAVERSE|TRIM|TRUE|TYPE|TYPED|TYPES|UNION|UNIQUE|UNIQUENESS|UNWIND|URL|USE|USER|USERS|USING|VALUE|VECTOR|VECTOR_DISTANCE|VECTOR_NORM|VERTEX|WAIT|WHEN|WHERE|WITH|WITHOUT|WRITE|XOR|YIELD|ZONE|ZONED|EXPLAIN|PROFILE|CYPHER)\\b",
"match": "(?i)\\b(ACCESS|ACTIVE|ADD|ADMIN|ADMINISTRATOR|ALIAS|ALIASES|ALL|ALLREDUCE|allShortestPaths|ALTER|AND|ANY|ARRAY|AS|ASC|ASCENDING|ASSIGN|AT|AUTH|BINDINGS|BOOL|BOOLEAN|BOOSTED|BOTH|BREAK|BUILT|BY|CALL|CASCADE|CASE|CIDR|CHANGE|COLLECT|COMMAND|COMMANDS|COMPOSITE|CONDITION|CONSTRAINT|CONSTRAINTS|CONTAINS|CONTINUE|COPY|COSINE|COUNT|CREATE|CREDENTIAL|CSV|CONCURRENT|CURRENT|DATA|DATABASE|DATABASES|DATE|DATETIME|DBMS|DEALLOCATE|DEFAULT|DEFINE|DEFINED|DELETE|DENY|DESC|DESCENDING|DESTROY|DETACH|DIFFERENT|DISTINCT|DOT|DRIVER|DROP|DRYRUN|DUMP|DURATION|EACH|EDGE|ELEMENT|ELEMENTS|ELSE|ENABLE|ENABLED|ENCRYPTED|END|ENDS|ERROR|EUCLIDEAN|EUCLIDEAN_SQUARED|EXECUTABLE|EXECUTE|EXIST|EXISTENCE|EXISTS|EXTENDED_IDENTIFIER|FAIL|FALSE|FIELDTERMINATOR|FILTER|FINISH|FLOAT|FLOAT32|FLOAT64|FOR|FOREACH|FORWARDING|FROM|FULLTEXT|FUNCTION|FUNCTIONS|GRANT|GRAPH|GRAPHS|GROUP|GROUPS|HAMMING|HEADERS|HOME|ID|IF|IMMUTABLE|IMPERSONATE|IMPLIES|IN|INDEX|INDEXES|INF|INFINITY|INSERT|INT|INT8|INT16|INT32|INT64|INTEGER|INTEGER8|INTEGER16|INTEGER32|INTEGER64|IS|JOIN|KEY|LABEL|LABELED|LABELS|LANGUAGE|LEADING|LET|LIMIT|LIST|LOAD|LOCAL|LOOKUP|MANAGEMENT|MANHATTAN|MAP|MATCH|MERGE|NAME|NAMES|NAN|NEW|NEXT|NFC|NFD|NFKC|NFKD|NODE|NODETACH|NODES|NONE|NORMALIZE|NORMALIZED|NOT|NOTHING|NOWAIT|NULL|OF|OFFSET|OIDC|ON|ONLY|OPTION|OPTIONAL|OPTIONS|OR|ORDER|PASSWORD|PASSWORDS|PATH|PATHS|PLAINTEXT|POINT|POPULATED|PRIMARY|PRIMARIES|PRIVILEGE|PRIVILEGES|PROCEDURE|PROCEDURES|PROPERTIES|PROPERTY|PROPERTY_EXISTS|PROVIDER|PROVIDERS|RANGE|READ|REALLOCATE|REDUCE|REL|RELATIONSHIP|RELATIONSHIPS|REMOVE|RENAME|REPEATABLE|REPLACE|REPLICA|REPLICAS|REPORT|REQUIRE|REQUIRED|RESTRICT|RETRY|RETURN|REVOKE|ROLE|ROLES|ROW|ROWS|RULE|RULES|SCAN|SCORE|SEARCH|SECONDARY|SECONDARIES|SEC|SECOND|SECONDS|SEEK|SERVER|SERVERS|SET|SETTING|SETTINGS|SHARD|SHARDS|SHORTEST|shortestPath|SHOW|SIGNED|SINGLE|SKIP|START|STARTS|STATUS|STOP|VARCHAR|STRING|SUPPORTED|SUSPENDED|TARGET|TERMINATE|TEXT|THEN|TIME|TIMESTAMP|TIMEZONE|TO|TOPOLOGY|TRAILING|TRANSACTION|TRANSACTIONS|TRAVERSE|TRIM|TRUE|TYPE|TYPED|TYPES|UNION|UNIQUE|UNIQUENESS|UNWIND|URL|USE|USER|USERS|USING|VALUE|VECTOR|VECTOR_DISTANCE|VECTOR_NORM|VERTEX|WAIT|WHEN|WHERE|WITH|WITHOUT|WRITE|XOR|YIELD|ZONE|ZONED|EXPLAIN|PROFILE|CYPHER)\\b",
"name": "keyword"
}
]
Expand Down
Loading