Skip to content

Commit aa83328

Browse files
authored
Simplify and expand schema based linting (#700)
1 parent 6866132 commit aa83328

8 files changed

Lines changed: 1154 additions & 350 deletions

File tree

packages/language-support/src/autocompletion/schemaBasedCompletions.ts

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
import { DbSchema } from '../dbSchema.js';
77
import { ParsedStatement } from '../cypherLanguageService.js';
88
import { isLabelLeaf, LabelOrCondition, SymbolsInfo } from '../types.js';
9-
import { findParent } from '../helpers.js';
9+
import { findParent, getDirection } from '../helpers.js';
1010
import {
1111
NodePatternContext,
1212
PatternElementContext,
@@ -44,7 +44,7 @@ export function getShortPathCompletions(
4444

4545
const { toRels: nodesToRelsSet, fromRels: nodesFromRelsSet } =
4646
getNodesFromRelsSet(dbSchema);
47-
const assumedDirection = lastNode.leftArrow() ? 'outgoing' : 'incoming';
47+
const assumedDirection = lastNode.leftArrow() ? 'left' : 'right';
4848

4949
let cnfTree: LabelOrCondition;
5050
try {
@@ -65,7 +65,7 @@ export function getShortPathCompletions(
6565
cnfTree,
6666
);
6767

68-
if (assumedDirection === 'outgoing') {
68+
if (assumedDirection === 'left') {
6969
for (const outLabel of outLabels) {
7070
snippetCompletions.push({
7171
label: '-(:' + outLabel + ')',
@@ -260,12 +260,7 @@ export function completeNodeLabel(
260260
return allLabelCompletions(dbSchema);
261261
}
262262

263-
const direction =
264-
lastValidElement.leftArrow() && !lastValidElement.rightArrow()
265-
? 'outgoing'
266-
: !lastValidElement.leftArrow() && lastValidElement.rightArrow()
267-
? 'incoming'
268-
: 'bidirectional';
263+
const direction = getDirection(lastValidElement);
269264

270265
// limitation: not checking node label repetition
271266
const { toRels: nodesToRelsSet, fromRels: nodesFromRelsSet } =
@@ -288,9 +283,9 @@ export function completeNodeLabel(
288283
cnfTree,
289284
);
290285
const allNodes =
291-
direction === 'outgoing'
286+
direction === 'left'
292287
? outLabels
293-
: direction === 'incoming'
288+
: direction === 'right'
294289
? inLabels
295290
: inLabels.union(outLabels);
296291
return labelsToCompletions(Array.from(allNodes));
@@ -334,14 +329,9 @@ export function completeRelationshipType(
334329
parsingResult.stopNode,
335330
(x) => x instanceof RelationshipPatternContext,
336331
);
337-
let direction = 'bidirectional';
332+
let direction = 'undirected';
338333
if (thisCtx instanceof RelationshipPatternContext) {
339-
direction =
340-
thisCtx.leftArrow() && !thisCtx.rightArrow()
341-
? 'outgoing'
342-
: !thisCtx.leftArrow() && thisCtx.rightArrow()
343-
? 'incoming'
344-
: 'bidirectional';
334+
direction = getDirection(thisCtx);
345335
}
346336

347337
// limitation: bailing out on quantifiers
@@ -381,9 +371,9 @@ export function completeRelationshipType(
381371
cnfTree,
382372
);
383373
const allRels =
384-
direction === 'outgoing'
374+
direction === 'left'
385375
? outLabels
386-
: direction === 'incoming'
376+
: direction === 'right'
387377
? inLabels
388378
: inLabels.union(outLabels);
389379
return reltypesToCompletions(Array.from(allRels));

packages/language-support/src/cypherLanguageService.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
StatementsOrCommandsContext,
2525
SymbolicNameStringContext,
2626
VariableContext,
27+
PatternElementContext,
2728
} from './generated-parser/CypherCmdParser.js';
2829
import {
2930
findParent,
@@ -66,6 +67,7 @@ export interface ParsedStatement {
6667
collectedParameters: ParsedParameter[];
6768
collectedFunctions: ParsedFunction[];
6869
collectedProcedures: ParsedProcedure[];
70+
collectedReadPatternElements: PatternElementContext[];
6971
cypherVersion?: CypherVersion;
7072
}
7173

@@ -204,6 +206,7 @@ export function createParsingResult(
204206
const variableFinder = new VariableCollector();
205207
const methodsFinder = new MethodsCollector(tokens);
206208
const cypherVersionCollector = new CypherVersionCollector();
209+
const readPatternElementsCollector = new ReadPatternElementsCollector();
207210
const errorListener = new SyntaxErrorsListener(
208211
tokens,
209212
settings.consoleCommandsEnabled,
@@ -214,6 +217,7 @@ export function createParsingResult(
214217
variableFinder,
215218
methodsFinder,
216219
cypherVersionCollector,
220+
readPatternElementsCollector,
217221
];
218222
parser.addErrorListener(errorListener);
219223
const ctx = parser.statementsOrCommands();
@@ -242,6 +246,8 @@ export function createParsingResult(
242246
collectedParameters: parameterFinder.parameters,
243247
collectedFunctions: methodsFinder.functions,
244248
collectedProcedures: methodsFinder.procedures,
249+
collectedReadPatternElements:
250+
readPatternElementsCollector.readPatternElements,
245251
cypherVersion: cypherVersionCollector.cypherVersion,
246252
};
247253
});
@@ -422,6 +428,25 @@ class VariableCollector implements ParseTreeListener {
422428
}
423429
}
424430

431+
class ReadPatternElementsCollector implements ParseTreeListener {
432+
readPatternElements: PatternElementContext[] = [];
433+
enterEveryRule() {
434+
/* no-op */
435+
}
436+
visitTerminal() {
437+
/* no-op */
438+
}
439+
visitErrorNode() {
440+
/* no-op */
441+
}
442+
443+
exitEveryRule(ctx: unknown) {
444+
if (ctx instanceof PatternElementContext && !couldCreateNewLabel(ctx)) {
445+
this.readPatternElements.push(ctx);
446+
}
447+
}
448+
}
449+
425450
export function getMethodName(
426451
ctx: ProcedureNameContext | FunctionNameContext,
427452
): string {

packages/language-support/src/helpers.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,23 @@ export function resolveCypherVersion(
209209
return cypherVersion;
210210
}
211211

212+
export function getDirection(
213+
rel: RelationshipPatternContext,
214+
): 'right' | 'left' | 'undirected' {
215+
// An unfinished relationship (no closing `]`) has an ambiguous direction, so
216+
// it is treated as undirected.
217+
const complete = Boolean(rel.RBRACKET());
218+
const hasLeft = Boolean(rel.leftArrow());
219+
const hasRight = Boolean(rel.rightArrow());
220+
if (complete && hasRight && !hasLeft) {
221+
return 'right';
222+
}
223+
if (complete && hasLeft && !hasRight) {
224+
return 'left';
225+
}
226+
return 'undirected';
227+
}
228+
212229
export const rulesDefiningVariables = [
213230
CypherParser.RULE_returnItem,
214231
CypherParser.RULE_unwindClause,

packages/language-support/src/labelTreeRewriting.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function copyLabelTree(labelTree: LabelOrCondition): LabelOrCondition {
1919
/**
2020
* Takes a label tree with an AND-root and converts it to Conjunctive Normal Form
2121
* @param root - the original label tree
22-
* @returns a an equivalent CNF tree
22+
* @returns an equivalent CNF tree
2323
*/
2424
export function convertToCNF(root: LabelOrCondition): LabelOrCondition {
2525
if (isLabelLeaf(root) || !(root.condition === 'and')) {
@@ -595,9 +595,12 @@ export function pushInNots(labelTree: LabelOrCondition): LabelOrCondition {
595595
}
596596

597597
export const isAnyNode = (n: LabelOrCondition) =>
598-
!isLabelLeaf(n) && n.condition === 'any';
598+
!isLabelLeaf(n) && n.condition === 'any' && n.children.length === 0;
599599
export const isNotAnyNode = (n: LabelOrCondition) =>
600-
!isLabelLeaf(n) && n.condition === 'not' && isAnyNode(n.children[0]);
600+
!isLabelLeaf(n) &&
601+
n.condition === 'not' &&
602+
n.children.length === 1 &&
603+
isAnyNode(n.children[0]);
601604

602605
/**
603606
* Converts a label tree with ANYs to one without ANYs by simplifying like

0 commit comments

Comments
 (0)