New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@neo4j-cypher/language-support

Package Overview
Dependencies
Maintainers
2
Versions
137
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@neo4j-cypher/language-support - npm Package Compare versions

Comparing version 2.0.0-next.13 to 2.0.0-next.14

dist/types/autocompletion/autocompletionHelpers.d.ts

13

CHANGELOG.md
# @neo4j-cypher/language-support
## 2.0.0-next.14
### Patch Changes
- 8ec797d: Updates semantic error worker to use given cypher version
- 7aa9c3a: Adds hints to the error when using a procedure/function where the other would be appropriate
- 2be5469: Updates grammar and semantic analysis to version 2025.01.0
- 245fb6a: Automatically opens autocompletions after "YIELD "
- c587b81: Fixes bug using console commands as variables or labels
- 3f8b64f: Fixes bug reporting missing label / rel type when inside opposite pattern
- 043d766: Moves the syntax errors to the semantic analysis web worker
- 704d1c5: Adds autocompletions for CYPHER <version>
## 2.0.0-next.13

@@ -4,0 +17,0 @@

10

dist/types/dbSchema.d.ts

@@ -1,2 +0,4 @@

import { Neo4jFunction, Neo4jProcedure } from './types';
import { CypherVersion, Neo4jFunction, Neo4jProcedure } from './types';
export type Registry<T> = Record<string, T>;
type ScopedRegistry<T> = Partial<Record<CypherVersion, Registry<T>>>;
export interface DbSchema {

@@ -11,4 +13,6 @@ labels?: string[];

propertyKeys?: string[];
procedures?: Record<string, Neo4jProcedure>;
functions?: Record<string, Neo4jFunction>;
procedures?: ScopedRegistry<Neo4jProcedure>;
functions?: ScopedRegistry<Neo4jFunction>;
defaultLanguage?: CypherVersion;
}
export {};
interface FeatureFlags {
consoleCommands: boolean;
cypher25: boolean;
}
export declare const _internalFeatureFlags: FeatureFlags;
export {};

@@ -38,290 +38,284 @@ import { ATN, CharStream, DFA, Lexer } from "antlr4";

static readonly ASCENDING = 35;
static readonly ASSERT = 36;
static readonly ASSIGN = 37;
static readonly AT = 38;
static readonly AUTH = 39;
static readonly BAR = 40;
static readonly BINDINGS = 41;
static readonly BOOL = 42;
static readonly BOOLEAN = 43;
static readonly BOOSTED = 44;
static readonly BOTH = 45;
static readonly BREAK = 46;
static readonly BRIEF = 47;
static readonly BTREE = 48;
static readonly BUILT = 49;
static readonly BY = 50;
static readonly CALL = 51;
static readonly CASCADE = 52;
static readonly CASE = 53;
static readonly CHANGE = 54;
static readonly CIDR = 55;
static readonly COLLECT = 56;
static readonly COLON = 57;
static readonly COLONCOLON = 58;
static readonly COMMA = 59;
static readonly COMMAND = 60;
static readonly COMMANDS = 61;
static readonly COMMIT = 62;
static readonly COMPOSITE = 63;
static readonly CONCURRENT = 64;
static readonly CONSTRAINT = 65;
static readonly CONSTRAINTS = 66;
static readonly CONTAINS = 67;
static readonly COPY = 68;
static readonly CONTINUE = 69;
static readonly COUNT = 70;
static readonly CREATE = 71;
static readonly CSV = 72;
static readonly CURRENT = 73;
static readonly DATA = 74;
static readonly DATABASE = 75;
static readonly DATABASES = 76;
static readonly DATE = 77;
static readonly DATETIME = 78;
static readonly DBMS = 79;
static readonly DEALLOCATE = 80;
static readonly DEFAULT = 81;
static readonly DEFINED = 82;
static readonly DELETE = 83;
static readonly DENY = 84;
static readonly DESC = 85;
static readonly DESCENDING = 86;
static readonly DESTROY = 87;
static readonly DETACH = 88;
static readonly DIFFERENT = 89;
static readonly DOLLAR = 90;
static readonly DISTINCT = 91;
static readonly DIVIDE = 92;
static readonly DOT = 93;
static readonly DOTDOT = 94;
static readonly DOUBLEBAR = 95;
static readonly DRIVER = 96;
static readonly DROP = 97;
static readonly DRYRUN = 98;
static readonly DUMP = 99;
static readonly DURATION = 100;
static readonly EACH = 101;
static readonly EDGE = 102;
static readonly ENABLE = 103;
static readonly ELEMENT = 104;
static readonly ELEMENTS = 105;
static readonly ELSE = 106;
static readonly ENCRYPTED = 107;
static readonly END = 108;
static readonly ENDS = 109;
static readonly EQ = 110;
static readonly EXECUTABLE = 111;
static readonly EXECUTE = 112;
static readonly EXIST = 113;
static readonly EXISTENCE = 114;
static readonly EXISTS = 115;
static readonly ERROR = 116;
static readonly FAIL = 117;
static readonly FALSE = 118;
static readonly FIELDTERMINATOR = 119;
static readonly FINISH = 120;
static readonly FLOAT = 121;
static readonly FOR = 122;
static readonly FOREACH = 123;
static readonly FROM = 124;
static readonly FULLTEXT = 125;
static readonly FUNCTION = 126;
static readonly FUNCTIONS = 127;
static readonly GE = 128;
static readonly GRANT = 129;
static readonly GRAPH = 130;
static readonly GRAPHS = 131;
static readonly GROUP = 132;
static readonly GROUPS = 133;
static readonly GT = 134;
static readonly HEADERS = 135;
static readonly HOME = 136;
static readonly ID = 137;
static readonly IF = 138;
static readonly IMPERSONATE = 139;
static readonly IMMUTABLE = 140;
static readonly IN = 141;
static readonly INDEX = 142;
static readonly INDEXES = 143;
static readonly INF = 144;
static readonly INFINITY = 145;
static readonly INSERT = 146;
static readonly INT = 147;
static readonly INTEGER = 148;
static readonly IS = 149;
static readonly JOIN = 150;
static readonly KEY = 151;
static readonly LABEL = 152;
static readonly LABELS = 153;
static readonly AMPERSAND = 154;
static readonly EXCLAMATION_MARK = 155;
static readonly LBRACKET = 156;
static readonly LCURLY = 157;
static readonly LE = 158;
static readonly LEADING = 159;
static readonly LIMITROWS = 160;
static readonly LIST = 161;
static readonly LOAD = 162;
static readonly LOCAL = 163;
static readonly LOOKUP = 164;
static readonly LPAREN = 165;
static readonly LT = 166;
static readonly MANAGEMENT = 167;
static readonly MAP = 168;
static readonly MATCH = 169;
static readonly MERGE = 170;
static readonly MINUS = 171;
static readonly PERCENT = 172;
static readonly INVALID_NEQ = 173;
static readonly NEQ = 174;
static readonly NAME = 175;
static readonly NAMES = 176;
static readonly NAN = 177;
static readonly NFC = 178;
static readonly NFD = 179;
static readonly NFKC = 180;
static readonly NFKD = 181;
static readonly NEW = 182;
static readonly NODE = 183;
static readonly NODETACH = 184;
static readonly NODES = 185;
static readonly NONE = 186;
static readonly NORMALIZE = 187;
static readonly NORMALIZED = 188;
static readonly NOT = 189;
static readonly NOTHING = 190;
static readonly NOWAIT = 191;
static readonly NULL = 192;
static readonly OF = 193;
static readonly OFFSET = 194;
static readonly ON = 195;
static readonly ONLY = 196;
static readonly OPTIONAL = 197;
static readonly OPTIONS = 198;
static readonly OPTION = 199;
static readonly OR = 200;
static readonly ORDER = 201;
static readonly OUTPUT = 202;
static readonly PASSWORD = 203;
static readonly PASSWORDS = 204;
static readonly PATH = 205;
static readonly PATHS = 206;
static readonly PERIODIC = 207;
static readonly PLAINTEXT = 208;
static readonly PLUS = 209;
static readonly PLUSEQUAL = 210;
static readonly POINT = 211;
static readonly POPULATED = 212;
static readonly POW = 213;
static readonly PRIMARY = 214;
static readonly PRIMARIES = 215;
static readonly PRIVILEGE = 216;
static readonly PRIVILEGES = 217;
static readonly PROCEDURE = 218;
static readonly PROCEDURES = 219;
static readonly PROPERTIES = 220;
static readonly PROPERTY = 221;
static readonly PROVIDER = 222;
static readonly PROVIDERS = 223;
static readonly QUESTION = 224;
static readonly RANGE = 225;
static readonly RBRACKET = 226;
static readonly RCURLY = 227;
static readonly READ = 228;
static readonly REALLOCATE = 229;
static readonly REDUCE = 230;
static readonly RENAME = 231;
static readonly REGEQ = 232;
static readonly REL = 233;
static readonly RELATIONSHIP = 234;
static readonly RELATIONSHIPS = 235;
static readonly REMOVE = 236;
static readonly REPEATABLE = 237;
static readonly REPLACE = 238;
static readonly REPORT = 239;
static readonly REQUIRE = 240;
static readonly REQUIRED = 241;
static readonly RESTRICT = 242;
static readonly RETURN = 243;
static readonly REVOKE = 244;
static readonly ROLE = 245;
static readonly ROLES = 246;
static readonly ROW = 247;
static readonly ROWS = 248;
static readonly RPAREN = 249;
static readonly SCAN = 250;
static readonly SEC = 251;
static readonly SECOND = 252;
static readonly SECONDARY = 253;
static readonly SECONDARIES = 254;
static readonly SECONDS = 255;
static readonly SEEK = 256;
static readonly SEMICOLON = 257;
static readonly SERVER = 258;
static readonly SERVERS = 259;
static readonly SET = 260;
static readonly SETTING = 261;
static readonly SETTINGS = 262;
static readonly SHORTEST_PATH = 263;
static readonly SHORTEST = 264;
static readonly SHOW = 265;
static readonly SIGNED = 266;
static readonly SINGLE = 267;
static readonly SKIPROWS = 268;
static readonly START = 269;
static readonly STARTS = 270;
static readonly STATUS = 271;
static readonly STOP = 272;
static readonly STRING = 273;
static readonly SUPPORTED = 274;
static readonly SUSPENDED = 275;
static readonly TARGET = 276;
static readonly TERMINATE = 277;
static readonly TEXT = 278;
static readonly THEN = 279;
static readonly TIME = 280;
static readonly TIMES = 281;
static readonly TIMESTAMP = 282;
static readonly TIMEZONE = 283;
static readonly TO = 284;
static readonly TOPOLOGY = 285;
static readonly TRAILING = 286;
static readonly TRANSACTION = 287;
static readonly TRANSACTIONS = 288;
static readonly TRAVERSE = 289;
static readonly TRIM = 290;
static readonly TRUE = 291;
static readonly TYPE = 292;
static readonly TYPED = 293;
static readonly TYPES = 294;
static readonly UNION = 295;
static readonly UNIQUE = 296;
static readonly UNIQUENESS = 297;
static readonly UNWIND = 298;
static readonly URL = 299;
static readonly USE = 300;
static readonly USER = 301;
static readonly USERS = 302;
static readonly USING = 303;
static readonly VALUE = 304;
static readonly VARCHAR = 305;
static readonly VECTOR = 306;
static readonly VERBOSE = 307;
static readonly VERTEX = 308;
static readonly WAIT = 309;
static readonly WHEN = 310;
static readonly WHERE = 311;
static readonly WITH = 312;
static readonly WITHOUT = 313;
static readonly WRITE = 314;
static readonly XOR = 315;
static readonly YIELD = 316;
static readonly ZONE = 317;
static readonly ZONED = 318;
static readonly IDENTIFIER = 319;
static readonly ARROW_LINE = 320;
static readonly ARROW_LEFT_HEAD = 321;
static readonly ARROW_RIGHT_HEAD = 322;
static readonly ErrorChar = 323;
static readonly ASSIGN = 36;
static readonly AT = 37;
static readonly AUTH = 38;
static readonly BAR = 39;
static readonly BINDINGS = 40;
static readonly BOOL = 41;
static readonly BOOLEAN = 42;
static readonly BOOSTED = 43;
static readonly BOTH = 44;
static readonly BREAK = 45;
static readonly BUILT = 46;
static readonly BY = 47;
static readonly CALL = 48;
static readonly CASCADE = 49;
static readonly CASE = 50;
static readonly CHANGE = 51;
static readonly CIDR = 52;
static readonly COLLECT = 53;
static readonly COLON = 54;
static readonly COLONCOLON = 55;
static readonly COMMA = 56;
static readonly COMMAND = 57;
static readonly COMMANDS = 58;
static readonly COMPOSITE = 59;
static readonly CONCURRENT = 60;
static readonly CONSTRAINT = 61;
static readonly CONSTRAINTS = 62;
static readonly CONTAINS = 63;
static readonly COPY = 64;
static readonly CONTINUE = 65;
static readonly COUNT = 66;
static readonly CREATE = 67;
static readonly CSV = 68;
static readonly CURRENT = 69;
static readonly DATA = 70;
static readonly DATABASE = 71;
static readonly DATABASES = 72;
static readonly DATE = 73;
static readonly DATETIME = 74;
static readonly DBMS = 75;
static readonly DEALLOCATE = 76;
static readonly DEFAULT = 77;
static readonly DEFINED = 78;
static readonly DELETE = 79;
static readonly DENY = 80;
static readonly DESC = 81;
static readonly DESCENDING = 82;
static readonly DESTROY = 83;
static readonly DETACH = 84;
static readonly DIFFERENT = 85;
static readonly DOLLAR = 86;
static readonly DISTINCT = 87;
static readonly DIVIDE = 88;
static readonly DOT = 89;
static readonly DOTDOT = 90;
static readonly DOUBLEBAR = 91;
static readonly DRIVER = 92;
static readonly DROP = 93;
static readonly DRYRUN = 94;
static readonly DUMP = 95;
static readonly DURATION = 96;
static readonly EACH = 97;
static readonly EDGE = 98;
static readonly ENABLE = 99;
static readonly ELEMENT = 100;
static readonly ELEMENTS = 101;
static readonly ELSE = 102;
static readonly ENCRYPTED = 103;
static readonly END = 104;
static readonly ENDS = 105;
static readonly EQ = 106;
static readonly EXECUTABLE = 107;
static readonly EXECUTE = 108;
static readonly EXIST = 109;
static readonly EXISTENCE = 110;
static readonly EXISTS = 111;
static readonly ERROR = 112;
static readonly FAIL = 113;
static readonly FALSE = 114;
static readonly FIELDTERMINATOR = 115;
static readonly FINISH = 116;
static readonly FLOAT = 117;
static readonly FOR = 118;
static readonly FOREACH = 119;
static readonly FROM = 120;
static readonly FULLTEXT = 121;
static readonly FUNCTION = 122;
static readonly FUNCTIONS = 123;
static readonly GE = 124;
static readonly GRANT = 125;
static readonly GRAPH = 126;
static readonly GRAPHS = 127;
static readonly GROUP = 128;
static readonly GROUPS = 129;
static readonly GT = 130;
static readonly HEADERS = 131;
static readonly HOME = 132;
static readonly ID = 133;
static readonly IF = 134;
static readonly IMPERSONATE = 135;
static readonly IMMUTABLE = 136;
static readonly IN = 137;
static readonly INDEX = 138;
static readonly INDEXES = 139;
static readonly INF = 140;
static readonly INFINITY = 141;
static readonly INSERT = 142;
static readonly INT = 143;
static readonly INTEGER = 144;
static readonly IS = 145;
static readonly JOIN = 146;
static readonly KEY = 147;
static readonly LABEL = 148;
static readonly LABELS = 149;
static readonly AMPERSAND = 150;
static readonly EXCLAMATION_MARK = 151;
static readonly LBRACKET = 152;
static readonly LCURLY = 153;
static readonly LE = 154;
static readonly LEADING = 155;
static readonly LIMITROWS = 156;
static readonly LIST = 157;
static readonly LOAD = 158;
static readonly LOCAL = 159;
static readonly LOOKUP = 160;
static readonly LPAREN = 161;
static readonly LT = 162;
static readonly MANAGEMENT = 163;
static readonly MAP = 164;
static readonly MATCH = 165;
static readonly MERGE = 166;
static readonly MINUS = 167;
static readonly PERCENT = 168;
static readonly INVALID_NEQ = 169;
static readonly NEQ = 170;
static readonly NAME = 171;
static readonly NAMES = 172;
static readonly NAN = 173;
static readonly NFC = 174;
static readonly NFD = 175;
static readonly NFKC = 176;
static readonly NFKD = 177;
static readonly NEW = 178;
static readonly NODE = 179;
static readonly NODETACH = 180;
static readonly NODES = 181;
static readonly NONE = 182;
static readonly NORMALIZE = 183;
static readonly NORMALIZED = 184;
static readonly NOT = 185;
static readonly NOTHING = 186;
static readonly NOWAIT = 187;
static readonly NULL = 188;
static readonly OF = 189;
static readonly OFFSET = 190;
static readonly ON = 191;
static readonly ONLY = 192;
static readonly OPTIONAL = 193;
static readonly OPTIONS = 194;
static readonly OPTION = 195;
static readonly OR = 196;
static readonly ORDER = 197;
static readonly PASSWORD = 198;
static readonly PASSWORDS = 199;
static readonly PATH = 200;
static readonly PATHS = 201;
static readonly PLAINTEXT = 202;
static readonly PLUS = 203;
static readonly PLUSEQUAL = 204;
static readonly POINT = 205;
static readonly POPULATED = 206;
static readonly POW = 207;
static readonly PRIMARY = 208;
static readonly PRIMARIES = 209;
static readonly PRIVILEGE = 210;
static readonly PRIVILEGES = 211;
static readonly PROCEDURE = 212;
static readonly PROCEDURES = 213;
static readonly PROPERTIES = 214;
static readonly PROPERTY = 215;
static readonly PROVIDER = 216;
static readonly PROVIDERS = 217;
static readonly QUESTION = 218;
static readonly RANGE = 219;
static readonly RBRACKET = 220;
static readonly RCURLY = 221;
static readonly READ = 222;
static readonly REALLOCATE = 223;
static readonly REDUCE = 224;
static readonly RENAME = 225;
static readonly REGEQ = 226;
static readonly REL = 227;
static readonly RELATIONSHIP = 228;
static readonly RELATIONSHIPS = 229;
static readonly REMOVE = 230;
static readonly REPEATABLE = 231;
static readonly REPLACE = 232;
static readonly REPORT = 233;
static readonly REQUIRE = 234;
static readonly REQUIRED = 235;
static readonly RESTRICT = 236;
static readonly RETURN = 237;
static readonly REVOKE = 238;
static readonly ROLE = 239;
static readonly ROLES = 240;
static readonly ROW = 241;
static readonly ROWS = 242;
static readonly RPAREN = 243;
static readonly SCAN = 244;
static readonly SEC = 245;
static readonly SECOND = 246;
static readonly SECONDARY = 247;
static readonly SECONDARIES = 248;
static readonly SECONDS = 249;
static readonly SEEK = 250;
static readonly SEMICOLON = 251;
static readonly SERVER = 252;
static readonly SERVERS = 253;
static readonly SET = 254;
static readonly SETTING = 255;
static readonly SETTINGS = 256;
static readonly SHORTEST_PATH = 257;
static readonly SHORTEST = 258;
static readonly SHOW = 259;
static readonly SIGNED = 260;
static readonly SINGLE = 261;
static readonly SKIPROWS = 262;
static readonly START = 263;
static readonly STARTS = 264;
static readonly STATUS = 265;
static readonly STOP = 266;
static readonly STRING = 267;
static readonly SUPPORTED = 268;
static readonly SUSPENDED = 269;
static readonly TARGET = 270;
static readonly TERMINATE = 271;
static readonly TEXT = 272;
static readonly THEN = 273;
static readonly TIME = 274;
static readonly TIMES = 275;
static readonly TIMESTAMP = 276;
static readonly TIMEZONE = 277;
static readonly TO = 278;
static readonly TOPOLOGY = 279;
static readonly TRAILING = 280;
static readonly TRANSACTION = 281;
static readonly TRANSACTIONS = 282;
static readonly TRAVERSE = 283;
static readonly TRIM = 284;
static readonly TRUE = 285;
static readonly TYPE = 286;
static readonly TYPED = 287;
static readonly TYPES = 288;
static readonly UNION = 289;
static readonly UNIQUE = 290;
static readonly UNIQUENESS = 291;
static readonly UNWIND = 292;
static readonly URL = 293;
static readonly USE = 294;
static readonly USER = 295;
static readonly USERS = 296;
static readonly USING = 297;
static readonly VALUE = 298;
static readonly VARCHAR = 299;
static readonly VECTOR = 300;
static readonly VERTEX = 301;
static readonly WAIT = 302;
static readonly WHEN = 303;
static readonly WHERE = 304;
static readonly WITH = 305;
static readonly WITHOUT = 306;
static readonly WRITE = 307;
static readonly XOR = 308;
static readonly YIELD = 309;
static readonly ZONE = 310;
static readonly ZONED = 311;
static readonly IDENTIFIER = 312;
static readonly EXTENDED_IDENTIFIER = 313;
static readonly ARROW_LINE = 314;
static readonly ARROW_LEFT_HEAD = 315;
static readonly ARROW_RIGHT_HEAD = 316;
static readonly ErrorChar = 317;
static readonly EOF: number;

