Skip to content

Commit e09ec06

Browse files
authored
Improves formatting of long patterns (split before nodes, not relations) (#421)
1 parent 3eb637a commit e09ec06

File tree

2 files changed

+131
-3
lines changed

2 files changed

+131
-3
lines changed

packages/language-support/src/formatting/formatting.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import {
6363
ParenthesizedPathContext,
6464
PathLengthContext,
6565
PatternContext,
66+
PatternElementContext,
6667
PatternListContext,
6768
ProcedureNameContext,
6869
PropertyContext,
@@ -1239,6 +1240,69 @@ export class TreePrintVisitor extends CypherCmdParserVisitor<void> {
12391240
this.endGroup(selectorAnonymousPatternGrp);
12401241
};
12411242

1243+
visitPatternElement = (ctx: PatternElementContext) => {
1244+
const n = ctx.getChildCount();
1245+
let i = 0;
1246+
1247+
while (i < n) {
1248+
const child = ctx.getChild(i);
1249+
1250+
if (
1251+
child instanceof NodePatternContext &&
1252+
i + 1 < n &&
1253+
ctx.getChild(i + 1) instanceof RelationshipPatternContext
1254+
) {
1255+
i = this._processNodeRelSequence(ctx, i, n);
1256+
} else if (child instanceof ParenthesizedPathContext) {
1257+
this.visitParenthesizedPath(child);
1258+
i++;
1259+
} else {
1260+
this._visit(child as ParserRuleContext);
1261+
i++;
1262+
}
1263+
}
1264+
};
1265+
1266+
// Very convoluted logic related to visitPatternElement, used becase we want to be able
1267+
// to put groups around node patterns and relationships like this: START (node)-[rel](qpp?) END
1268+
// and the grammar is very unhelpful...
1269+
_processNodeRelSequence = (
1270+
ctx: PatternElementContext,
1271+
startIndex: number,
1272+
n: number,
1273+
): number => {
1274+
let i = startIndex;
1275+
let nodeRelPatternGrp = this.startGroup();
1276+
1277+
this.visitNodePattern(ctx.getChild(i) as NodePatternContext);
1278+
i++;
1279+
while (i < n && ctx.getChild(i) instanceof RelationshipPatternContext) {
1280+
this.visitRelationshipPattern(
1281+
ctx.getChild(i) as RelationshipPatternContext,
1282+
);
1283+
i++;
1284+
1285+
if (i < n && ctx.getChild(i) instanceof QuantifierContext) {
1286+
this.visitQuantifier(ctx.getChild(i) as QuantifierContext);
1287+
i++;
1288+
}
1289+
this.endGroup(nodeRelPatternGrp);
1290+
if (i < n && ctx.getChild(i) instanceof NodePatternContext) {
1291+
if (
1292+
i + 1 < n &&
1293+
ctx.getChild(i + 1) instanceof RelationshipPatternContext
1294+
) {
1295+
nodeRelPatternGrp = this.startGroup();
1296+
}
1297+
this.visitNodePattern(ctx.getChild(i) as NodePatternContext);
1298+
i++;
1299+
} else {
1300+
break;
1301+
}
1302+
}
1303+
return i;
1304+
};
1305+
12421306
visitPatternList = (ctx: PatternListContext) => {
12431307
const n = ctx.pattern_list().length;
12441308
if (n === 1) {

packages/language-support/src/tests/formatting/linebreaks.test.ts

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,10 @@ MATCH (dmk:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(l1a:CallingPoint)-[:NEX
389389
(l2b)-[:CALLS_AT]->(gtw:Station {name: 'Gatwick Airport'})
390390
RETURN dmk`;
391391
const expected = `
392-
MATCH (dmk:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-(l1a:CallingPoint)-
393-
[:NEXT]->+(l1b)-[:CALLS_AT]->(x:Station)<-[:CALLS_AT]-(l2a:CallingPoint)-
394-
[:NEXT]->*(l2b)-[:CALLS_AT]->(gtw:Station {name: 'Gatwick Airport'})
392+
MATCH (dmk:Station {name: 'Denmark Hill'})<-[:CALLS_AT]-
393+
(l1a:CallingPoint)-[:NEXT]->+(l1b)-[:CALLS_AT]->(x:Station)<-[:CALLS_AT]-
394+
(l2a:CallingPoint)-[:NEXT]->*(l2b)-[:CALLS_AT]->
395+
(gtw:Station {name: 'Gatwick Airport'})
395396
RETURN dmk`.trim();
396397
verifyFormatting(query, expected);
397398
});
@@ -544,6 +545,69 @@ RETURN person.name AS name, COUNT {
544545
const expected = query;
545546
verifyFormatting(query, expected);
546547
});
548+
549+
test('should prefer to not split before a relation 1', () => {
550+
const query = `
551+
MATCH (a:person {name: 'alice', age: 30})-[r:friend_of]->
552+
(b:person {name: 'bob'})-[s:colleague_of]->(c:person {name: 'carol'})-
553+
[t:partner_of]->(d:person {name: 'david'})-[u:mentor_and_friend_of]->
554+
(e:person {name: 'eve'})
555+
RETURN a`;
556+
const expected = `
557+
MATCH (a:person {name: 'alice', age: 30})-[r:friend_of]->
558+
(b:person {name: 'bob'})-[s:colleague_of]->
559+
(c:person {name: 'carol'})-[t:partner_of]->
560+
(d:person {name: 'david'})-[u:mentor_and_friend_of]->
561+
(e:person {name: 'eve'})
562+
RETURN a`.trimStart();
563+
verifyFormatting(query, expected);
564+
});
565+
566+
test('should prefer to not split before a relation 2', () => {
567+
const query = `
568+
MATCH (a:Person {name: 'Alice'}) -[r:KNOWS]->
569+
(b:Person {name: 'Bob'}) -[s:FRIEND_OF]->
570+
(c:Person {name: 'Charlie'})
571+
RETURN a`;
572+
const expected = `
573+
MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->
574+
(b:Person {name: 'Bob'})-[s:FRIEND_OF]->(c:Person {name: 'Charlie'})
575+
RETURN a`.trimStart();
576+
verifyFormatting(query, expected);
577+
});
578+
579+
test('should prefer to not split before a relation 3', () => {
580+
const query = `
581+
MATCH (NOD01)-[REL01]->
582+
(NOD02)-[REL02]->
583+
(NOD03)-[REL03]->
584+
(NOD04)-[REL04]->
585+
(N)-[REL05]->
586+
(NOD06)-[REL06]->
587+
(NOD07)
588+
RETURN NOD01`;
589+
// The node (N) would fit on the previous line but we prefer to split before nodes
590+
const expected = `
591+
MATCH (NOD01)-[REL01]->(NOD02)-[REL02]->(NOD03)-[REL03]->(NOD04)-[REL04]->
592+
(N)-[REL05]->(NOD06)-[REL06]->(NOD07)
593+
RETURN NOD01`.trimStart();
594+
verifyFormatting(query, expected);
595+
});
596+
597+
test('should prefer to not split before a relation 4', () => {
598+
const query = `
599+
MATCH (Alice123:Person)-[FRND_REL:friendship]->
600+
(Bob:Indiv)-[COWORK_REL:colleagueRelationship]->
601+
(Carla55:EmployeeType)-[PARTNR:partner_of]->
602+
(Dave:Short)
603+
RETURN Alice123`;
604+
const expected = `
605+
MATCH (Alice123:Person)-[FRND_REL:friendship]->
606+
(Bob:Indiv)-[COWORK_REL:colleagueRelationship]->
607+
(Carla55:EmployeeType)-[PARTNR:partner_of]->(Dave:Short)
608+
RETURN Alice123`.trimStart();
609+
verifyFormatting(query, expected);
610+
});
547611
});
548612

549613
describe('tests for respcecting user line breaks', () => {

0 commit comments

Comments
 (0)