@@ -328,0 +322,0 @@ static readonly channelNames: string[];

import antlrDefaultExport, { CommonTokenStream, ParserRuleContext, ParseTree, Token } from 'antlr4';
import { DbSchema } from './dbSchema';
import CypherLexer from './generated-parser/CypherCmdLexer';
import CypherParser, { StatementsOrCommandsContext } from './generated-parser/CypherCmdParser';
import { ParsedStatement, ParsingResult } from './parserWrapper';
import { CypherVersion } from './types';
export type EnrichedParseTree = ParseTree & {

@@ -30,4 +32,5 @@ parentCtx: ParserRuleContext | undefined;

export declare function isCommentOpener(thisToken: Token, nextToken: Token | undefined): boolean;
export declare function resolveCypherVersion(parsedVersion: CypherVersion | undefined, dbSchema: DbSchema): string;
export declare const rulesDefiningVariables: number[];
export declare const rulesDefiningOrUsingVariables: number[];
export {};
export type { ParserRuleContext } from 'antlr4';
export { autocomplete } from './autocompletion/autocompletion';
export { shouldAutoCompleteYield } from './autocompletion/autocompletionHelpers';
export type { DbSchema } from './dbSchema';
export { _internalFeatureFlags } from './featureFlags';
export { formatQuery } from './formatting/formatting';
export { antlrUtils } from './helpers';

@@ -11,9 +13,10 @@ export { CypherTokenType, lexerSymbols } from './lexerSymbols';

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';
export { cypherVersions } from './types';
export type { CompletionItem, CypherVersion, Neo4jFunction, Neo4jProcedure, } from './types';
export { CypherLexer, CypherParser };
import CypherLexer from './generated-parser/CypherCmdLexer';
import CypherParser from './generated-parser/CypherCmdParser';
import type { ParserRuleContext, Token } from 'antlr4';
import { default as CypherParser, FunctionNameContext, ProcedureNameContext, StatementOrCommandContext, StatementsOrCommandsContext } from './generated-parser/CypherCmdParser';
import { SyntaxDiagnostic } from './syntaxValidation/syntaxValidationHelpers';
import { SyntaxDiagnostic } from './syntaxValidation/syntaxValidation';
import { CypherVersion } from './types';
export interface ParsedStatement {

@@ -15,2 +16,3 @@ command: ParsedCommand;

collectedProcedures: ParsedProcedure[];
cypherVersion?: CypherVersion;
}

@@ -35,3 +37,3 @@ export interface ParsingResult {

export type LabelOrRelType = {
labeltype: LabelType;
labelType: LabelType;
labelText: string;

@@ -38,0 +40,0 @@ couldCreateNewLabel: boolean;

@@ -1,11 +0,11 @@

import { DiagnosticSeverity } from 'vscode-languageserver-types';
import { DbSchema } from '../dbSchema';
import { CypherVersion } from '../types';
import { SyntaxDiagnostic } from './syntaxValidation';
export interface SemanticAnalysisResult {
errors: SemanticAnalysisElement[];
notifications: SemanticAnalysisElement[];
errors: SyntaxDiagnostic[];
notifications: SyntaxDiagnostic[];
}
export interface SemanticAnalysisElement {
severity: DiagnosticSeverity;
message: string;
position: {
startPosition: {
offset: number;

@@ -15,3 +15,8 @@ line: number;

};
endPosition: {
offset: number;
line: number;
column: number;
};
}
export declare function wrappedSemanticAnalysis(query: string, dbSchema: DbSchema): SemanticAnalysisResult;
export declare function wrappedSemanticAnalysis(query: string, dbSchema: DbSchema, parsedVersion?: CypherVersion): SemanticAnalysisResult;

@@ -0,9 +1,10 @@

import { Diagnostic } from 'vscode-languageserver-types';
import { DbSchema } from '../dbSchema';
import { SyntaxDiagnostic } from './syntaxValidationHelpers';
export type SyntaxDiagnostic = Diagnostic & {
offsets: {
start: number;
end: number;
};
};
export declare function sortByPositionAndMessage(a: SyntaxDiagnostic, b: SyntaxDiagnostic): number;
export declare function lintCypherQuery(query: string, dbSchema: DbSchema): SyntaxDiagnostic[];
export declare function validateSyntax(query: string, dbSchema: DbSchema): SyntaxDiagnostic[];
/**
* Assumes the provided query has no parse errors
*/
export declare function validateSemantics(query: string, dbSchema: DbSchema): SyntaxDiagnostic[];
import { CommonToken, ErrorListener as ANTLRErrorListener, Recognizer, Token } from 'antlr4';
import { Diagnostic } from 'vscode-languageserver-types';
export type SyntaxDiagnostic = Diagnostic & {
offsets: {
start: number;
end: number;
};
};
import { SyntaxDiagnostic } from './syntaxValidation';
export declare class SyntaxErrorsListener implements ANTLRErrorListener<CommonToken> {

@@ -10,0 +4,0 @@ errors: SyntaxDiagnostic[];

@@ -6,3 +6,3 @@ import { DbSchema } from '../../dbSchema';

};
export declare function getDiagnosticsForQuery({ query, dbSchema, }: SyntaxValidationTestArgs): import("../..").SyntaxDiagnostic[];
export declare function getDiagnosticsForQuery({ query, dbSchema, }: SyntaxValidationTestArgs): import("../../syntaxValidation/syntaxValidation").SyntaxDiagnostic[];
export {};

@@ -8,2 +8,4 @@ import { CompletionItem as VSCodeCompletionItem } from 'vscode-languageserver-types';

};
export declare const cypherVersions: string[];
export type CypherVersion = (typeof cypherVersions)[number];
export type Neo4jStringType = string;

@@ -26,2 +28,3 @@ export type ArgumentDescription = ReturnDescription & {

} & Record<string, unknown>;
deprecatedBy?: string;
};

@@ -38,2 +41,3 @@ export type Neo4jFunction = {

isDeprecated: boolean;
deprecatedBy?: string;
};

@@ -40,0 +44,0 @@ export type CompletionItem = VSCodeCompletionItem & {

@@ -21,3 +21,3 @@ {

],
"version": "2.0.0-next.13",
"version": "2.0.0-next.14",
"main": "./dist/cjs/index.cjs",

@@ -52,3 +52,3 @@ "module": "./dist/esm/index.mjs",

"scripts": {
"gen-parser": "antlr4 -Dlanguage=TypeScript -visitor src/antlr-grammar/CypherCmdLexer.g4 src/antlr-grammar/CypherCmdParser.g4 -o src/generated-parser/ -Xexact-output-dir",
"gen-parser": "cross-env ANTLR4_TOOLS_ANTLR_VERSION=4.13.2 antlr4 -Dlanguage=TypeScript -visitor src/antlr-grammar/CypherCmdLexer.g4 src/antlr-grammar/CypherCmdParser.g4 -o src/generated-parser/ -Xexact-output-dir",
"build": "npm run gen-parser && concurrently 'npm:build-types' 'npm:build-esm' 'npm:build-commonjs'",

@@ -55,0 +55,0 @@ "build-types": "tsc --emitDeclarationOnly --outDir dist/types",

@@ -18,2 +18,3 @@ import { Token } from 'antlr4';

findPreviousNonSpace,
resolveCypherVersion,
rulesDefiningVariables,

@@ -31,6 +32,13 @@ } from '../helpers';

import { _internalFeatureFlags } from '../featureFlags';
import { CompletionItem, Neo4jFunction, Neo4jProcedure } from '../types';
import {
CompletionItem,
CypherVersion,
Neo4jFunction,
Neo4jProcedure,
} from '../types';
const uniq = <T>(arr: T[]) => Array.from(new Set(arr));
const versions = ['5'];
function backtickIfNeeded(e: string): string | undefined {

@@ -56,2 +64,20 @@ if (e == null || e == '') {

const versionCompletions = () =>
versions.map((v) => {
const result: CompletionItem = {
label: v,
kind: CompletionItemKind.EnumMember,
};
return result;
});
const cypherVersionCompletions = () =>
versions.map((v) => {
const result: CompletionItem = {
label: 'CYPHER ' + v,
kind: CompletionItemKind.Keyword,
};
return result;
});
const labelCompletions = (dbSchema: DbSchema) =>

@@ -80,5 +106,8 @@ dbSchema.labels?.map((labelName) => {

dbSchema: DbSchema,
cypherVersion: CypherVersion,
): CompletionItem[] => {
return (
dbSchema?.procedures?.[procedureName]?.returnDescription?.map((x) => {
dbSchema.procedures?.[cypherVersion]?.[
procedureName
]?.returnDescription?.map((x) => {
return { label: x.name, kind: CompletionItemKind.Variable };

@@ -93,2 +122,3 @@ }) ?? []

dbSchema: DbSchema,
cypherVersion: CypherVersion,
): CompletionItem[] =>

@@ -98,3 +128,3 @@ namespacedCompletion(

tokens,
dbSchema?.functions ?? {},
dbSchema.functions?.[cypherVersion] ?? {},
'function',

@@ -107,2 +137,3 @@ );

dbSchema: DbSchema,
cypherVersion: CypherVersion,
): CompletionItem[] =>

@@ -112,3 +143,3 @@ namespacedCompletion(

tokens,
dbSchema?.procedures ?? {},
dbSchema.procedures?.[cypherVersion] ?? {},
'procedure',

@@ -421,2 +452,6 @@ );

): CompletionItem[] {
const cypherVersion = resolveCypherVersion(
parsingResult.cypherVersion,
dbSchema,
);
const parser = parsingResult.parser;

@@ -466,2 +501,4 @@ const tokens = parsingResult.tokens;

CypherParser.RULE_procedureResultItem,
CypherParser.RULE_cypherVersion,
CypherParser.RULE_cypher,

@@ -502,2 +539,16 @@ // Either enable the helper rules for lexer clashes,

const [ruleNumber, candidateRule] = candidate;
if (ruleNumber === CypherParser.RULE_cypher) {
return [
...cypherVersionCompletions(),
{
label: 'CYPHER',
kind: CompletionItemKind.Keyword,
},
];
}
if (ruleNumber === CypherParser.RULE_cypherVersion) {
return versionCompletions();
}
if (ruleNumber === CypherParser.RULE_procedureResultItem) {

@@ -514,5 +565,7 @@ const callContext = findParent(

const name = getMethodName(procedureNameCtx);
return procedureReturnCompletions(name, dbSchema).filter(
(a) => !existingYieldItems.has(a?.label),
);
return procedureReturnCompletions(
name,
dbSchema,
cypherVersion,
).filter((a) => !existingYieldItems.has(a?.label));
}

@@ -522,7 +575,17 @@ }

if (ruleNumber === CypherParser.RULE_functionName) {
return functionNameCompletions(candidateRule, tokens, dbSchema);
return functionNameCompletions(
candidateRule,
tokens,
dbSchema,
cypherVersion,
);
}
if (ruleNumber === CypherParser.RULE_procedureName) {
return procedureNameCompletions(candidateRule, tokens, dbSchema);
return procedureNameCompletions(
candidateRule,
tokens,
dbSchema,
cypherVersion,
);
}

@@ -749,3 +812,3 @@

...parameterSuggestions,
...(dbSchema?.aliasNames ?? []).map((aliasName) => ({
...(dbSchema.aliasNames ?? []).map((aliasName) => ({
label: aliasName,

@@ -815,3 +878,3 @@ kind: CompletionItemKind.Value,

...parameterSuggestions,
...(dbSchema?.userNames ?? []).map((userName) => ({
...(dbSchema.userNames ?? []).map((userName) => ({
label: userName,

@@ -834,3 +897,3 @@ kind: CompletionItemKind.Value,

...parameterSuggestions,
...(dbSchema?.roleNames ?? []).map((roleName) => ({
...(dbSchema.roleNames ?? []).map((roleName) => ({
label: roleName,

@@ -837,0 +900,0 @@ kind: CompletionItemKind.Value,

@@ -1,3 +0,6 @@

import { Neo4jFunction, Neo4jProcedure } from './types';
import { CypherVersion, Neo4jFunction, Neo4jProcedure } from './types';
export type Registry<T> = Record<string, T>;
type ScopedRegistry<T> = Partial<Record<CypherVersion, Registry<T>>>;
export interface DbSchema {

@@ -12,4 +15,5 @@ labels?: string[];

propertyKeys?: string[];
procedures?: Record<string, Neo4jProcedure>;
functions?: Record<string, Neo4jFunction>;
procedures?: ScopedRegistry<Neo4jProcedure>;
functions?: ScopedRegistry<Neo4jFunction>;
defaultLanguage?: CypherVersion;
}
interface FeatureFlags {
consoleCommands: boolean;
cypher25: boolean;
}

@@ -14,2 +15,3 @@

consoleCommands: false,
cypher25: false,
};

@@ -9,2 +9,4 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment

} from 'antlr4';
import { DbSchema } from './dbSchema';
import { _internalFeatureFlags } from './featureFlags';
import CypherLexer from './generated-parser/CypherCmdLexer';

@@ -17,2 +19,3 @@ import CypherParser, {

import { ParsedStatement, ParsingResult } from './parserWrapper';
import { CypherVersion } from './types';

@@ -100,6 +103,8 @@ /* In antlr we have

stopNode,
(p) => p instanceof NodePatternContext,
(p) =>
p instanceof NodePatternContext ||
p instanceof RelationshipPatternContext,
);
return isDefined(nodePattern);
return nodePattern instanceof NodePatternContext;
}

@@ -110,6 +115,8 @@

stopNode,
(p) => p instanceof RelationshipPatternContext,
(p) =>
p instanceof NodePatternContext ||
p instanceof RelationshipPatternContext,
);
return isDefined(relPattern);
return relPattern instanceof RelationshipPatternContext;
}

@@ -206,2 +213,16 @@

export function resolveCypherVersion(
parsedVersion: CypherVersion | undefined,
dbSchema: DbSchema,
) {
if (_internalFeatureFlags.cypher25) {
const cypherVersion: CypherVersion =
parsedVersion ?? dbSchema.defaultLanguage ?? 'CYPHER 5';
return cypherVersion;
} else {
return 'CYPHER 5';
}
}
export const rulesDefiningVariables = [

@@ -208,0 +229,0 @@ CypherParser.RULE_returnItem,

export type { ParserRuleContext } from 'antlr4';
export { autocomplete } from './autocompletion/autocompletion';
export { shouldAutoCompleteYield } from './autocompletion/autocompletionHelpers';
export type { DbSchema } from './dbSchema';
export { _internalFeatureFlags } from './featureFlags';
export { formatQuery } from './formatting/formatting';
export { antlrUtils } from './helpers';

@@ -15,13 +17,15 @@ export { CypherTokenType, lexerSymbols } from './lexerSymbols';

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';
export { cypherVersions } from './types';
export type {
CompletionItem,
CypherVersion,
Neo4jFunction,
Neo4jProcedure,
} from './types';
export { CypherLexer, CypherParser };
import CypherLexer from './generated-parser/CypherCmdLexer';
import CypherParser from './generated-parser/CypherCmdParser';

@@ -94,2 +94,3 @@ import CypherLexer from './generated-parser/CypherCmdLexer';

CypherLexer.ESCAPED_SYMBOLIC_NAME,
CypherLexer.EXTENDED_IDENTIFIER,
];

@@ -118,3 +119,2 @@

CypherLexer.ASCENDING,
CypherLexer.ASSERT,
CypherLexer.ASSIGN,

@@ -129,4 +129,2 @@ CypherLexer.AT,

CypherLexer.BREAK,
CypherLexer.BRIEF,
CypherLexer.BTREE,
CypherLexer.BUILT,

@@ -142,3 +140,2 @@ CypherLexer.BY,

CypherLexer.COMMANDS,
CypherLexer.COMMIT,
CypherLexer.COMPOSITE,

@@ -192,2 +189,3 @@ CypherLexer.CONSTRAINT,

CypherLexer.EXISTS,
CypherLexer.EXTENDED_IDENTIFIER,
CypherLexer.FAIL,

@@ -265,3 +263,2 @@ CypherLexer.FALSE,

CypherLexer.ORDER,
CypherLexer.OUTPUT,
CypherLexer.PASSWORD,

@@ -271,3 +268,2 @@ CypherLexer.PASSWORDS,

CypherLexer.PATHS,
CypherLexer.PERIODIC,
CypherLexer.PLAINTEXT,

@@ -362,3 +358,2 @@ CypherLexer.POINT,

CypherLexer.VECTOR,
CypherLexer.VERBOSE,
CypherLexer.VERTEX,

@@ -365,0 +360,0 @@ CypherLexer.WAIT,

@@ -10,6 +10,6 @@ import type { ParserRuleContext, Token } from 'antlr4';

ClauseContext,
CypherVersionContext,
default as CypherParser,
FunctionNameContext,
LabelNameContext,
LabelNameIsContext,
LabelOrRelTypeContext,

@@ -33,6 +33,5 @@ ProcedureNameContext,

} from './helpers';
import {
SyntaxDiagnostic,
SyntaxErrorsListener,
} from './syntaxValidation/syntaxValidationHelpers';
import { SyntaxDiagnostic } from './syntaxValidation/syntaxValidation';
import { SyntaxErrorsListener } from './syntaxValidation/syntaxValidationHelpers';
import { CypherVersion } from './types';

@@ -52,2 +51,3 @@ export interface ParsedStatement {

collectedProcedures: ParsedProcedure[];
cypherVersion?: CypherVersion;
}

@@ -94,3 +94,3 @@

export type LabelOrRelType = {
labeltype: LabelType;
labelType: LabelType;
labelText: string;

@@ -183,4 +183,10 @@ couldCreateNewLabel: boolean;

const methodsFinder = new MethodsCollector(tokens);
const cypherVersionCollector = new CypherVersionCollector();
const errorListener = new SyntaxErrorsListener(tokens);
parser._parseListeners = [labelsCollector, variableFinder, methodsFinder];
parser._parseListeners = [
labelsCollector,
variableFinder,
methodsFinder,
cypherVersionCollector,
];
parser.addErrorListener(errorListener);

@@ -193,4 +199,7 @@ const ctx = parser.statementsOrCommands();

) === 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
: [];

@@ -212,2 +221,3 @@ if (!_internalFeatureFlags.consoleCommands) {

collectedProcedures: methodsFinder.procedures,
cypherVersion: cypherVersionCollector.cypherVersion,
};

@@ -239,3 +249,3 @@ });

exitEveryRule(ctx: unknown) {
if (ctx instanceof LabelNameContext || ctx instanceof LabelNameIsContext) {
if (ctx instanceof LabelNameContext) {
// If the parent ctx start doesn't coincide with this ctx start,

@@ -247,3 +257,3 @@ // it means the parser recovered from an error reading the label

this.labelOrRelTypes.push({
labeltype: getLabelType(ctx),
labelType: getLabelType(ctx),
labelText: ctx.getText(),

@@ -268,3 +278,3 @@ couldCreateNewLabel: couldCreateNewLabel(ctx),

this.labelOrRelTypes.push({
labeltype: getLabelType(ctx),
labelType: getLabelType(ctx),
labelText: symbolicName.start.text,

@@ -322,3 +332,3 @@ couldCreateNewLabel: couldCreateNewLabel(ctx),

if (ctx instanceof ProcedureResultItemContext) {
const variable = ctx.symbolicNameString().getText();
const variable = ctx.getText();
if (variable) {

@@ -416,2 +426,31 @@ this.variables.push(variable);

class CypherVersionCollector extends ParseTreeListener {
public cypherVersion: CypherVersion;
constructor() {
super();
}
enterEveryRule() {
/* no-op */
}
visitTerminal() {
/* no-op */
}
visitErrorNode() {
/* no-op */
}
exitEveryRule(ctx: unknown) {
if (ctx instanceof CypherVersionContext) {
this.cypherVersion =
ctx.getText() === '5'
? 'CYPHER 5'
: ctx.getText() === '25'
? 'CYPHER 25'
: undefined;
}
}
}
type CypherCmd = { type: 'cypher'; query: string };

@@ -446,2 +485,3 @@ type RuleTokens = {

stmts: StatementsOrCommandsContext,
tokens: Token[],
isEmptyStatement: boolean,

@@ -452,4 +492,9 @@ ): ParsedCommand {

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();

@@ -459,8 +504,6 @@ if (cypherStmt) {

// 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 };
}

@@ -527,3 +570,3 @@

const lambda = paramArgs.lambda();
const name = lambda?.unescapedSymbolicNameString()?.getText();
const name = lambda?.parameterName()?.getText();
const expression = lambda?.expression()?.getText();

@@ -586,3 +629,4 @@ if (name && expression) {

}
return { type: 'parse-error', start, stop };
const statement = inputstream.getText(start.start, stop.stop);
return { type: 'cypher', statement, start: start, stop: stop };
}

@@ -609,3 +653,3 @@ return { type: 'parse-error', start: stmts.start, stop: stmts.stop };

return [command]
.filter((cmd) => cmd.type !== 'cypher' && cmd.type !== 'parse-error')
.filter((cmd) => cmd.type !== 'cypher')
.map(

@@ -612,0 +656,0 @@ ({ start, stop }): SyntaxDiagnostic => ({

@@ -16,3 +16,3 @@ import {

import CypherCmdParserListener from './generated-parser/CypherCmdParserListener';
import { findCaret, isDefined } from './helpers';
import { findCaret, isDefined, resolveCypherVersion } from './helpers';
import { parserWrapper } from './parserWrapper';

@@ -204,6 +204,16 @@ import { Neo4jFunction, Neo4jProcedure } from './types';

if (method !== undefined) {
const cypherVersion = resolveCypherVersion(
statement.cypherVersion,
dbSchema,
);
if (method.methodType === MethodType.function) {
result = toSignatureHelp(dbSchema.functions, method);
result = toSignatureHelp(
dbSchema.functions?.[cypherVersion] ?? {},
method,
);
} else {
result = toSignatureHelp(dbSchema.procedures, method);
result = toSignatureHelp(
dbSchema.procedures?.[cypherVersion] ?? {},
method,
);
}

@@ -210,0 +220,0 @@ }

@@ -12,3 +12,2 @@ import { ParseTreeWalker, TerminalNode, Token } from 'antlr4';

LabelNameContext,
LabelNameIsContext,
LabelOrRelTypeContext,

@@ -20,2 +19,3 @@ LabelTypeContext,

ParameterContext,
ParameterNameContext,
ParamsArgsContext,

@@ -131,6 +131,2 @@ ProcedureNameContext,

exitLabelNameIs = (ctx: LabelNameIsContext) => {
this.addToken(ctx.start, CypherTokenType.label, ctx.getText());
};
exitLabelType = (ctx: LabelTypeContext) => {

@@ -194,2 +190,6 @@ const labelName = ctx.symbolicNameString()?.start;

exitParameterName = (ctx: ParameterNameContext) => {
this.addToken(ctx.start, CypherTokenType.variable, ctx.getText());
};
exitProcedureResultItem = (ctx: ProcedureResultItemContext) => {

@@ -196,0 +196,0 @@ this.addToken(ctx.start, CypherTokenType.variable, ctx.getText());

@@ -40,3 +40,2 @@ import { Token } from 'antlr4';

[CypherParser.RULE_labelExpression2]: 'a node label / rel type',
[CypherParser.RULE_labelExpression2Is]: 'a node label / rel type',
[CypherParser.RULE_procedureName]: 'a procedure name',

@@ -131,10 +130,2 @@ [CypherParser.RULE_stringLiteral]: 'a string',

if (options.length === 0) {
// options length is 0 should only happen when RULE_consoleCommand is hit and there are no other options
if (
ruleCandidates.find(
(ruleNumber) => ruleNumber === CypherParser.RULE_consoleCommand,
)
) {
return 'Console commands are unsupported in this environment.';
}
return undefined;

@@ -141,0 +132,0 @@ }

@@ -5,17 +5,18 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */

import { DiagnosticSeverity } from 'vscode-languageserver-types';
import { DbSchema } from '../dbSchema';
import { DiagnosticSeverity, Position } from 'vscode-languageserver-types';
import { DbSchema, Registry } from '../dbSchema';
import { CypherVersion, Neo4jFunction, Neo4jProcedure } from '../types';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { analyzeQuery, updateSignatureResolver } from './semanticAnalysis';
import { SyntaxDiagnostic } from './syntaxValidation';
export interface SemanticAnalysisResult {
errors: SemanticAnalysisElement[];
notifications: SemanticAnalysisElement[];
errors: SyntaxDiagnostic[];
notifications: SyntaxDiagnostic[];
}
export interface SemanticAnalysisElement {
severity: DiagnosticSeverity;
message: string;
position: {
startPosition: {
offset: number;

@@ -25,50 +26,79 @@ line: number;

};
endPosition: {
offset: number;
line: number;
column: number;
};
}
type SemanticAnalysisElementNoSeverity = Omit<
SemanticAnalysisElement,
'severity'
>;
let previousSchema: DbSchema | undefined = undefined;
const previousResolvers: {
[cypherVersion: CypherVersion]: {
functions: Registry<Neo4jFunction>;
procedures: Registry<Neo4jProcedure>;
};
} = {};
function copySettingSeverity(
elements: SemanticAnalysisElement[],
severity: DiagnosticSeverity,
): SyntaxDiagnostic[] {
return elements.map(({ message, startPosition, endPosition }) => ({
severity: severity,
message,
range: {
start: Position.create(startPosition.line, startPosition.column),
end: Position.create(endPosition.line, endPosition.column),
},
offsets: {
start: startPosition.offset,
end: endPosition.offset,
},
}));
}
function updateResolverForVersion(
dbSchema: DbSchema,
cypherVersion: CypherVersion,
) {
const previousResolver = previousResolvers?.[cypherVersion];
const currentResolver = {
procedures: dbSchema?.procedures?.[cypherVersion],
functions: dbSchema?.functions?.[cypherVersion],
};
if (JSON.stringify(previousResolver) !== JSON.stringify(currentResolver)) {
previousResolvers[cypherVersion] = currentResolver;
const procedures = Object.values(
dbSchema?.procedures?.[cypherVersion] ?? {},
);
const functions = Object.values(dbSchema?.functions?.[cypherVersion] ?? {});
updateSignatureResolver(
{
procedures: procedures,
functions: functions,
},
cypherVersion,
);
}
}
export function wrappedSemanticAnalysis(
query: string,
dbSchema: DbSchema,
parsedVersion?: CypherVersion,
): SemanticAnalysisResult {
try {
if (JSON.stringify(dbSchema) !== JSON.stringify(previousSchema)) {
previousSchema = dbSchema;
const procedures = Object.values(dbSchema.procedures ?? {});
const functions = Object.values(dbSchema.functions ?? {});
updateSignatureResolver({
procedures: procedures,
functions: functions,
});
}
const semanticErrorsResult = analyzeQuery(query);
const errors: SemanticAnalysisElementNoSeverity[] =
semanticErrorsResult.errors;
const notifications: SemanticAnalysisElementNoSeverity[] =
const defaultVersion = dbSchema?.defaultLanguage;
const cypherVersion = parsedVersion ?? defaultVersion ?? 'CYPHER 5';
updateResolverForVersion(dbSchema, cypherVersion);
const semanticErrorsResult = analyzeQuery(query, cypherVersion);
const errors: SemanticAnalysisElement[] = semanticErrorsResult.errors;
const notifications: SemanticAnalysisElement[] =
semanticErrorsResult.notifications;
return {
errors: errors.map(({ message, position }) => ({
severity: DiagnosticSeverity.Error,
message,
position: {
column: position.column,
line: position.line,
offset: position.offset,
},
})),
notifications: notifications.map(({ message, position }) => ({
severity: DiagnosticSeverity.Warning,
message,
position: {
column: position.column,
line: position.line,
offset: position.offset,
},
})),
errors: copySettingSeverity(errors, DiagnosticSeverity.Error),
notifications: copySettingSeverity(
notifications,
DiagnosticSeverity.Warning,
),
};

@@ -75,0 +105,0 @@ } catch (e) {

import {
Diagnostic,
DiagnosticSeverity,

@@ -7,4 +8,4 @@ DiagnosticTag,

import { ParserRuleContext, ParseTree, Token } from 'antlr4';
import { DbSchema } from '../dbSchema';
import { resolveCypherVersion } from '../helpers';
import {

@@ -18,9 +19,9 @@ LabelOrRelType,

} from '../parserWrapper';
import { Neo4jFunction } from '../types';
import {
SemanticAnalysisElement,
wrappedSemanticAnalysis,
} from './semanticAnalysisWrapper';
import { SyntaxDiagnostic } from './syntaxValidationHelpers';
import { Neo4jFunction, Neo4jProcedure } from '../types';
import { wrappedSemanticAnalysis } from './semanticAnalysisWrapper';
export type SyntaxDiagnostic = Diagnostic & {
offsets: { start: number; end: number };
};
function detectNonDeclaredLabel(

@@ -34,5 +35,5 @@ labelOrRelType: LabelOrRelType,

const notInDatabase =
(labelOrRelType.labeltype === LabelType.nodeLabelType &&
(labelOrRelType.labelType === LabelType.nodeLabelType &&
!dbLabels.has(normalizedLabelName)) ||
(labelOrRelType.labeltype === LabelType.relLabelType &&
(labelOrRelType.labelType === LabelType.relLabelType &&
!dbRelationshipTypes.has(normalizedLabelName)) ||

@@ -44,3 +45,3 @@ (!dbLabels.has(normalizedLabelName) &&

const message =
labelOrRelType.labeltype +
labelOrRelType.labelType +
' ' +

@@ -92,4 +93,26 @@ labelName +

functionsSchema: Record<string, Neo4jFunction>,
procedureSchema: Record<string, Neo4jProcedure>,
): SyntaxDiagnostic | undefined {
const lowercaseFunctionName = parsedFunction.name.toLowerCase();
const exists = functionExists(parsedFunction, functionsSchema);
if (!exists) {
const existsAsProcedure =
procedureSchema && Boolean(procedureSchema[parsedFunction.name]);
if (existsAsProcedure) {
return generateProcedureUsedAsFunctionError(parsedFunction);
}
return generateFunctionNotFoundError(parsedFunction);
}
}
function functionExists(
functionCandidate: ParsedFunction,
functionsSchema: Record<string, Neo4jFunction>,
): boolean {
if (!functionCandidate || !functionsSchema) {
return false;
}
const functionExistsWithExactName = Boolean(
functionsSchema[functionCandidate.name],
);
const lowercaseFunctionName = functionCandidate.name.toLowerCase();
const caseInsensitiveFunctionInDatabase =

@@ -99,15 +122,18 @@ functionsSchema[lowercaseFunctionName];

// Built-in functions are case-insensitive in the database
if (
caseInsensitiveFunctionInDatabase &&
caseInsensitiveFunctionInDatabase.isBuiltIn
) {
return undefined;
}
return (
functionExistsWithExactName ||
(caseInsensitiveFunctionInDatabase &&
caseInsensitiveFunctionInDatabase.isBuiltIn)
);
}
const functionExistsWithExactName = Boolean(
functionsSchema[parsedFunction.name],
function generateFunctionUsedAsProcedureError(
parsedProcedure: ParsedProcedure,
): SyntaxDiagnostic {
return generateSyntaxDiagnostic(
parsedProcedure.rawText,
parsedProcedure,
DiagnosticSeverity.Error,
`${parsedProcedure.name} is a function, not a procedure. Did you mean to use the function ${parsedProcedure.name} with a RETURN instead of a CALL clause?`,
);
if (!functionExistsWithExactName) {
return generateFunctionNotFoundError(parsedFunction);
}
}

@@ -126,2 +152,13 @@

function generateProcedureUsedAsFunctionError(
parsedFunction: ParsedFunction,
): SyntaxDiagnostic {
return generateSyntaxDiagnostic(
parsedFunction.rawText,
parsedFunction,
DiagnosticSeverity.Error,
`${parsedFunction.name} is a procedure, not a function. Did you mean to call the procedure ${parsedFunction.name} inside a CALL clause?`,
);
}
function generateProcedureNotFoundError(

@@ -140,3 +177,5 @@ parsedProcedure: ParsedProcedure,

parsedProcedure: ParsedProcedure,
deprecatedBy: string | undefined,
): SyntaxDiagnostic {
const procDeprecatedWarning = `Procedure ${parsedProcedure.name} is deprecated.`;
return generateSyntaxDiagnostic(

@@ -146,3 +185,5 @@ parsedProcedure.rawText,

DiagnosticSeverity.Warning,
`Procedure ${parsedProcedure.name} is deprecated.`,
deprecatedBy
? procDeprecatedWarning + ` Alternative: ${deprecatedBy}`
: procDeprecatedWarning,
true,

@@ -154,3 +195,5 @@ );

parsedFunction: ParsedFunction,
deprecatedBy: string | undefined,
): SyntaxDiagnostic {
const funcDeprecatedWarning = `Function ${parsedFunction.name} is deprecated.`;
return generateSyntaxDiagnostic(

@@ -160,3 +203,5 @@ parsedFunction.rawText,

DiagnosticSeverity.Warning,
`Function ${parsedFunction.name} is deprecated.`,
deprecatedBy
? funcDeprecatedWarning + ` Alternative: ${deprecatedBy}`
: funcDeprecatedWarning,
true,

@@ -191,76 +236,2 @@ );

type FixSemanticPositionsArgs = {
semanticElements: SemanticAnalysisElement[];
parseResult: ParsedStatement;
};
function fixSemanticAnalysisPositions({
semanticElements,
parseResult,
}: FixSemanticPositionsArgs): SyntaxDiagnostic[] {
const cmd = parseResult.command;
return semanticElements.map((e) => {
let token: Token | undefined = undefined;
const start = Position.create(
e.position.line - 1 + cmd.start.line - 1,
e.position.column - 1 + (e.position.line === 1 ? cmd.start.column : 0),
);
const startOffset = e.position.offset + cmd.start.start;
const toExplore: ParseTree[] = [parseResult.ctx];
while (toExplore.length > 0) {
const current: ParseTree = toExplore.pop();
if (current instanceof ParserRuleContext) {
const startToken = current.start;
const stopToken = current.stop;
if (
startToken.start <= startOffset &&
stopToken &&
startOffset <= stopToken.stop
) {
token = stopToken;
if (startToken.start < startOffset && current.children) {
current.children.forEach((child) => toExplore.push(child));
}
}
}
}
if (token === undefined) {
return {
severity: e.severity,
message: e.message,
range: {
start: start,
end: start,
},
offsets: {
start: startOffset,
end: startOffset,
},
};
} else {
return {
severity: e.severity,
message: e.message,
range: {
start: start,
end: Position.create(
token.line - 1,
token.column + token.text.length,
),
},
offsets: {
start: startOffset,
end: token.stop + 1,
},
};
}
});
}
export function sortByPositionAndMessage(

@@ -279,37 +250,39 @@ a: SyntaxDiagnostic,

export function lintCypherQuery(
query: string,
dbSchema: DbSchema,
): SyntaxDiagnostic[] {
const syntaxErrors = validateSyntax(query, dbSchema);
// If there are any syntactic errors in the query, do not run the semantic validation
if (syntaxErrors.find((d) => d.severity === DiagnosticSeverity.Error)) {
return syntaxErrors;
}
type FixSemanticPositionsArgs = {
semanticDiagnostics: SyntaxDiagnostic[];
parseResult: ParsedStatement;
};
const semanticErrors = validateSemantics(query, dbSchema);
return syntaxErrors.concat(semanticErrors);
}
function fixOffsets({
semanticDiagnostics,
parseResult,
}: FixSemanticPositionsArgs): SyntaxDiagnostic[] {
const cmd = parseResult.command;
return semanticDiagnostics.map((e) => {
const { range, offsets } = e;
const lineAdjust = cmd.start.line - 1;
const colAdjust = range.start.line === 0 ? cmd.start.column : 0;
const offsetAdjust = cmd.start.start;
export function validateSyntax(
query: string,
dbSchema: DbSchema,
): SyntaxDiagnostic[] {
if (query.length === 0) {
return [];
}
const statements = parserWrapper.parse(query);
const result = statements.statementsParsing.flatMap((statement) => {
const syntaxErrors = statement.syntaxErrors;
const labelWarnings = warnOnUndeclaredLabels(statement, dbSchema);
return syntaxErrors.concat(labelWarnings).sort(sortByPositionAndMessage);
const start = Position.create(
range.start.line + lineAdjust,
range.start.character + colAdjust,
);
const end = Position.create(
range.end.line + lineAdjust,
range.end.character + colAdjust,
);
const adjustedRange = { start, end };
const adjustedOffset = {
start: offsets.start + offsetAdjust,
end: offsets.end + offsetAdjust,
};
return { ...e, range: adjustedRange, offsets: adjustedOffset };
});
return result;
}
/**
* Assumes the provided query has no parse errors
*/
export function validateSemantics(
export function lintCypherQuery(
query: string,

@@ -321,44 +294,42 @@ dbSchema: DbSchema,

const statements = cachedParse.statementsParsing;
const semanticErrors = statements.flatMap((current) => {
if (current.syntaxErrors.length === 0) {
const cmd = current.command;
if (cmd.type === 'cypher' && cmd.statement.length > 0) {
const functionErrors = errorOnUndeclaredFunctions(current, dbSchema);
const procedureErrors = errorOnUndeclaredProcedures(
current,
dbSchema,
);
const procedureWarnings = warningOnDeprecatedProcedure(
current,
dbSchema,
);
const functionWarnings = warningOnDeprecatedFunction(
current,
dbSchema,
);
const errors = statements.flatMap((current) => {
const cmd = current.command;
if (cmd.type === 'cypher' && cmd.statement.length > 0) {
const functionErrors = errorOnUndeclaredFunctions(current, dbSchema);
const procedureErrors = errorOnUndeclaredProcedures(current, dbSchema);
const procedureWarnings = warningOnDeprecatedProcedure(
current,
dbSchema,
);
const functionWarnings = warningOnDeprecatedFunction(current, dbSchema);
const labelWarnings = warnOnUndeclaredLabels(current, dbSchema);
const { notifications, errors } = wrappedSemanticAnalysis(
cmd.statement,
dbSchema,
);
const { notifications, errors } = wrappedSemanticAnalysis(
cmd.statement,
dbSchema,
current.cypherVersion,
);
const elements = notifications.concat(errors);
const semanticDiagnostics = fixSemanticAnalysisPositions({
semanticElements: elements,
parseResult: current,
});
return semanticDiagnostics
.concat(
functionErrors,
procedureErrors,
functionWarnings,
procedureWarnings,
)
.sort(sortByPositionAndMessage);
}
// This contains both the syntax and the semantic errors
const rawSemanticDiagnostics = notifications.concat(errors);
const semanticDiagnostics = fixOffsets({
semanticDiagnostics: rawSemanticDiagnostics,
parseResult: current,
});
return semanticDiagnostics
.concat(
labelWarnings,
functionErrors,
procedureErrors,
functionWarnings,
procedureWarnings,
)
.sort(sortByPositionAndMessage);
}
return [];
// There could be console command errors
return current.syntaxErrors;
});
return semanticErrors;
return errors;
}

@@ -375,10 +346,18 @@

if (dbSchema.procedures) {
const cypherVersion = resolveCypherVersion(
parsingResult.cypherVersion,
dbSchema,
);
const proceduresInQuery = parsingResult.collectedProcedures;
proceduresInQuery.forEach((parsedProcedure) => {
const procedureDeprecated =
dbSchema.procedures?.[parsedProcedure.name]?.option?.deprecated;
if (procedureDeprecated) {
warnings.push(generateProcedureDeprecatedWarning(parsedProcedure));
}
const proc = dbSchema.procedures?.[cypherVersion]?.[parsedProcedure.name];
const procedureDeprecated = proc?.option?.deprecated;
const deprecatedBy = proc?.deprecatedBy;
if (deprecatedBy)
if (procedureDeprecated) {
warnings.push(
generateProcedureDeprecatedWarning(parsedProcedure, deprecatedBy),
);
}
});

@@ -395,8 +374,15 @@ }

if (dbSchema.functions) {
const cypherVersion = resolveCypherVersion(
parsingResult.cypherVersion,
dbSchema,
);
const functionsInQuery = parsingResult.collectedFunctions;
functionsInQuery.forEach((parsedFunction) => {
const functionDeprecated =
dbSchema.functions?.[parsedFunction.name]?.isDeprecated;
const fn = dbSchema.functions?.[cypherVersion]?.[parsedFunction.name];
const functionDeprecated = fn?.isDeprecated;
const deprecatedBy = fn?.deprecatedBy;
if (functionDeprecated) {
warnings.push(generateFunctionDeprecatedWarning(parsedFunction));
warnings.push(
generateFunctionDeprecatedWarning(parsedFunction, deprecatedBy),
);
}

@@ -415,2 +401,6 @@ });

if (dbSchema.functions) {
const cypherVersion = resolveCypherVersion(
parsingResult.cypherVersion,
dbSchema,
);
const functionsInQuery = parsingResult.collectedFunctions;

@@ -421,3 +411,4 @@

parsedFunction,
dbSchema.functions,
dbSchema.functions?.[cypherVersion] ?? {},
dbSchema.procedures?.[cypherVersion] ?? {},
);

@@ -439,2 +430,6 @@

if (dbSchema.procedures) {
const cypherVersion = resolveCypherVersion(
parsingResult.cypherVersion,
dbSchema,
);
const proceduresInQuery = parsingResult.collectedProcedures;

@@ -444,6 +439,14 @@

const procedureExists = Boolean(
dbSchema.procedures[parsedProcedure.name],
dbSchema.procedures?.[cypherVersion]?.[parsedProcedure.name],
);
if (!procedureExists) {
errors.push(generateProcedureNotFoundError(parsedProcedure));
const existsAsFunction = functionExists(
parsedProcedure,
dbSchema.functions?.[cypherVersion] ?? {},
);
if (existsAsFunction) {
errors.push(generateFunctionUsedAsProcedureError(parsedProcedure));
} else {
errors.push(generateProcedureNotFoundError(parsedProcedure));
}
}

@@ -450,0 +453,0 @@ });

@@ -8,7 +8,3 @@ import {

import type { ParserRuleContext } from 'antlr4-c3';
import {
Diagnostic,
DiagnosticSeverity,
Position,
} from 'vscode-languageserver-types';
import { DiagnosticSeverity, Position } from 'vscode-languageserver-types';
import CypherLexer from '../generated-parser/CypherCmdLexer';

@@ -18,7 +14,4 @@ import CypherParser from '../generated-parser/CypherCmdParser';

import { completionCoreErrormessage } from './completionCoreErrors';
import { SyntaxDiagnostic } from './syntaxValidation';
export type SyntaxDiagnostic = Diagnostic & {
offsets: { start: number; end: number };
};
export class SyntaxErrorsListener implements ANTLRErrorListener<CommonToken> {

@@ -25,0 +18,0 @@ errors: SyntaxDiagnostic[];

@@ -6,2 +6,3 @@ import {

import { DbSchema } from '../../dbSchema';
import { _internalFeatureFlags } from '../../featureFlags';
import { testData } from '../testData';

@@ -11,2 +12,13 @@ import { testCompletions } from './completionAssertionHelpers';

describe('function invocations', () => {
let isCypher25: boolean;
beforeAll(() => {
isCypher25 = _internalFeatureFlags.cypher25;
_internalFeatureFlags.cypher25 = true;
});
afterAll(() => {
_internalFeatureFlags.cypher25 = isCypher25;
});
const dbSchema: DbSchema = testData.mockSchema;

@@ -25,4 +37,4 @@ const functions = dbSchema.functions;

detail: '(function)',
signature: functions['acos'].signature,
documentation: functions['acos'].description,
signature: functions['CYPHER 5']['acos'].signature,
documentation: functions['CYPHER 5']['acos'].description,
},

@@ -38,4 +50,4 @@ {

detail: '(function)',
signature: functions['apoc.agg.graph'].signature,
documentation: functions['apoc.agg.graph'].description,
signature: functions['CYPHER 5']['apoc.agg.graph'].signature,
documentation: functions['CYPHER 5']['apoc.agg.graph'].description,
},

@@ -46,4 +58,4 @@ {

detail: '(function)',
signature: functions['apoc.coll.pairs'].signature,
documentation: functions['apoc.coll.pairs'].description,
signature: functions['CYPHER 5']['apoc.coll.pairs'].signature,
documentation: functions['CYPHER 5']['apoc.coll.pairs'].description,
},

@@ -54,4 +66,4 @@ {

detail: '(function)',
signature: functions['apoc.create.uuid'].signature,
documentation: functions['apoc.create.uuid'].description,
signature: functions['CYPHER 5']['apoc.create.uuid'].signature,
documentation: functions['CYPHER 5']['apoc.create.uuid'].description,
tags: [CompletionItemTag.Deprecated],

@@ -73,4 +85,4 @@ },

detail: '(function)',
signature: functions['acos'].signature,
documentation: functions['acos'].description,
signature: functions['CYPHER 5']['acos'].signature,
documentation: functions['CYPHER 5']['acos'].description,
},

@@ -81,4 +93,4 @@ {

detail: '(function)',
signature: functions['apoc.agg.graph'].signature,
documentation: functions['apoc.agg.graph'].description,
signature: functions['CYPHER 5']['apoc.agg.graph'].signature,
documentation: functions['CYPHER 5']['apoc.agg.graph'].description,
},

@@ -89,4 +101,4 @@ {

detail: '(function)',
signature: functions['apoc.coll.pairs'].signature,
documentation: functions['apoc.coll.pairs'].description,
signature: functions['CYPHER 5']['apoc.coll.pairs'].signature,
documentation: functions['CYPHER 5']['apoc.coll.pairs'].description,
},

@@ -97,4 +109,4 @@ {

detail: '(function)',
signature: functions['apoc.create.uuid'].signature,
documentation: functions['apoc.create.uuid'].description,
signature: functions['CYPHER 5']['apoc.create.uuid'].signature,
documentation: functions['CYPHER 5']['apoc.create.uuid'].description,
tags: [CompletionItemTag.Deprecated],

@@ -143,4 +155,4 @@ },

detail: '(function)',
signature: functions['apoc.agg.first'].signature,
documentation: functions['apoc.agg.first'].description,
signature: functions['CYPHER 5']['apoc.agg.first'].signature,
documentation: functions['CYPHER 5']['apoc.agg.first'].description,
},

@@ -151,4 +163,4 @@ {

detail: '(function)',
signature: functions['apoc.agg.last'].signature,
documentation: functions['apoc.agg.last'].description,
signature: functions['CYPHER 5']['apoc.agg.last'].signature,
documentation: functions['CYPHER 5']['apoc.agg.last'].description,
},

@@ -159,4 +171,4 @@ {

detail: '(function)',
signature: functions['apoc.agg.slice'].signature,
documentation: functions['apoc.agg.slice'].description,
signature: functions['CYPHER 5']['apoc.agg.slice'].signature,
documentation: functions['CYPHER 5']['apoc.agg.slice'].description,
},

@@ -194,4 +206,4 @@ ],

detail: '(function)',
signature: functions['apoc.agg.first'].signature,
documentation: functions['apoc.agg.first'].description,
signature: functions['CYPHER 5']['apoc.agg.first'].signature,
documentation: functions['CYPHER 5']['apoc.agg.first'].description,
},

@@ -202,4 +214,4 @@ {

detail: '(function)',
signature: functions['apoc.agg.last'].signature,
documentation: functions['apoc.agg.last'].description,
signature: functions['CYPHER 5']['apoc.agg.last'].signature,
documentation: functions['CYPHER 5']['apoc.agg.last'].description,
},

@@ -210,4 +222,4 @@ {

detail: '(function)',
signature: functions['apoc.agg.slice'].signature,
documentation: functions['apoc.agg.slice'].description,
signature: functions['CYPHER 5']['apoc.agg.slice'].signature,
documentation: functions['CYPHER 5']['apoc.agg.slice'].description,
},

@@ -325,4 +337,5 @@ ],

detail: '(function)',
signature: functions['apoc.agg.percentiles'].signature,
documentation: functions['apoc.agg.percentiles'].description,
signature: functions['CYPHER 5']['apoc.agg.percentiles'].signature,
documentation:
functions['CYPHER 5']['apoc.agg.percentiles'].description,
},

@@ -333,4 +346,4 @@ {

detail: '(function)',
signature: functions['acos'].signature,
documentation: functions['acos'].description,
signature: functions['CYPHER 5']['acos'].signature,
documentation: functions['CYPHER 5']['acos'].description,
},

@@ -351,4 +364,5 @@ ],

detail: '(function)',
signature: functions['apoc.agg.percentiles'].signature,
documentation: functions['apoc.agg.percentiles'].description,
signature: functions['CYPHER 5']['apoc.agg.percentiles'].signature,
documentation:
functions['CYPHER 5']['apoc.agg.percentiles'].description,
},

@@ -364,4 +378,4 @@ {

detail: '(function)',
signature: functions['acos'].signature,
documentation: functions['acos'].description,
signature: functions['CYPHER 5']['acos'].signature,
documentation: functions['CYPHER 5']['acos'].description,
},

@@ -378,4 +392,6 @@ ],

functions: {
math: { ...testData.emptyFunction, name: 'math' },
'math.max': { ...testData.emptyFunction, name: 'math.max' },
'CYPHER 5': {
math: { ...testData.emptyFunction, name: 'math' },
'math.max': { ...testData.emptyFunction, name: 'math.max' },
},
},

@@ -410,4 +426,4 @@ },

detail: '(function)',
signature: functions['apoc.create.uuid'].signature,
documentation: functions['apoc.create.uuid'].description,
signature: functions['CYPHER 5']['apoc.create.uuid'].signature,
documentation: functions['CYPHER 5']['apoc.create.uuid'].description,
tags: [CompletionItemTag.Deprecated],

@@ -418,2 +434,35 @@ },

});
test('Functions are completed based on cypher version', () => {
// apoc.create.uuid was deprecated in Cypher 5 and removed in Cypher 25
testCompletions({
query: 'CYPHER 5 RETURN apoc.create.',
dbSchema,
expected: [
{
label: 'uuid',
kind: CompletionItemKind.Function,
detail: '(function)',
signature: functions['CYPHER 5']['apoc.create.uuid'].signature,
documentation: functions['CYPHER 5']['apoc.create.uuid'].description,
tags: [CompletionItemTag.Deprecated],
},
],
});
testCompletions({
query: 'CYPHER 25 RETURN apoc.create.',
dbSchema,
excluded: [
{
label: 'uuid',
kind: CompletionItemKind.Function,
detail: '(function)',
signature: functions['CYPHER 5']['apoc.create.uuid'].signature,
documentation: functions['CYPHER 5']['apoc.create.uuid'].description,
tags: [CompletionItemTag.Deprecated],
},
],
});
});
});

@@ -8,2 +8,23 @@ import { CompletionItemKind } from 'vscode-languageserver-types';

describe('Misc auto-completion', () => {
test('Correctly completes cypher version number', () => {
const query = 'CYPHER ';
testCompletions({
query,
expected: [{ label: '5', kind: CompletionItemKind.EnumMember }],
});
});
test('Correctly completes CYPHER when keyword is not finished, optionally with version', () => {
const query = 'CYP';
testCompletions({
query,
expected: [
{ label: 'CYPHER 5', kind: CompletionItemKind.Keyword },
{ label: 'CYPHER', kind: CompletionItemKind.Keyword },
],
});
});
test('Correctly completes empty statement', () => {

@@ -207,3 +228,2 @@ const query = '';

{ kind: 14, label: 'FOR' },
{ kind: 14, label: 'ON' },
],

@@ -210,0 +230,0 @@ });

@@ -86,3 +86,3 @@ import { CompletionItemKind } from 'vscode-languageserver-types';

const query =
'CREATE CONSTRAINT abc ON (n:person) ASSERT EXISTS n.name OPTIONS ';
'CREATE CONSTRAINT abc FOR (n:person) REQUIRE n.name IS NOT NULL OPTIONS ';
testCompletions({

@@ -89,0 +89,0 @@ query,

@@ -6,2 +6,3 @@ import {

import { DbSchema } from '../../dbSchema';
import { _internalFeatureFlags } from '../../featureFlags';
import { testData } from '../testData';

@@ -11,2 +12,13 @@ import { testCompletions } from './completionAssertionHelpers';

describe('Procedures auto-completion', () => {
let isCypher25: boolean;
beforeAll(() => {
isCypher25 = _internalFeatureFlags.cypher25;
_internalFeatureFlags.cypher25 = true;
});
afterAll(() => {
_internalFeatureFlags.cypher25 = isCypher25;
});
const procedures = testData.mockSchema.procedures;

@@ -16,14 +28,19 @@

procedures: {
'tx.getMetaData': procedures['tx.getMetaData'],
'db.index.fulltext.awaitEventuallyConsistentIndexRefresh':
procedures['db.index.fulltext.awaitEventuallyConsistentIndexRefresh'],
'db.ping': procedures['db.ping'],
'db.stats.retrieve': procedures['db.stats.retrieve'],
'db.stats.collect': procedures['db.stats.collect'],
'db.stats.clear': procedures['db.stats.clear'],
'dbms.components': procedures['dbms.components'],
'cdc.current': procedures['cdc.current'],
'jwt.security.requestAccess': {
...testData.emptyProcedure,
name: 'jwt.security.requestAccess',
'CYPHER 5': {
'tx.getMetaData': procedures['CYPHER 5']['tx.getMetaData'],
'db.index.fulltext.awaitEventuallyConsistentIndexRefresh':
procedures['CYPHER 5'][
'db.index.fulltext.awaitEventuallyConsistentIndexRefresh'
],
'db.ping': procedures['CYPHER 5']['db.ping'],
'db.stats.retrieve': procedures['CYPHER 5']['db.stats.retrieve'],
'db.stats.collect': procedures['CYPHER 5']['db.stats.collect'],
'db.stats.clear': procedures['CYPHER 5']['db.stats.clear'],
'dbms.components': procedures['CYPHER 5']['dbms.components'],
'cdc.current': procedures['CYPHER 5']['cdc.current'],
'jwt.security.requestAccess': {
...testData.emptyProcedure,
name: 'jwt.security.requestAccess',
},
'apoc.create.uuids': procedures['CYPHER 5']['apoc.create.uuids'],
},

@@ -71,4 +88,4 @@ },

detail: '(procedure)',
signature: procedures['tx.getMetaData'].signature,
documentation: procedures['tx.getMetaData'].description,
signature: procedures['CYPHER 5']['tx.getMetaData'].signature,
documentation: procedures['CYPHER 5']['tx.getMetaData'].description,
},

@@ -79,4 +96,4 @@ {

detail: '(procedure)',
signature: procedures['cdc.current'].signature,
documentation: procedures['cdc.current'].description,
signature: procedures['CYPHER 5']['cdc.current'].signature,
documentation: procedures['CYPHER 5']['cdc.current'].description,
tags: [CompletionItemTag.Deprecated],

@@ -110,4 +127,4 @@ },

detail: '(procedure)',
signature: procedures['db.ping'].signature,
documentation: procedures['db.ping'].description,
signature: procedures['CYPHER 5']['db.ping'].signature,
documentation: procedures['CYPHER 5']['db.ping'].description,
},

@@ -133,4 +150,5 @@ ],

detail: '(procedure)',
signature: procedures['db.stats.retrieve'].signature,
documentation: procedures['db.stats.retrieve'].description,
signature: procedures['CYPHER 5']['db.stats.retrieve'].signature,
documentation:
procedures['CYPHER 5']['db.stats.retrieve'].description,
},

@@ -141,4 +159,4 @@ {

detail: '(procedure)',
signature: procedures['db.stats.collect'].signature,
documentation: procedures['db.stats.collect'].description,
signature: procedures['CYPHER 5']['db.stats.collect'].signature,
documentation: procedures['CYPHER 5']['db.stats.collect'].description,
},

@@ -149,4 +167,4 @@ {

detail: '(procedure)',
signature: procedures['db.stats.clear'].signature,
documentation: procedures['db.stats.clear'].description,
signature: procedures['CYPHER 5']['db.stats.clear'].signature,
documentation: procedures['CYPHER 5']['db.stats.clear'].description,
},

@@ -184,4 +202,5 @@ ],

detail: '(procedure)',
signature: procedures['db.stats.retrieve'].signature,
documentation: procedures['db.stats.retrieve'].description,
signature: procedures['CYPHER 5']['db.stats.retrieve'].signature,
documentation:
procedures['CYPHER 5']['db.stats.retrieve'].description,
},

@@ -192,4 +211,4 @@ {

detail: '(procedure)',
signature: procedures['db.stats.collect'].signature,
documentation: procedures['db.stats.collect'].description,
signature: procedures['CYPHER 5']['db.stats.collect'].signature,
documentation: procedures['CYPHER 5']['db.stats.collect'].description,
},

@@ -200,4 +219,4 @@ {

detail: '(procedure)',
signature: procedures['db.stats.clear'].signature,
documentation: procedures['db.stats.clear'].description,
signature: procedures['CYPHER 5']['db.stats.clear'].signature,
documentation: procedures['CYPHER 5']['db.stats.clear'].description,
},

@@ -274,4 +293,4 @@ ],

detail: '(procedure)',
signature: procedures['db.ping'].signature,
documentation: procedures['db.ping'].description,
signature: procedures['CYPHER 5']['db.ping'].signature,
documentation: procedures['CYPHER 5']['db.ping'].description,
},

@@ -298,4 +317,4 @@ {

detail: '(procedure)',
signature: procedures['cdc.current'].signature,
documentation: procedures['cdc.current'].description,
signature: procedures['CYPHER 5']['cdc.current'].signature,
documentation: procedures['CYPHER 5']['cdc.current'].description,
tags: [CompletionItemTag.Deprecated],

@@ -441,2 +460,37 @@ },

});
test('Procedures are completed based on cypher version', () => {
// apoc.create.uuids was deprecated in Cypher 5 and removed in Cypher 25
testCompletions({
query: 'CYPHER 5 CALL apoc.create.',
dbSchema,
expected: [
{
label: 'uuids',
kind: CompletionItemKind.Method,
detail: '(procedure)',
signature: procedures['CYPHER 5']['apoc.create.uuids'].signature,
documentation:
procedures['CYPHER 5']['apoc.create.uuids'].description,
tags: [CompletionItemTag.Deprecated],
},
],
});
testCompletions({
query: 'CYPHER 25 CALL apoc.create.',
dbSchema,
excluded: [
{
label: 'uuids',
kind: CompletionItemKind.Method,
detail: '(procedure)',
signature: procedures['CYPHER 5']['apoc.create.uuids'].signature,
documentation:
procedures['CYPHER 5']['apoc.create.uuids'].description,
tags: [CompletionItemTag.Deprecated],
},
],
});
});
});

@@ -10,5 +10,7 @@ import { CompletionItemKind } from 'vscode-languageserver-types';

functions: {
'apoc.util.sleep': {
...testData.emptyFunction,
name: 'apoc.util.sleep',
'CYPHER 5': {
'apoc.util.sleep': {
...testData.emptyFunction,
name: 'apoc.util.sleep',
},
},

@@ -15,0 +17,0 @@ },

@@ -119,3 +119,3 @@ import { CompletionItemKind } from 'vscode-languageserver-types';

test('suggests variables for index hint rule', () => {
const query = 'match (n) USING BTREE INDEX ';
const query = 'match (n) USING RANGE INDEX ';

@@ -122,0 +122,0 @@ testCompletions({

@@ -7,6 +7,3 @@ /* eslint-disable no-console */

import { applySyntaxColouring } from '../../syntaxColouring/syntaxColouring';
import {
lintCypherQuery,
validateSyntax,
} from '../../syntaxValidation/syntaxValidation';
import { lintCypherQuery } from '../../syntaxValidation/syntaxValidation';
import { testData } from '../testData';

@@ -39,3 +36,3 @@ import {

parserWrapper.clearCache();
validateSyntax(simpleQuery, testData.mockSchema);
lintCypherQuery(simpleQuery, testData.mockSchema);
})

@@ -42,0 +39,0 @@ .add('simple - autocomplete next statement', function () {

@@ -39,3 +39,6 @@ import { autocomplete } from '../autocompletion/autocompletion';

describe('sanity checks', () => {
let consoleCommands: boolean;
beforeAll(() => {
consoleCommands = _internalFeatureFlags.consoleCommands;
_internalFeatureFlags.consoleCommands = true;

@@ -45,3 +48,3 @@ });

afterAll(() => {
_internalFeatureFlags.consoleCommands = false;
_internalFeatureFlags.consoleCommands = consoleCommands;
});

@@ -224,7 +227,10 @@

describe(':use', () => {
let consoleCommands: boolean;
beforeAll(() => {
consoleCommands = _internalFeatureFlags.consoleCommands;
_internalFeatureFlags.consoleCommands = true;
});
afterAll(() => {
_internalFeatureFlags.consoleCommands = false;
_internalFeatureFlags.consoleCommands = consoleCommands;
});

@@ -291,7 +297,10 @@ test('parses without arg', () => {

describe('parameters', () => {
let consoleCommands: boolean;
beforeAll(() => {
consoleCommands = _internalFeatureFlags.consoleCommands;
_internalFeatureFlags.consoleCommands = true;
});
afterAll(() => {
_internalFeatureFlags.consoleCommands = false;
_internalFeatureFlags.consoleCommands = consoleCommands;
});

@@ -324,8 +333,29 @@ test('basic param usage', () => {

test('allows setting parameters with escaped name', () => {
expectParsedCommands(':param `foo foo` => bar', [
{
type: 'set-parameters',
parameters: [{ name: '`foo foo`', expression: 'bar' }],
},
]);
expectParsedCommands(':param {`a a a`: 2, `b-b-b`: rand()}', [
{
type: 'set-parameters',
parameters: [
{ name: '`a a a`', expression: '2' },
{ name: '`b-b-b`', expression: 'rand()' },
],
},
]);
});
test('autocompletes expressions', () => {
const arrowCompletions = autocomplete(':param foo => ', {
functions: {
'duration.inSeconds': {
...testData.emptyFunction,
name: 'duration.inSeconds',
'CYPHER 5': {
'duration.inSeconds': {
...testData.emptyFunction,
name: 'duration.inSeconds',
},
},

@@ -336,5 +366,7 @@ },

functions: {
'duration.inSeconds': {
...testData.emptyFunction,
name: 'duration.inSeconds',
'CYPHER 5': {
'duration.inSeconds': {
...testData.emptyFunction,
name: 'duration.inSeconds',
},
},

@@ -536,3 +568,6 @@ },

describe('server', () => {
let consoleCommands: boolean;
beforeAll(() => {
consoleCommands = _internalFeatureFlags.consoleCommands;
_internalFeatureFlags.consoleCommands = true;

@@ -542,3 +577,3 @@ });

afterAll(() => {
_internalFeatureFlags.consoleCommands = false;
_internalFeatureFlags.consoleCommands = consoleCommands;
});

@@ -558,5 +593,7 @@

functions: {
'duration.inSeconds': {
...testData.emptyFunction,
name: 'duration.inSeconds',
'CYPHER 5': {
'duration.inSeconds': {
...testData.emptyFunction,
name: 'duration.inSeconds',
},
},

@@ -642,8 +679,13 @@ },

describe('command parser also handles cypher', () => {
let consoleCommands: boolean;
beforeAll(() => {
consoleCommands = _internalFeatureFlags.consoleCommands;
_internalFeatureFlags.consoleCommands = true;
});
afterAll(() => {
_internalFeatureFlags.consoleCommands = false;
_internalFeatureFlags.consoleCommands = consoleCommands;
});
test('parses cypher', () => {

@@ -650,0 +692,0 @@ expectParsedCommands('MATCH (n) RETURN n', [

import { SignatureHelp } from 'vscode-languageserver-types';
import { DbSchema } from '../dbSchema';
import { _internalFeatureFlags } from '../featureFlags';
import {

@@ -24,5 +25,17 @@ emptyResult,

describe('Procedures signature help', () => {
let isCypher25: boolean;
beforeAll(() => {
isCypher25 = _internalFeatureFlags.cypher25;
_internalFeatureFlags.cypher25 = true;
});
afterAll(() => {
_internalFeatureFlags.cypher25 = isCypher25;
});
const dbSchema = testData.mockSchema;
const procedureName = 'apoc.do.when';
const signature = toSignatureInformation(dbSchema.procedures[procedureName]);
const procedure = dbSchema.procedures['CYPHER 5'][procedureName];
const signature = toSignatureInformation(procedure);

@@ -230,8 +243,41 @@ function expectedArgIndex(i: number): SignatureHelp {

});
test('Procedures signature help depends on cypher version', () => {
const dbSchema = {
functions: {},
procedures: {
'CYPHER 5': {
[procedureName]: procedure,
},
'CYPHER 25': {},
},
};
testSignatureHelp(
'CYPHER 5 CALL apoc.do.when(',
dbSchema,
expectedArgIndex(0),
);
testSignatureHelp('CYPHER 25 CALL apoc.do.when(', dbSchema, {
activeParameter: 0,
activeSignature: undefined,
signatures: [],
});
});
});
describe('Functions signature help', () => {
beforeAll(() => {
_internalFeatureFlags.cypher25 = true;
});
afterAll(() => {
_internalFeatureFlags.cypher25 = false;
});
const dbSchema = testData.mockSchema;
const functionName = 'apoc.coll.combinations';
const signature = toSignatureInformation(dbSchema.functions[functionName]);
const fn = dbSchema.functions['CYPHER 5'][functionName];
const signature = toSignatureInformation(fn);

@@ -418,2 +464,26 @@ function expectedArgIndex(i: number): SignatureHelp {

});
test('Functions signature help depends on cypher version', () => {
const dbSchema = {
procedures: {},
functions: {
'CYPHER 5': {
[functionName]: fn,
},
'CYPHER 25': {},
},
};
testSignatureHelp(
'CYPHER 5 RETURN apoc.coll.combinations(',
dbSchema,
expectedArgIndex(0),
);
testSignatureHelp('CYPHER 25 RETURN apoc.coll.combinations(', dbSchema, {
activeParameter: 0,
activeSignature: undefined,
signatures: [],
});
});
});

@@ -538,2 +538,242 @@ import { applySyntaxColouring } from '../../syntaxColouring/syntaxColouring';

});
test('Correctly colours console commands used as variables or labels', () => {
const query = `CREATE (n:clear {use: 0})
WITH $param AS map
RETURN map.propertyKey`;
expect(applySyntaxColouring(query)).toEqual([
{
bracketInfo: undefined,
length: 6,
position: {
line: 0,
startCharacter: 0,
startOffset: 0,
},
token: 'CREATE',
tokenType: 'keyword',
},
{
bracketInfo: {
bracketLevel: 0,
bracketType: 'parenthesis',
},
length: 1,
position: {
line: 0,
startCharacter: 7,
startOffset: 7,
},
token: '(',
tokenType: 'bracket',
},
{
bracketInfo: undefined,
length: 1,
position: {
line: 0,
startCharacter: 8,
startOffset: 8,
},
token: 'n',
tokenType: 'variable',
},
{
bracketInfo: undefined,
length: 1,
position: {
line: 0,
startCharacter: 9,
startOffset: 9,
},
token: ':',
tokenType: 'operator',
},
{
bracketInfo: undefined,
length: 5,
position: {
line: 0,
startCharacter: 10,
startOffset: 10,
},
token: 'clear',
tokenType: 'label',
},
{
bracketInfo: {
bracketLevel: 0,
bracketType: 'curly',
},
length: 1,
position: {
line: 0,
startCharacter: 16,
startOffset: 16,
},
token: '{',
tokenType: 'bracket',
},
{
bracketInfo: undefined,
length: 3,
position: {
line: 0,
startCharacter: 17,
startOffset: 17,
},
token: 'use',
tokenType: 'property',
},
{
bracketInfo: undefined,
length: 1,
position: {
line: 0,
startCharacter: 20,
startOffset: 20,
},
token: ':',
tokenType: 'operator',
},
{
bracketInfo: undefined,
length: 1,
position: {
line: 0,
startCharacter: 22,
startOffset: 22,
},
token: '0',
tokenType: 'numberLiteral',
},
{
bracketInfo: {
bracketLevel: 0,
bracketType: 'curly',
},
length: 1,
position: {
line: 0,
startCharacter: 23,
startOffset: 23,
},
token: '}',
tokenType: 'bracket',
},
{
bracketInfo: {
bracketLevel: 0,
bracketType: 'parenthesis',
},
length: 1,
position: {
line: 0,
startCharacter: 24,
startOffset: 24,
},
token: ')',
tokenType: 'bracket',
},
{
bracketInfo: undefined,
length: 4,
position: {
line: 1,
startCharacter: 0,
startOffset: 26,
},
token: 'WITH',
tokenType: 'keyword',
},
{
bracketInfo: undefined,
length: 1,
position: {
line: 1,
startCharacter: 5,
startOffset: 31,
},
token: '$',
tokenType: 'paramDollar',
},
{
bracketInfo: undefined,
length: 5,
position: {
line: 1,
startCharacter: 6,
startOffset: 32,
},
token: 'param',
tokenType: 'paramValue',
},
{
bracketInfo: undefined,
length: 2,
position: {
line: 1,
startCharacter: 12,
startOffset: 38,
},
token: 'AS',
tokenType: 'keyword',
},
{
bracketInfo: undefined,
length: 3,
position: {
line: 1,
startCharacter: 15,
startOffset: 41,
},
token: 'map',
tokenType: 'variable',
},
{
bracketInfo: undefined,
length: 6,
position: {
line: 2,
startCharacter: 0,
startOffset: 45,
},
token: 'RETURN',
tokenType: 'keyword',
},
{
bracketInfo: undefined,
length: 3,
position: {
line: 2,
startCharacter: 7,
startOffset: 52,
},
token: 'map',
tokenType: 'variable',
},
{
bracketInfo: undefined,
length: 1,
position: {
line: 2,
startCharacter: 10,
startOffset: 55,
},
token: '.',
tokenType: 'operator',
},
{
bracketInfo: undefined,
length: 11,
position: {
line: 2,
startCharacter: 11,
startOffset: 56,
},
token: 'propertyKey',
tokenType: 'property',
},
]);
});
});
import { DiagnosticTag } from 'vscode-languageserver-types';
import { _internalFeatureFlags } from '../../featureFlags';
import { testData } from '../testData';

@@ -6,3 +7,14 @@ import { getDiagnosticsForQuery } from './helpers';

describe('Functions semantic validation spec', () => {
test('Syntax validation warns on deprecated function when database can be contacted', () => {
let isCypher25: boolean;
beforeAll(() => {
isCypher25 = _internalFeatureFlags.cypher25;
_internalFeatureFlags.cypher25 = true;
});
afterAll(() => {
_internalFeatureFlags.cypher25 = isCypher25;
});
test('Syntax validation warns on deprecated function when database can be contacted and deprecated by is not present', () => {
const query = `RETURN id()`;

@@ -43,2 +55,145 @@ expect(

test('Syntax validation warns on deprecated function when database can be contacted and deprecated by is present', () => {
const query = `RETURN apoc.text.regreplace("Neo4j GraphQL Neo4j GraphQL", "GraphQL", "GRANDstack") AS output;`;
expect(
getDiagnosticsForQuery({
query,
dbSchema: {
labels: ['Dog', 'Cat'],
relationshipTypes: ['Person'],
functions: testData.mockSchema.functions,
},
}),
).toEqual([
{
message:
'Function apoc.text.regreplace is deprecated. Alternative: apoc.text.replace',
offsets: {
end: 27,
start: 7,
},
range: {
end: {
character: 27,
line: 0,
},
start: {
character: 7,
line: 0,
},
},
severity: 2,
tags: [2],
},
]);
});
test('Syntax validation error on function used as procedure returns helpful message', () => {
const query = `CALL abs(-50) YIELD *`;
expect(
getDiagnosticsForQuery({
query,
dbSchema: {
labels: ['Dog', 'Cat'],
relationshipTypes: ['Person'],
functions: testData.mockSchema.functions,
procedures: testData.mockSchema.procedures,
},
}),
).toEqual([
{
message:
'abs is a function, not a procedure. Did you mean to use the function abs with a RETURN instead of a CALL clause?',
offsets: {
end: 8,
start: 5,
},
range: {
end: {
character: 8,
line: 0,
},
start: {
character: 5,
line: 0,
},
},
severity: 1,
},
]);
});
test('Using improved message for function used as procedure is not case sensitive for built-in functions', () => {
const query = `CALL aBs(-50) YIELD *`;
expect(
getDiagnosticsForQuery({
query,
dbSchema: {
labels: ['Dog', 'Cat'],
relationshipTypes: ['Person'],
functions: testData.mockSchema.functions,
procedures: testData.mockSchema.procedures,
},
}),
).toEqual([
{
message:
'aBs is a function, not a procedure. Did you mean to use the function aBs with a RETURN instead of a CALL clause?',
offsets: {
end: 8,
start: 5,
},
range: {
end: {
character: 8,
line: 0,
},
start: {
character: 5,
line: 0,
},
},
severity: 1,
},
]);
});
test('Using improved message for function used as procedure is case sensitive for external functions', () => {
const query = `CALL apoc.text.TOUpperCase("message") YIELD *`;
expect(
getDiagnosticsForQuery({
query,
dbSchema: {
labels: ['Dog', 'Cat'],
relationshipTypes: ['Person'],
functions: testData.mockSchema.functions,
procedures: testData.mockSchema.procedures,
},
}),
).toEqual([
{
message:
"Procedure apoc.text.TOUpperCase is not present in the database. Make sure you didn't misspell it or that it is available when you run this statement in your application",
offsets: {
end: 26,
start: 5,
},
range: {
end: {
character: 26,
line: 0,
},
start: {
character: 5,
line: 0,
},
},
severity: 1,
},
]);
});
test('Syntax validation errors on missing function when database can be contacted', () => {

@@ -592,2 +747,207 @@ const query = `RETURN dontpanic("marvin")`;

});
test('Deprecations and removals for functions are based on the cypher version', () => {
const dbSchema = {
functions: {
'CYPHER 5': {
'apoc.create.uuid': {
name: 'apoc.create.uuid',
category: '',
description: 'Returns a UUID.',
signature: 'apoc.create.uuid(config :: MAP) :: STRING',
isBuiltIn: false,
argumentDescription: [
{
isDeprecated: true,
description: '',
name: 'config',
type: 'MAP',
},
],
returnDescription: 'STRING',
aggregating: false,
isDeprecated: false,
deprecatedBy: 'Neo4j randomUUID() function',
},
},
'CYPHER 25': {
'apoc.create.uuid': {
name: 'apoc.create.uuid',
category: '',
description: 'Returns a UUID.',
signature: 'apoc.create.uuid() :: STRING',
isBuiltIn: false,
argumentDescription: [],
returnDescription: 'STRING',
aggregating: false,
isDeprecated: false,
deprecatedBy: 'Neo4j randomUUID() function',
},
},
},
};
expect(
getDiagnosticsForQuery({
query: 'CYPHER 5 RETURN apoc.create.uuid()',
dbSchema: dbSchema,
}),
).toEqual([
{
message: `Function call does not provide the required number of arguments: expected 1 got 0.
Function apoc.create.uuid has signature: apoc.create.uuid(config :: MAP) :: STRING
meaning that it expects 1 argument of type MAP`,
offsets: {
end: 34,
start: 16,
},
range: {
end: {
character: 34,
line: 0,
},
start: {
character: 16,
line: 0,
},
},
severity: 1,
},
{
message:
"The function has a deprecated field. ('config' used by 'apoc.create.uuid' is deprecated.)",
offsets: {
end: 34,
start: 16,
},
range: {
end: {
character: 34,
line: 0,
},
start: {
character: 16,
line: 0,
},
},
severity: 2,
},
]);
expect(
getDiagnosticsForQuery({
query: 'CYPHER 25 RETURN apoc.create.uuid()',
dbSchema: dbSchema,
}),
).toEqual([]);
});
test('Errors and notifications for functions are different based on the cypher version', () => {
expect(
getDiagnosticsForQuery({
query: `
CYPHER 5 call apoc.cypher.runTimeboxed("match (n:Node), (m:Node)
WHERE n <> m
match path = shortestpath((n)-[:CONNECTED_TO*]-(m))
RETURN n, m, length(path) AS path", {}, 100, {})
YIELD value
RETURN value.n.uuid, value.m.uuid, value.path;`,
dbSchema: testData.mockSchema,
}),
).toEqual([
{
message: `Procedure call provides too many arguments: got 4 expected no more than 3.
Procedure apoc.cypher.runTimeboxed has signature: apoc.cypher.runTimeboxed(statement :: STRING, params :: MAP, timeout :: INTEGER) :: value :: MAP
meaning that it expects at least 3 arguments of types STRING, MAP, INTEGER
`,
offsets: {
end: 297,
start: 20,
},
range: {
end: {
character: 74,
line: 6,
},
start: {
character: 19,
line: 1,
},
},
severity: 1,
},
]);
expect(
getDiagnosticsForQuery({
query: `
CYPHER 25 call apoc.cypher.runTimeboxed("match (n:Node), (m:Node)
WHERE n <> m
match path = shortestpath((n)-[:CONNECTED_TO*]-(m))
RETURN n, m, length(path) AS path", {}, 100, {})
YIELD value
RETURN value.n.uuid, value.m.uuid, value.path;`,
dbSchema: testData.mockSchema,
}),
).toEqual([]);
});
test('Warnings for procedures misused as functions are different depending on cypher the version', () => {
// Procedure misused as function, but deprecated in Cypher5, so removed in Cypher25
expect(
getDiagnosticsForQuery({
query: 'CYPHER 5 RETURN apoc.create.uuids(5)',
dbSchema: testData.mockSchema,
}),
).toEqual([
{
message:
'apoc.create.uuids is a procedure, not a function. Did you mean to call the procedure apoc.create.uuids inside a CALL clause?',
offsets: {
end: 33,
start: 16,
},
range: {
end: {
character: 33,
line: 0,
},
start: {
character: 16,
line: 0,
},
},
severity: 1,
},
]);
expect(
getDiagnosticsForQuery({
query: 'CYPHER 25 RETURN apoc.create.uuids(5)',
dbSchema: testData.mockSchema,
}),
).toEqual([
{
message:
"Function apoc.create.uuids is not present in the database. Make sure you didn't misspell it or that it is available when you run this statement in your application",
offsets: {
end: 34,
start: 17,
},
range: {
end: {
character: 34,
line: 0,
},
start: {
character: 17,
line: 0,
},
},
severity: 1,
},
]);
});
});

@@ -1,2 +0,2 @@

import { DiagnosticTag } from 'vscode-languageserver-types';
import { _internalFeatureFlags } from '../../featureFlags';
import { testData } from '../testData';

@@ -6,2 +6,13 @@ import { getDiagnosticsForQuery } from './helpers';

describe('Procedures semantic validation spec', () => {
let isCypher25: boolean;
beforeAll(() => {
_internalFeatureFlags.cypher25 = true;
});
afterAll(() => {
isCypher25 = _internalFeatureFlags.cypher25;
_internalFeatureFlags.cypher25 = isCypher25;
});
test('Syntax validation warns on deprecated procedure when database can be contacted', () => {

@@ -18,27 +29,120 @@ const query = `CALL db.create.setVectorProperty()`;

}),
).toEqual(
expect.arrayContaining([
{
tags: [DiagnosticTag.Deprecated],
offsets: {
end: 32,
start: 5,
).toEqual([
{
message: `Procedure call does not provide the required number of arguments: got 0 expected at least 3 (total: 3, 0 of which have default values).
Procedure db.create.setVectorProperty has signature: db.create.setVectorProperty(node :: NODE, key :: STRING, vector :: ANY) :: node :: NODE
meaning that it expects at least 3 arguments of types NODE, STRING, ANY
`,
offsets: {
end: 34,
start: 0,
},
range: {
end: {
character: 34,
line: 0,
},
message: 'Procedure db.create.setVectorProperty is deprecated.',
range: {
end: {
character: 32,
line: 0,
},
start: {
character: 5,
line: 0,
},
start: {
character: 0,
line: 0,
},
severity: 2,
},
]),
);
severity: 1,
},
{
message:
'Procedure db.create.setVectorProperty is deprecated. Alternative: db.create.setNodeVectorProperty',
offsets: {
end: 32,
start: 5,
},
range: {
end: {
character: 32,
line: 0,
},
start: {
character: 5,
line: 0,
},
},
severity: 2,
tags: [2],
},
]);
});
test('Syntax validation error on procedure used as function returns helpful message', () => {
const query = `RETURN apoc.create.uuids(50)`;
expect(
getDiagnosticsForQuery({
query,
dbSchema: {
labels: ['Dog', 'Cat'],
relationshipTypes: ['Person'],
functions: testData.mockSchema.functions,
procedures: testData.mockSchema.procedures,
},
}),
).toEqual([
{
message:
'apoc.create.uuids is a procedure, not a function. Did you mean to call the procedure apoc.create.uuids inside a CALL clause?',
offsets: {
end: 24,
start: 7,
},
range: {
end: {
character: 24,
line: 0,
},
start: {
character: 7,
line: 0,
},
},
severity: 1,
},
]);
});
test('Using improved message for procedure used as function is case sensitive', () => {
const query = `RETURN apoc.cReAtE.uuids(50)`;
expect(
getDiagnosticsForQuery({
query,
dbSchema: {
labels: ['Dog', 'Cat'],
relationshipTypes: ['Person'],
functions: testData.mockSchema.functions,
procedures: testData.mockSchema.procedures,
},
}),
).toEqual([
{
message:
"Function apoc.cReAtE.uuids is not present in the database. Make sure you didn't misspell it or that it is available when you run this statement in your application",
offsets: {
end: 24,
start: 7,
},
range: {
end: {
character: 24,
line: 0,
},
start: {
character: 7,
line: 0,
},
},
severity: 1,
},
]);
});
test('Syntax validation warns on missing procedures when database can be contacted', () => {

@@ -89,21 +193,23 @@ const query = `CALL dontpanic("marvin")`;

procedures: {
mockProcedure: {
name: 'mockProcedure',
description:
'Returns the current change identifier that can be used to stream changes from.',
mode: 'READ',
worksOnSystem: false,
argumentDescription: [],
signature: 'mockProcedure() :: (id :: STRING)',
returnDescription: [
{
isDeprecated: false,
description: 'id :: STRING',
name: 'id',
type: 'STRING',
'CYPHER 5': {
mockProcedure: {
name: 'mockProcedure',
description:
'Returns the current change identifier that can be used to stream changes from.',
mode: 'READ',
worksOnSystem: false,
argumentDescription: [],
signature: 'mockProcedure() :: (id :: STRING)',
returnDescription: [
{
isDeprecated: false,
description: 'id :: STRING',
name: 'id',
type: 'STRING',
},
],
admin: false,
option: {
deprecated: false,
},
],
admin: false,
option: {
deprecated: false,
},

@@ -416,21 +522,23 @@ },

procedures: {
mockProcedure: {
name: 'mockProcedure',
description:
'Returns the current change identifier that can be used to stream changes from.',
mode: 'READ',
worksOnSystem: false,
argumentDescription: [],
signature: 'mockProcedure() :: (id :: STRING)',
returnDescription: [
{
isDeprecated: false,
description: 'id :: STRING',
name: 'id',
type: 'STRING',
'CYPHER 5': {
mockProcedure: {
name: 'mockProcedure',
description:
'Returns the current change identifier that can be used to stream changes from.',
mode: 'READ',
worksOnSystem: false,
argumentDescription: [],
signature: 'mockProcedure() :: (id :: STRING)',
returnDescription: [
{
isDeprecated: false,
description: 'id :: STRING',
name: 'id',
type: 'STRING',
},
],
admin: false,
option: {
deprecated: false,
},
],
admin: false,
option: {
deprecated: false,
},

@@ -456,21 +564,23 @@ },

procedures: {
mockProcedure: {
name: 'mockProcedure',
description:
'Returns the current change identifier that can be used to stream changes from.',
mode: 'READ',
worksOnSystem: false,
argumentDescription: [],
signature: 'mockProcedure() :: (id :: STRING)',
returnDescription: [
{
isDeprecated: false,
description: 'id :: STRING',
name: 'id',
type: 'STRING',
'CYPHER 5': {
mockProcedure: {
name: 'mockProcedure',
description:
'Returns the current change identifier that can be used to stream changes from.',
mode: 'READ',
worksOnSystem: false,
argumentDescription: [],
signature: 'mockProcedure() :: (id :: STRING)',
returnDescription: [
{
isDeprecated: false,
description: 'id :: STRING',
name: 'id',
type: 'STRING',
},
],
admin: false,
option: {
deprecated: false,
},
],
admin: false,
option: {
deprecated: false,
},

@@ -749,38 +859,40 @@ },

procedures: {
'apoc.meta.graphSample': {
name: 'apoc.meta.graphSample',
description:
'Examines the full graph and returns a meta-graph.\nUnlike `apoc.meta.graph`, this procedure does not filter away non-existing paths.',
mode: 'DEFAULT',
worksOnSystem: false,
argumentDescription: [
{
isDeprecated: false,
default: 'DefaultParameterValue{value={}, type=MAP}',
description: 'config = {} :: MAP',
name: 'config',
type: 'MAP',
},
],
'CYPHER 5': {
'apoc.meta.graphSample': {
name: 'apoc.meta.graphSample',
description:
'Examines the full graph and returns a meta-graph.\nUnlike `apoc.meta.graph`, this procedure does not filter away non-existing paths.',
mode: 'DEFAULT',
worksOnSystem: false,
argumentDescription: [
{
isDeprecated: false,
default: 'DefaultParameterValue{value={}, type=MAP}',
description: 'config = {} :: MAP',
name: 'config',
type: 'MAP',
},
],
signature:
'apoc.meta.graphSample(config = {} :: MAP) :: (nodes :: LIST<NODE>, relationships :: LIST<RELATIONSHIP>)',
returnDescription: [
{
isDeprecated: true,
description: 'nodes :: LIST<NODE>',
name: 'nodes',
type: 'LIST<NODE>',
signature:
'apoc.meta.graphSample(config = {} :: MAP) :: (nodes :: LIST<NODE>, relationships :: LIST<RELATIONSHIP>)',
returnDescription: [
{
isDeprecated: true,
description: 'nodes :: LIST<NODE>',
name: 'nodes',
type: 'LIST<NODE>',
},
{
isDeprecated: false,
description: 'relationships :: LIST<RELATIONSHIP>',
name: 'relationships',
type: 'LIST<RELATIONSHIP>',
},
],
admin: false,
option: {
deprecated: false,
},
{
isDeprecated: false,
description: 'relationships :: LIST<RELATIONSHIP>',
name: 'relationships',
type: 'LIST<RELATIONSHIP>',
},
],
admin: false,
option: {
deprecated: false,
},

@@ -813,2 +925,167 @@ },

});
test('Deprecations and removals for procedures are based on the cypher version', () => {
expect(
getDiagnosticsForQuery({
query: 'CYPHER 5 CALL apoc.export.arrow.stream.all()',
dbSchema: testData.mockSchema,
}),
).toEqual([
{
message:
'Procedure apoc.export.arrow.stream.all is deprecated. Alternative: This procedure is being moved to APOC Extended.',
offsets: {
end: 42,
start: 14,
},
range: {
end: {
character: 42,
line: 0,
},
start: {
character: 14,
line: 0,
},
},
severity: 2,
tags: [2],
},
]);
expect(
getDiagnosticsForQuery({
query: 'CYPHER 25 CALL apoc.export.arrow.stream.all()',
dbSchema: testData.mockSchema,
}),
).toEqual([
{
message:
"Procedure apoc.export.arrow.stream.all is not present in the database. Make sure you didn't misspell it or that it is available when you run this statement in your application",
offsets: {
end: 43,
start: 15,
},
range: {
end: {
character: 43,
line: 0,
},
start: {
character: 15,
line: 0,
},
},
severity: 1,
},
]);
});
test('Errors and notifications for procedures are different based on the cypher version', () => {
expect(
getDiagnosticsForQuery({
query: `
CYPHER 5 call apoc.cypher.runTimeboxed("match (n:Node), (m:Node)
WHERE n <> m
match path = shortestpath((n)-[:CONNECTED_TO*]-(m))
RETURN n, m, length(path) AS path", {}, 100, {})
YIELD value
RETURN value.n.uuid, value.m.uuid, value.path;`,
dbSchema: testData.mockSchema,
}),
).toEqual([
{
message: `Procedure call provides too many arguments: got 4 expected no more than 3.
Procedure apoc.cypher.runTimeboxed has signature: apoc.cypher.runTimeboxed(statement :: STRING, params :: MAP, timeout :: INTEGER) :: value :: MAP
meaning that it expects at least 3 arguments of types STRING, MAP, INTEGER
`,
offsets: {
end: 297,
start: 20,
},
range: {
end: {
character: 74,
line: 6,
},
start: {
character: 19,
line: 1,
},
},
severity: 1,
},
]);
expect(
getDiagnosticsForQuery({
query: `
CYPHER 25 call apoc.cypher.runTimeboxed("match (n:Node), (m:Node)
WHERE n <> m
match path = shortestpath((n)-[:CONNECTED_TO*]-(m))
RETURN n, m, length(path) AS path", {}, 100, {})
YIELD value
RETURN value.n.uuid, value.m.uuid, value.path;`,
dbSchema: testData.mockSchema,
}),
).toEqual([]);
});
test('Warnings for functions misused as procedures are different depending on cypher the version', () => {
// Function misused as procedure, but deprecated in Cypher5, so removed in Cypher25
expect(
getDiagnosticsForQuery({
query: 'CYPHER 5 CALL apoc.create.uuid()',
dbSchema: testData.mockSchema,
}),
).toEqual([
{
message:
'apoc.create.uuid is a function, not a procedure. Did you mean to use the function apoc.create.uuid with a RETURN instead of a CALL clause?',
offsets: {
end: 30,
start: 14,
},
range: {
end: {
character: 30,
line: 0,
},
start: {
character: 14,
line: 0,
},
},
severity: 1,
},
]);
expect(
getDiagnosticsForQuery({
query: 'CYPHER 25 CALL apoc.create.uuid()',
dbSchema: testData.mockSchema,
}),
).toEqual([
{
message:
"Procedure apoc.create.uuid is not present in the database. Make sure you didn't misspell it or that it is available when you run this statement in your application",
offsets: {
end: 31,
start: 15,
},
range: {
end: {
character: 31,
line: 0,
},
start: {
character: 15,
line: 0,
},
},
severity: 1,
},
]);
});
});

@@ -6,2 +6,224 @@ import { _internalFeatureFlags } from '../../featureFlags';

describe('Semantic validation spec', () => {
let isCypher25: boolean;
beforeAll(() => {
isCypher25 = _internalFeatureFlags.cypher25;
_internalFeatureFlags.cypher25 = true;
});
afterAll(() => {
_internalFeatureFlags.cypher25 = isCypher25;
});
test('Semantic analysis is dependant on cypher version', () => {
const query1 = 'CYPHER 5 MATCH (n)-[r]->(m) SET r += m';
const diagnostics1 = getDiagnosticsForQuery({ query: query1 });
const query2 = 'CYPHER 25 MATCH (n)-[r]->(m) SET r += m';
const diagnostics2 = getDiagnosticsForQuery({ query: query2 });
expect(diagnostics1[0].message).not.toEqual(diagnostics2[0].message);
expect(diagnostics1).toEqual([
{
message:
'The use of nodes or relationships for setting properties is deprecated and will be removed in a future version. Please use properties() instead.',
offsets: {
end: 39,
start: 38,
},
range: {
end: {
character: 39,
line: 0,
},
start: {
character: 38,
line: 0,
},
},
severity: 2,
},
]);
expect(diagnostics2).toEqual([
{
message: 'Type mismatch: expected Map but was Node',
offsets: {
end: 39,
start: 38,
},
range: {
end: {
character: 39,
line: 0,
},
start: {
character: 38,
line: 0,
},
},
severity: 1,
},
]);
});
test('Semantic analysis defaults to cypher 5 when no default version is given, and no version is given in query', () => {
const query1 = 'CYPHER 5 MATCH (n)-[r]->(m) SET r += m';
const diagnostics1 = getDiagnosticsForQuery({ query: query1 });
const query2 = 'MATCH (n)-[r]->(m) SET r += m';
const diagnostics2 = getDiagnosticsForQuery({ query: query2 });
expect(diagnostics1[0].message).toEqual(diagnostics2[0].message);
expect(diagnostics1).toEqual([
{
message:
'The use of nodes or relationships for setting properties is deprecated and will be removed in a future version. Please use properties() instead.',
offsets: {
end: 39,
start: 38,
},
range: {
end: {
character: 39,
line: 0,
},
start: {
character: 38,
line: 0,
},
},
severity: 2,
},
]);
expect(diagnostics2).toEqual([
{
message:
'The use of nodes or relationships for setting properties is deprecated and will be removed in a future version. Please use properties() instead.',
offsets: {
end: 29,
start: 28,
},
range: {
end: {
character: 29,
line: 0,
},
start: {
character: 28,
line: 0,
},
},
severity: 2,
},
]);
});
//TODO: Maybe this should actually yield a warning - to be fixed in follow-up, ignoring for now
test('Semantic analysis defaults to cypher 5 when faulty version is given', () => {
const query1 = 'CYPHER 5 MATCH (n)-[r]->(m) SET r += m';
const diagnostics1 = getDiagnosticsForQuery({ query: query1 });
const query2 = 'CYPHER 800 MATCH (n)-[r]->(m) SET r += m';
const diagnostics2 = getDiagnosticsForQuery({ query: query2 });
expect(diagnostics1[0].message).toEqual(diagnostics2[0].message);
});
test('Semantic analysis uses default language if no language is defined in query', () => {
const query1 = 'MATCH (n)-[r]->(m) SET r += m';
const diagnostics1 = getDiagnosticsForQuery({
query: query1,
dbSchema: { defaultLanguage: 'CYPHER 25' },
});
const query2 = 'CYPHER 25 MATCH (n)-[r]->(m) SET r += m';
const diagnostics2 = getDiagnosticsForQuery({ query: query2 });
expect(diagnostics1[0].message).toEqual(diagnostics2[0].message);
expect(diagnostics1).toEqual([
{
message: 'Type mismatch: expected Map but was Node',
offsets: {
end: 29,
start: 28,
},
range: {
end: {
character: 29,
line: 0,
},
start: {
character: 28,
line: 0,
},
},
severity: 1,
},
]);
expect(diagnostics2).toEqual([
{
message: 'Type mismatch: expected Map but was Node',
offsets: {
end: 39,
start: 38,
},
range: {
end: {
character: 39,
line: 0,
},
start: {
character: 38,
line: 0,
},
},
severity: 1,
},
]);
});
test('In-query version takes priority for semantic analysis even if defaultLanguage is defined', () => {
const query1 = 'CYPHER 5 MATCH (n)-[r]->(m) SET r += m';
const diagnostics1 = getDiagnosticsForQuery({
query: query1,
dbSchema: { defaultLanguage: 'CYPHER 25' },
});
const query2 = 'CYPHER 25 MATCH (n)-[r]->(m) SET r += m';
const diagnostics2 = getDiagnosticsForQuery({ query: query2 });
expect(diagnostics1[0].message).not.toEqual(diagnostics2[0].message);
expect(diagnostics1).toEqual([
{
message:
'The use of nodes or relationships for setting properties is deprecated and will be removed in a future version. Please use properties() instead.',
offsets: {
end: 38,
start: 37,
},
range: {
end: {
character: 38,
line: 0,
},
start: {
character: 37,
line: 0,
},
},
severity: 2,
},
]);
expect(diagnostics2).toEqual([
{
message: 'Type mismatch: expected Map but was Node',
offsets: {
end: 39,
start: 38,
},
range: {
end: {
character: 39,
line: 0,
},
start: {
character: 38,
line: 0,
},
},
severity: 1,
},
]);
});
test('Does not trigger semantic errors when there are syntactic errors', () => {

@@ -12,3 +234,4 @@ const query = 'METCH (n) RETURN m';

{
message: 'Unrecognized keyword. Did you mean MATCH?',
message:
"Invalid input 'METCH': expected 'FOREACH', 'ALTER', 'ORDER BY', 'CALL', 'USING PERIODIC COMMIT', 'CREATE', 'LOAD CSV', 'START DATABASE', 'STOP DATABASE', 'DEALLOCATE', 'DELETE', 'DENY', 'DETACH', 'DROP', 'DRYRUN', 'FINISH', 'GRANT', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REALLOCATE', 'REMOVE', 'RENAME', 'RETURN', 'REVOKE', 'ENABLE SERVER', 'SET', 'SHOW', 'SKIP', 'TERMINATE', 'UNWIND', 'USE' or 'WITH'",
offsets: {

@@ -15,0 +238,0 @@ end: 5,

@@ -13,3 +13,4 @@ import { getDiagnosticsForQuery } from './helpers';

},
message: 'Unrecognized keyword. Did you mean MATCH?',
message:
"Invalid input 'METCH': expected 'FOREACH', 'ALTER', 'ORDER BY', 'CALL', 'USING PERIODIC COMMIT', 'CREATE', 'LOAD CSV', 'START DATABASE', 'STOP DATABASE', 'DEALLOCATE', 'DELETE', 'DENY', 'DETACH', 'DROP', 'DRYRUN', 'FINISH', 'GRANT', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REALLOCATE', 'REMOVE', 'RENAME', 'RETURN', 'REVOKE', 'ENABLE SERVER', 'SET', 'SHOW', 'SKIP', 'TERMINATE', 'UNWIND', 'USE' or 'WITH'",
range: {

@@ -35,2 +36,4 @@ end: {

{
message:
"Invalid input 'CAT': expected 'FOREACH', 'ALTER', 'ORDER BY', 'CALL', 'USING PERIODIC COMMIT', 'CREATE', 'LOAD CSV', 'START DATABASE', 'STOP DATABASE', 'DEALLOCATE', 'DELETE', 'DENY', 'DETACH', 'DROP', 'DRYRUN', 'FINISH', 'GRANT', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REALLOCATE', 'REMOVE', 'RENAME', 'RETURN', 'REVOKE', 'ENABLE SERVER', 'SET', 'SHOW', 'SKIP', 'TERMINATE', 'UNWIND', 'USE' or 'WITH'",
offsets: {

@@ -40,4 +43,2 @@ end: 3,

},
message:
'Expected any of ALTER, CALL, CREATE, CYPHER, DEALLOCATE, DELETE, DENY, DETACH, DROP, DRYRUN, ENABLE, EXPLAIN, FINISH, FOREACH, GRANT, INSERT, LIMIT, LOAD, MATCH, MERGE, NODETACH, OFFSET, OPTIONAL, ORDER, PROFILE, REALLOCATE, REMOVE, RENAME, RETURN, REVOKE, SET, SHOW, SKIP, START, STOP, TERMINATE, UNWIND, USE, USING or WITH',
range: {

@@ -63,2 +64,4 @@ end: {

{
message:
"Invalid input 'WERE': expected a graph pattern, 'FOREACH', ',', 'ORDER BY', 'CALL', 'CREATE', 'LOAD CSV', 'DELETE', 'DETACH', 'FINISH', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REMOVE', 'RETURN', 'SET', 'SKIP', 'UNION', 'UNWIND', 'USE', 'USING', 'WHERE', 'WITH' or <EOF>",
offsets: {

@@ -68,3 +71,2 @@ end: 21,

},
message: 'Unexpected token. Did you mean WHERE?',
range: {

@@ -90,2 +92,4 @@ end: {

{
message:
"Invalid input 'WERE': expected a graph pattern, 'FOREACH', ',', 'ORDER BY', 'CALL', 'CREATE', 'LOAD CSV', 'DELETE', 'DETACH', 'FINISH', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REMOVE', 'RETURN', 'SET', 'SKIP', 'UNION', 'UNWIND', 'USE', 'USING', 'WHERE', 'WITH' or <EOF>",
offsets: {

@@ -95,3 +99,2 @@ end: 21,

},
message: 'Unexpected token. Did you mean WHERE?',
range: {

@@ -121,2 +124,4 @@ end: {

{
message:
"Invalid input 'n': expected an expression, 'FOREACH', 'ORDER BY', 'CALL', 'CREATE', 'LOAD CSV', 'DELETE', 'DETACH', 'FINISH', 'INSERT', 'LIMIT', 'MATCH', 'MERGE', 'NODETACH', 'OFFSET', 'OPTIONAL', 'REMOVE', 'RETURN', 'SET', 'SKIP', 'UNION', 'UNWIND', 'USE', 'WITH' or '}'",
offsets: {

@@ -126,4 +131,2 @@ end: 114,

},
message:
"Expected any of '}', AND, CALL, CREATE, DELETE, DETACH, FINISH, FOREACH, INSERT, LIMIT, LOAD, MATCH, MERGE, NODETACH, OFFSET, OPTIONAL, OR, ORDER, REMOVE, RETURN, SET, SKIP, UNION, UNWIND, USE, WITH, XOR or an expression",
range: {

@@ -144,27 +147,2 @@ end: {

test('Misspelt keyword in the middle of the statement', () => {
const query = "MATCH (n:Person) WERE n.name = 'foo'";
expect(getDiagnosticsForQuery({ query })).toEqual([
{
offsets: {
end: 21,
start: 17,
},
message: 'Unexpected token. Did you mean WHERE?',
range: {
end: {
character: 21,
line: 0,
},
start: {
character: 17,
line: 0,
},
},
severity: 1,
},
]);
});
test('Syntax validation warns on missing label when database can be contacted', () => {

@@ -472,3 +450,4 @@ const query = `MATCH (n: Person) RETURN n`;

{
message: 'Expected a node label / rel type',
message:
"Invalid input ')': expected a node label/relationship type name, '$', '%' or '('",
offsets: {

@@ -566,2 +545,4 @@ end: 10,

{
message:
"Invalid input '': expected 'ALIAS', 'ALIASES', 'ALL', 'BTREE', 'CONSTRAINT', 'CONSTRAINTS', 'DATABASE', 'DEFAULT DATABASE', 'HOME DATABASE', 'DATABASES', 'EXIST', 'EXISTENCE', 'EXISTS', 'FULLTEXT', 'FUNCTION', 'FUNCTIONS', 'BUILT IN', 'INDEX', 'INDEXES', 'KEY', 'LOOKUP', 'NODE', 'POINT', 'POPULATED', 'PRIVILEGE', 'PRIVILEGES', 'PROCEDURE', 'PROCEDURES', 'PROPERTY', 'RANGE', 'REL', 'RELATIONSHIP', 'ROLE', 'ROLES', 'SERVER', 'SERVERS', 'SETTING', 'SETTINGS', 'SUPPORTED', 'TEXT', 'TRANSACTION', 'TRANSACTIONS', 'UNIQUE', 'UNIQUENESS', 'USER', 'CURRENT USER', 'USERS' or 'VECTOR'",
offsets: {

@@ -571,4 +552,2 @@ end: 4,

},
message:
'Expected any of ALIAS, ALIASES, ALL, BTREE, BUILT, CONSTRAINT, CONSTRAINTS, CURRENT, DATABASE, DATABASES, DEFAULT, EXIST, EXISTENCE, EXISTS, FULLTEXT, FUNCTION, FUNCTIONS, HOME, INDEX, INDEXES, KEY, LOOKUP, NODE, POINT, POPULATED, PRIVILEGE, PRIVILEGES, PROCEDURE, PROCEDURES, PROPERTY, RANGE, REL, RELATIONSHIP, ROLE, ROLES, SERVER, SERVERS, SETTING, SETTINGS, SUPPORTED, TEXT, TRANSACTION, TRANSACTIONS, UNIQUE, UNIQUENESS, USER, USERS or VECTOR',
range: {

@@ -608,3 +587,4 @@ end: {

{
message: 'Unfinished string literal',
message:
'Failed to parse string literal. The query must contain an even number of non-escaped quotes.',
offsets: {

@@ -629,3 +609,3 @@ end: 17,

test('Syntax validation errors on multiline unfinished string', () => {
test('Syntax validation errors on multiline single-quoted unfinished string', () => {
const query = `RETURN 'something

@@ -641,3 +621,4 @@ foo

{
message: 'Unfinished string literal',
message:
'Failed to parse string literal. The query must contain an even number of non-escaped quotes.',
offsets: {

@@ -662,7 +643,6 @@ end: 37,

test('Syntax validation errors on multiline unfinished property keys', () => {
const query = `RETURN {\`something
foo
bar: "hello"}`;
test('Syntax validation errors on multiline double-quoted unfinished string', () => {
const query = `RETURN "something
foo
bar`;

@@ -675,14 +655,15 @@ expect(

{
message: 'Unfinished escaped identifier',
message:
'Failed to parse string literal. The query must contain an even number of non-escaped quotes.',
offsets: {
end: 49,
start: 8,
end: 37,
start: 7,
},
range: {
end: {
character: 17,
line: 3,
character: 9,
line: 2,
},
start: {
character: 8,
character: 7,
line: 0,

@@ -696,2 +677,37 @@ },

test.fails(
'Syntax validation errors on multiline unfinished property keys',
() => {
const query = `RETURN {\`something
foo
bar: "hello"}`;
expect(
getDiagnosticsForQuery({
query,
}),
).toEqual([
{
message: "Invalid input '`': expected an identifier or '}'",
offsets: {
end: 49,
start: 8,
},
range: {
end: {
character: 17,
line: 3,
},
start: {
character: 8,
line: 0,
},
},
severity: 1,
},
]);
},
);
test('Syntax validation errors on unfinished multiline comment', () => {

@@ -708,3 +724,4 @@ const query = `/* something

{
message: 'Unfinished comment',
message:
'Failed to parse comment. A comment starting on `/*` must have a closing `*/`.',
offsets: {

@@ -738,3 +755,3 @@ end: 34,

{
message: "Expected any of '*', DISTINCT or an expression",
message: "Invalid input '}': expected an expression, '*' or 'DISTINCT'",
offsets: {

@@ -756,24 +773,8 @@ end: 25,

},
{
message: "Expected any of '*', DISTINCT or an expression",
offsets: {
end: 48,
start: 48,
},
range: {
end: {
character: 48,
line: 0,
},
start: {
character: 48,
line: 0,
},
},
severity: 1,
},
]);
});
test('Syntax validation errors on an expected procedure name', () => {
// TODO FIX ME
// Problem here is we were getting a better error before
test.fails('Syntax validation errors on an expected procedure name', () => {
const query = `CALL ,foo`;

@@ -816,3 +817,3 @@

{
message: "Expected '}' or an identifier",
message: "Invalid input '[': expected an identifier or '}'",
offsets: {

@@ -846,3 +847,3 @@ end: 9,

{
message: "Expected any of '=', ')', '{', ':', IS, WHERE or a parameter",
message: `Invalid input '"foo"': expected a graph pattern, a parameter, ')', ':', 'IS', 'WHERE' or '{'`,
offsets: {

@@ -876,3 +877,3 @@ end: 14,

{
message: 'Expected an identifier',
message: "Invalid input ''bar'': expected an identifier",
offsets: {

@@ -907,3 +908,4 @@ end: 23,

{
message: 'Expected any of CHANGE, a string or a parameter',
message:
"Invalid input 'foo': expected a parameter, a string or 'CHANGE'",
offsets: {

@@ -937,3 +939,3 @@ end: 39,

{
message: "Expected any of '=', a database name or a parameter",
message: `Invalid input '"something"': expected a database name, a graph pattern or a parameter`,
offsets: {

@@ -967,3 +969,3 @@ end: 27,

{
message: "Expected '}' or an unsigned integer",
message: `Invalid input '"foo"': expected '}' or an integer value`,
offsets: {

@@ -997,3 +999,4 @@ end: 40,

{
message: 'Expected a node label / rel type',
message:
"Invalid input ''Person'': expected a node label/relationship type name, '$', '%' or '('",
offsets: {

@@ -1027,3 +1030,3 @@ end: 18,

{
message: 'Expected a node label / rel type',
message: `Invalid input ''Person'': expected an identifier, '$', '%' or '('`,
offsets: {

@@ -1060,3 +1063,3 @@ end: 20,

end: 1,
start: 1,
start: 0,
},

@@ -1069,3 +1072,3 @@ range: {

start: {
character: 1,
character: 0,
line: 0,

@@ -1094,2 +1097,30 @@ },

);
test('Existing relationship types are not incorrectly reported inside node predicates', () => {
const query = `MATCH ()-[]->*(n WHERE COUNT{ (n)-[:R]->()} = 0) RETURN n`;
expect(
getDiagnosticsForQuery({
query,
dbSchema: {
labels: [],
relationshipTypes: ['R'],
},
}),
).toEqual([]);
});
test('Existing node labels are not incorrectly reported inside relationship predicates', () => {
const query = `MATCH ()-[r WHERE COUNT{ (n:A)-[]->()} = 0]->() RETURN r`;
expect(
getDiagnosticsForQuery({
query,
dbSchema: {
labels: ['A'],
relationshipTypes: [],
},
}),
).toEqual([]);
});
});

@@ -10,2 +10,5 @@ import { CompletionItem as VSCodeCompletionItem } from 'vscode-languageserver-types';

export const cypherVersions = ['CYPHER 25', 'CYPHER 5'];
export type CypherVersion = (typeof cypherVersions)[number];
// we could parse this string for better types in the future

@@ -29,2 +32,3 @@ export type Neo4jStringType = string;

option: { deprecated: boolean } & Record<string, unknown>;
deprecatedBy?: string;
};

@@ -42,2 +46,3 @@

isDeprecated: boolean;
deprecatedBy?: string;
};

@@ -44,0 +49,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc