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
0
Versions
130
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-canary-f4641e2 to 2.0.0-canary-f5e5c79

dist/types/tests/syntaxValidation/functionsValidation.test.d.ts

34

CHANGELOG.md
# @neo4j-cypher/language-support
## 2.0.0-next.11
### Patch Changes
- bccf518: Fixes signature help bug with arguments that include a default value
## 2.0.0-next.10
### Patch Changes
- 8760c02: Adds backticking to labels, rel types and property names on auto-completions
## 2.0.0-next.9
### Patch Changes
- 2e72ac8: Adds errors for undeclared procedures / functions
## 2.0.0-next.8
### Patch Changes
- 05663bd: Fixes bug using non language keywords (EXPLAIN, PROFILE, etc) as symbolic names
## 2.0.0-next.7
### Patch Changes
- 3661e9d: Fixes database completions for CREATE ALIAS commands
- b76af58: Fixes bug in signature help of functions nested inside procedure calls
- 21699b7: Updates the semantic analysis module to use the antlr parser instead of the javaCC one
- 6afc0e3: Adds signature information on auto-completions
- 39b924d: Fixes bug in labels completion
## 2.0.0-next.6

@@ -4,0 +38,0 @@

3

dist/types/autocompletion/completionCoreCompletions.d.ts

@@ -0,4 +1,5 @@

import { Token } from 'antlr4';
import { DbSchema } from '../dbSchema';
import { ParsedStatement } from '../parserWrapper';
import { CompletionItem } from '../types';
export declare function completionCoreCompletion(parsingResult: ParsedStatement, dbSchema: DbSchema, manualTrigger?: boolean): CompletionItem[];
export declare function completionCoreCompletion(parsingResult: ParsedStatement, dbSchema: DbSchema, caretToken: Token, manualTrigger?: boolean): CompletionItem[];

@@ -6,309 +6,321 @@ import { ATN, CharStream, DFA, Lexer } from "antlr4";

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

@@ -315,0 +327,0 @@ static readonly channelNames: string[];

@@ -7,3 +7,3 @@ export type { ParserRuleContext } from 'antlr4';

export { CypherTokenType, lexerSymbols } from './lexerSymbols';
export { parse, parserWrapper } from './parserWrapper';
export { parse, parserWrapper, parseStatementsStrs } from './parserWrapper';
export { signatureHelp, toSignatureInformation } from './signatureHelp';

@@ -10,0 +10,0 @@ export { applySyntaxColouring, mapCypherToSemanticTokenIndex, syntaxColouringLegend, } from './syntaxColouring/syntaxColouring';

import type { ParserRuleContext, Token } from 'antlr4';
import { default as CypherParser, StatementOrCommandContext, StatementsOrCommandsContext } from './generated-parser/CypherCmdParser';
import { default as CypherParser, FunctionNameContext, ProcedureNameContext, StatementOrCommandContext, StatementsOrCommandsContext } from './generated-parser/CypherCmdParser';
import { SyntaxDiagnostic } from './syntaxValidation/syntaxValidationHelpers';

@@ -9,6 +9,8 @@ export interface ParsedStatement {

ctx: StatementsOrCommandsContext;
diagnostics: SyntaxDiagnostic[];
syntaxErrors: SyntaxDiagnostic[];
stopNode: ParserRuleContext;
collectedLabelOrRelTypes: LabelOrRelType[];
collectedVariables: string[];
collectedFunctions: ParsedFunction[];
collectedProcedures: ParsedProcedure[];
}

@@ -43,5 +45,18 @@ export interface ParsingResult {

};
export type ParsedFunction = {
name: string;
rawText: string;
line: number;
column: number;
offsets: {
start: number;
end: number;
};
};
export type ParsedProcedure = ParsedFunction;
export declare function createParsingScaffolding(query: string): ParsingScaffolding;
export declare function parseStatementsStrs(query: string): string[];
export declare function parse(query: string): StatementOrCommandContext[];
export declare function createParsingResult(query: string): ParsingResult;
export declare function getMethodName(ctx: ProcedureNameContext | FunctionNameContext): string;
type CypherCmd = {

@@ -77,3 +92,14 @@ type: 'cypher';

} | {
type: 'connect';
} | {
type: 'disconnect';
} | {
type: 'server';
operation: string;
} | {
type: 'welcome';
} | {
type: 'parse-error';
} | {
type: 'sysinfo';
};

@@ -80,0 +106,0 @@ export type ParsedCommand = ParsedCommandNoPosition & RuleTokens;

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

],
"version": "2.0.0-canary-f4641e2",
"version": "2.0.0-canary-f5e5c79",
"main": "./dist/cjs/index.cjs",

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

"dependencies": {
"antlr4": "*",
"antlr4": "4.13.2",
"antlr4-c3": "*",

@@ -60,9 +60,8 @@ "fastest-levenshtein": "^1.0.16",

"clean": "rm -rf {dist,src/generated-parser}",
"test": "jest"
"test": "vitest run"
},
"devDependencies": {
"@types/benchmark": "^2.1.5",
"@types/jest": "^29.5.5",
"benchmark": "^2.1.4"
}
}

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

const parsingResult = parserWrapper.parse(query);
/* We try to locate the latest statement by finding the latest available `;`
in the query and take from that point to the end of the query
/* We try to locate the statement where the caret is and the token of the caret

@@ -24,4 +23,2 @@ The reason for doing that is we need a way to "resynchronise" when the

If there was no ;, we don't want to reparse, so we return undefined
inside findLatestStatement
*/

@@ -31,3 +28,4 @@ const caret = findCaret(parsingResult, caretPosition);

const statement = caret.statement;
return completionCoreCompletion(statement, dbSchema, manual);
const caretToken = caret.token;
return completionCoreCompletion(statement, dbSchema, caretToken, manual);
}

@@ -34,0 +32,0 @@

@@ -12,6 +12,11 @@ import { Token } from 'antlr4';

import CypherParser, {
CallClauseContext,
Expression2Context,
} from '../generated-parser/CypherCmdParser';
import { findPreviousNonSpace, rulesDefiningVariables } from '../helpers';
import {
findParent,
findPreviousNonSpace,
rulesDefiningVariables,
} from '../helpers';
import {
CypherTokenType,

@@ -23,3 +28,3 @@ lexerKeywords,

import { ParsedStatement } from '../parserWrapper';
import { getMethodName, ParsedStatement } from '../parserWrapper';

@@ -31,14 +36,43 @@ import { _internalFeatureFlags } from '../featureFlags';

function backtickIfNeeded(e: string): string | undefined {
if (e == null || e == '') {
return undefined;
} else if (/[^\p{L}\p{N}_]/u.test(e) || /[^\p{L}_]/u.test(e[0])) {
return `\`${e}\``;
} else {
return undefined;
}
}
const labelCompletions = (dbSchema: DbSchema) =>
dbSchema.labels?.map((labelName) => ({
label: labelName,
kind: CompletionItemKind.TypeParameter,
})) ?? [];
dbSchema.labels?.map((labelName) => {
const result: CompletionItem = {
label: labelName,
kind: CompletionItemKind.TypeParameter,
insertText: backtickIfNeeded(labelName),
};
return result;
}) ?? [];
const reltypeCompletions = (dbSchema: DbSchema) =>
dbSchema.relationshipTypes?.map((relType) => ({
label: relType,
kind: CompletionItemKind.TypeParameter,
})) ?? [];
dbSchema.relationshipTypes?.map((relType) => {
const result: CompletionItem = {
label: relType,
kind: CompletionItemKind.TypeParameter,
insertText: backtickIfNeeded(relType),
};
return result;
}) ?? [];
const procedureReturnCompletions = (
procedureName: string,
dbSchema: DbSchema,
): CompletionItem[] => {
return (
dbSchema?.procedures?.[procedureName]?.returnDescription?.map((x) => {
return { label: x.name, kind: CompletionItemKind.Variable };
}) ?? []
);
};
const functionNameCompletions = (

@@ -263,6 +297,10 @@ candidateRule: CandidateRule,

const propertyKeyCompletions = (dbInfo: DbSchema): CompletionItem[] =>
dbInfo.propertyKeys?.map((propertyKey) => ({
label: propertyKey,
kind: CompletionItemKind.Property,
})) ?? [];
dbInfo.propertyKeys?.map((propertyKey) => {
const result: CompletionItem = {
label: propertyKey,
kind: CompletionItemKind.Property,
insertText: backtickIfNeeded(propertyKey),
};
return result;
}) ?? [];

@@ -360,2 +398,3 @@ enum ExpectedParameterType {

dbSchema: DbSchema,
caretToken: Token,
manualTrigger = false,

@@ -368,6 +407,7 @@ ): CompletionItem[] {

let caretIndex = caretToken.tokenIndex;
// Move the caret index to the end of the query
let caretIndex = tokens.length > 0 ? tokens.length - 1 : 0;
const eofIndex = tokens.length > 0 ? tokens.length - 1 : 0;
const eof = tokens[caretIndex];
const eof = tokens[eofIndex];

@@ -377,3 +417,3 @@ // When we have EOF with a different text in the token, it means the parser has failed to parse it.

// point of completion (e.g. an unclosed string)
if (eof.text !== '<EOF>') {
if (eof.type === CypherLexer.EOF && eof.text !== '<EOF>') {
return [];

@@ -407,2 +447,3 @@ }

CypherParser.RULE_commandNameExpression,
CypherParser.RULE_procedureResultItem,

@@ -415,2 +456,3 @@ // Either enable the helper rules for lexer clashes,

CypherParser.RULE_listCompletionRule,
CypherParser.RULE_serverCompletionRule,
]

@@ -443,2 +485,19 @@ : [CypherParser.RULE_consoleCommand]),

const [ruleNumber, candidateRule] = candidate;
if (ruleNumber === CypherParser.RULE_procedureResultItem) {
const callContext = findParent(
parsingResult.stopNode.parentCtx,
(x) => x instanceof CallClauseContext,
);
if (callContext instanceof CallClauseContext) {
const procedureNameCtx = callContext.procedureName();
const existingYieldItems = new Set(
callContext.procedureResultItem_list().map((a) => a.getText()),
);
const name = getMethodName(procedureNameCtx);
return procedureReturnCompletions(name, dbSchema).filter(
(a) => !existingYieldItems.has(a?.label),
);
}
}
if (ruleNumber === CypherParser.RULE_functionName) {

@@ -554,2 +613,6 @@ return functionNameCompletions(candidateRule, tokens, dbSchema);

if (ruleNumber === CypherParser.RULE_serverCompletionRule) {
return [{ label: 'server', kind: CompletionItemKind.Event }];
}
if (ruleNumber === CypherParser.RULE_leftArrow) {

@@ -631,20 +694,27 @@ return [

// parameters are valid values in all cases of symbolicAliasName
const baseSuggestions = parameterCompletions(
const parameterSuggestions = parameterCompletions(
dbSchema,
ExpectedParameterType.String,
);
const rulesCreatingNewAliasOrDb = [
CypherParser.RULE_createAlias,
const rulesCreatingNewDb = [
CypherParser.RULE_createDatabase,
CypherParser.RULE_createCompositeDatabase,
];
// avoid suggesting existing database names when creating a new alias or database
// avoid suggesting existing database names when creating a new database
if (
rulesCreatingNewAliasOrDb.some((rule) =>
candidateRule.ruleList.includes(rule),
)
rulesCreatingNewDb.some((rule) => candidateRule.ruleList.includes(rule))
) {
return baseSuggestions;
return parameterSuggestions;
}
// For `CREATE ALIAS aliasName FOR DATABASE databaseName`
// Should not suggest existing aliases for aliasName but should suggest existing databases for databaseName
// so we return base suggestions if we're at the `aliasName` rule
if (
candidateRule.ruleList.includes(CypherParser.RULE_createAlias) &&
candidateRule.ruleList.includes(CypherParser.RULE_aliasName)
) {
return parameterSuggestions;
}
const rulesThatOnlyAcceptAlias = [

@@ -661,3 +731,3 @@ CypherParser.RULE_dropAlias,

return [
...baseSuggestions,
...parameterSuggestions,
...(dbSchema?.aliasNames ?? []).map((aliasName) => ({

@@ -672,3 +742,3 @@ label: aliasName,

return [
...baseSuggestions,
...parameterSuggestions,
...(dbSchema.databaseNames ?? [])

@@ -689,3 +759,3 @@ .concat(dbSchema.aliasNames ?? [])

// parameters are valid values in all cases of symbolic name
const baseSuggestions = parameterCompletions(
const parameterSuggestions = parameterCompletions(
dbSchema,

@@ -715,3 +785,3 @@ inferExpectedParameterTypeFromContext(candidateRule),

) {
return baseSuggestions;
return parameterSuggestions;
}

@@ -728,3 +798,3 @@

const result = [
...baseSuggestions,
...parameterSuggestions,
...(dbSchema?.userNames ?? []).map((userName) => ({

@@ -747,3 +817,3 @@ label: userName,

return [
...baseSuggestions,
...parameterSuggestions,
...(dbSchema?.roleNames ?? []).map((roleName) => ({

@@ -750,0 +820,0 @@ label: roleName,

@@ -7,3 +7,3 @@ export type { ParserRuleContext } from 'antlr4';

export { CypherTokenType, lexerSymbols } from './lexerSymbols';
export { parse, parserWrapper } from './parserWrapper';
export { parse, parserWrapper, parseStatementsStrs } from './parserWrapper';
export { signatureHelp, toSignatureInformation } from './signatureHelp';

@@ -10,0 +10,0 @@ export {

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

CypherLexer.AT,
CypherLexer.AUTH,
CypherLexer.BINDINGS,

@@ -130,2 +131,3 @@ CypherLexer.BOOL,

CypherLexer.CALL,
CypherLexer.CASCADE,
CypherLexer.CASE,

@@ -204,2 +206,3 @@ CypherLexer.CIDR,

CypherLexer.HOME,
CypherLexer.ID,
CypherLexer.IF,

@@ -250,2 +253,3 @@ CypherLexer.IMMUTABLE,

CypherLexer.OF,
CypherLexer.OFFSET,
CypherLexer.ON,

@@ -275,2 +279,4 @@ CypherLexer.ONLY,

CypherLexer.PROPERTY,
CypherLexer.PROVIDER,
CypherLexer.PROVIDERS,
CypherLexer.RANGE,

@@ -290,2 +296,3 @@ CypherLexer.READ,

CypherLexer.REQUIRED,
CypherLexer.RESTRICT,
CypherLexer.RETURN,

@@ -362,2 +369,3 @@ CypherLexer.REVOKE,

CypherLexer.YIELD,
CypherLexer.ZONE,
CypherLexer.ZONED,

@@ -373,2 +381,6 @@ // Preparser tokens

CypherLexer.CLEAR,
CypherLexer.CONNECT,
CypherLexer.DISCONNECT,
CypherLexer.WELCOME,
CypherLexer.SYSINFO,
];

@@ -375,0 +387,0 @@

@@ -11,7 +11,11 @@ import type { ParserRuleContext, Token } from 'antlr4';

default as CypherParser,
FunctionNameContext,
LabelNameContext,
LabelNameIsContext,
LabelOrRelTypeContext,
ProcedureNameContext,
ProcedureResultItemContext,
StatementOrCommandContext,
StatementsOrCommandsContext,
SymbolicNameStringContext,
VariableContext,

@@ -22,2 +26,3 @@ } from './generated-parser/CypherCmdParser';

findStopNode,
getTokens,
inNodeLabel,

@@ -41,6 +46,8 @@ inRelationshipType,

ctx: StatementsOrCommandsContext;
diagnostics: SyntaxDiagnostic[];
syntaxErrors: SyntaxDiagnostic[];
stopNode: ParserRuleContext;
collectedLabelOrRelTypes: LabelOrRelType[];
collectedVariables: string[];
collectedFunctions: ParsedFunction[];
collectedProcedures: ParsedProcedure[];
}

@@ -98,2 +105,14 @@

export type ParsedFunction = {
name: string;
rawText: string;
line: number;
column: number;
offsets: {
start: number;
end: number;
};
};
export type ParsedProcedure = ParsedFunction;
export function createParsingScaffolding(query: string): ParsingScaffolding {

@@ -123,2 +142,24 @@ const inputStream = CharStreams.fromString(query);

export function parseStatementsStrs(query: string): string[] {
const statements = parse(query);
const result: string[] = [];
for (const statement of statements) {
const tokenStream = statement.parser?.getTokenStream() ?? [];
const tokens = getTokens(tokenStream as CommonTokenStream);
const statementStr = tokens
.filter((token) => token.type !== CypherLexer.EOF)
.map((token) => token.text)
.join('');
// Do not return empty statements
if (statementStr.trimLeft().length != 0) {
result.push(statementStr);
}
}
return result;
}
/* Parses a query without storing it in the cache */
export function parse(query: string): StatementOrCommandContext[] {

@@ -142,4 +183,5 @@ const statementScaffolding =

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

@@ -152,7 +194,7 @@ const ctx = parser.statementsOrCommands();

) === undefined;
const diagnostics = isEmptyStatement ? [] : errorListener.errors;
const syntaxErrors = isEmptyStatement ? [] : errorListener.errors;
const collectedCommand = parseToCommand(ctx, isEmptyStatement);
if (!_internalFeatureFlags.consoleCommands) {
diagnostics.push(...errorOnNonCypherCommands(collectedCommand));
syntaxErrors.push(...errorOnNonCypherCommands(collectedCommand));
}

@@ -164,3 +206,3 @@

tokens: tokens,
diagnostics: diagnostics,
syntaxErrors: syntaxErrors,
ctx: ctx,

@@ -170,2 +212,4 @@ stopNode: findStopNode(ctx),

collectedVariables: variableFinder.variables,
collectedFunctions: methodsFinder.functions,
collectedProcedures: methodsFinder.procedures,
};

@@ -182,3 +226,3 @@ });

// This listener is collects all labels and relationship types
// This listener collects all labels and relationship types
class LabelAndRelTypesCollector extends ParseTreeListener {

@@ -257,3 +301,3 @@ labelOrRelTypes: LabelOrRelType[] = [];

if (ctx instanceof VariableContext) {
const variable = ctx.symbolicNameString().getText();
const variable = ctx.symbolicVariableNameString().getText();
// To avoid suggesting the variable that is currently being typed

@@ -278,5 +322,96 @@ // For example RETURN a| <- we don't want to suggest "a" as a variable

}
if (ctx instanceof ProcedureResultItemContext) {
const variable = ctx.symbolicNameString().getText();
if (variable) {
this.variables.push(variable);
}
}
}
}
export function getMethodName(
ctx: ProcedureNameContext | FunctionNameContext,
): string {
const namespaces = ctx.namespace().symbolicNameString_list();
const methodName = ctx.symbolicNameString();
const normalizedName = [...namespaces, methodName]
.map((symbolicName) => {
return getNamespaceString(symbolicName);
})
.join('.');
return normalizedName;
}
function getNamespaceString(ctx: SymbolicNameStringContext): string {
const text = ctx.getText();
const isEscaped = Boolean(ctx.escapedSymbolicNameString());
const hasDot = text.includes('.');
if (isEscaped && !hasDot) {
return text.slice(1, -1);
}
return text;
}
// This listener collects all functions and procedures
class MethodsCollector extends ParseTreeListener {
public procedures: ParsedProcedure[] = [];
public functions: ParsedFunction[] = [];
private tokens: Token[];
constructor(tokens: Token[]) {
super();
this.tokens = tokens;
}
enterEveryRule() {
/* no-op */
}
visitTerminal() {
/* no-op */
}
visitErrorNode() {
/* no-op */
}
exitEveryRule(ctx: unknown) {
if (
ctx instanceof FunctionNameContext ||
ctx instanceof ProcedureNameContext
) {
const methodName = getMethodName(ctx);
const startTokenIndex = ctx.start.tokenIndex;
const stopTokenIndex = ctx.stop.tokenIndex;
const rawText = this.tokens
.slice(startTokenIndex, stopTokenIndex + 1)
.map((token) => {
return token.text;
})
.join('');
const result = {
name: methodName,
rawText: rawText,
line: ctx.start.line,
column: ctx.start.column,
offsets: {
start: ctx.start.start,
end: ctx.stop.stop + 1,
},
};
if (ctx instanceof FunctionNameContext) {
this.functions.push(result);
} else {
this.procedures.push(result);
}
}
}
}
type CypherCmd = { type: 'cypher'; query: string };

@@ -300,3 +435,8 @@ type RuleTokens = {

| { type: 'clear-parameters' }
| { type: 'parse-error' };
| { type: 'connect' }
| { type: 'disconnect' }
| { type: 'server'; operation: string }
| { type: 'welcome' }
| { type: 'parse-error' }
| { type: 'sysinfo' };

@@ -314,9 +454,12 @@ export type ParsedCommand = ParsedCommandNoPosition & RuleTokens;

const cypherStmt = stmt.preparsedStatement();
const cypherStmt = stmt.preparsedStatement()?.statement();
if (cypherStmt) {
// we get the original text input to preserve whitespace
// stripping the preparser part of it
const inputstream = start.getInputStream();
const statement = inputstream.getText(start.start, stop.stop);
const stmtStart = cypherStmt.start;
const stmtStop = cypherStmt.stop;
const statement = inputstream.getText(stmtStart.start, stmtStop.stop);
return { type: 'cypher', statement, start, stop };
return { type: 'cypher', statement, start: stmtStart, stop: stmtStop };
}

@@ -405,2 +548,36 @@

const connectCmd = consoleCmd.connectCmd();
if (connectCmd) {
return { type: 'connect', start, stop };
}
const disconnectCmd = consoleCmd.disconnectCmd();
if (disconnectCmd) {
return { type: 'disconnect', start, stop };
}
const serverCmd = consoleCmd.serverCmd();
const serverArgs = serverCmd?.serverArgs();
if (serverCmd && serverArgs) {
const connect = serverArgs.CONNECT();
if (connect) {
return { type: 'server', operation: 'connect', start, stop };
}
const disconnect = serverArgs.DISCONNECT();
if (disconnect) {
return { type: 'server', operation: 'disconnect', start, stop };
}
}
const welcomeCmd = consoleCmd.welcomeCmd();
if (welcomeCmd) {
return { type: 'welcome', start, stop };
}
const sysInfoCmd = consoleCmd.sysInfoCmd();
if (sysInfoCmd) {
return { type: 'sysinfo', start, stop };
}
return { type: 'parse-error', start, stop };

@@ -407,0 +584,0 @@ }

@@ -40,10 +40,30 @@ import {

const { name, argumentDescription, description } = curr;
const argDescriptions = argumentDescription.map((arg) => {
let label = '';
// If there's a default value, it has the shape
// DefaultParameterValue{value=[0.5, 0.75, 0.9, 0.95, 0.99], type=LIST<FLOAT>}
if (arg.default) {
const startIndex = arg.default.indexOf('value=') + 'value='.length;
const endIndex = arg.default.indexOf(', type=', startIndex);
const defaultArg = arg.default.substring(startIndex, endIndex);
label = `${arg.name} = ${defaultArg} :: ${arg.type}`;
} else {
label = `${arg.name} :: ${arg.type}`;
}
return {
label: label,
documentation: arg.description,
};
});
const argsString = argDescriptions.map((arg) => arg.label).join(', ');
const signature = `${name}(${argsString})`;
return SignatureInformation.create(
name,
signature,
description,
...argumentDescription.map((arg) => ({
label: arg.name,
documentation: arg.description,
})),
...argDescriptions,
);

@@ -50,0 +70,0 @@ }

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

RightArrowContext,
ServerCompletionRuleContext,
StringLiteralContext,

@@ -35,3 +36,3 @@ StringsLiteralContext,

} from 'vscode-languageserver-types';
import { CypherLexer } from '..';
import CypherLexer from '../generated-parser/CypherCmdLexer';
import CypherParserListener from '../generated-parser/CypherCmdParserListener';

@@ -254,2 +255,12 @@ import { CypherTokenType } from '../lexerSymbols';

exitServerCompletionRule = (ctx: ServerCompletionRuleContext) => {
const server = ctx.SERVER();
this.addToken(
server.symbol,
CypherTokenType.consoleCommand,
server.getText(),
);
};
exitParamsArgs = (ctx: ParamsArgsContext) => {

@@ -256,0 +267,0 @@ const clear = ctx.CLEAR();

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

[CypherParser.RULE_listCompletionRule]: 'list',
[CypherParser.RULE_serverCompletionRule]: 'server',
}

@@ -55,0 +56,0 @@ : { [CypherParser.RULE_consoleCommand]: null }),

@@ -9,3 +9,3 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */

// @ts-ignore
import { semanticAnalysis, updateSignatureResolver } from './semanticAnalysis';
import { analyzeQuery, updateSignatureResolver } from './semanticAnalysis';

@@ -36,13 +36,7 @@ export interface SemanticAnalysisResult {

try {
let semanticErrorsResult = undefined;
if (dbSchema.functions && dbSchema.procedures) {
updateSignatureResolver({
procedures: Object.values(dbSchema.procedures),
functions: Object.values(dbSchema.functions),
});
}
semanticAnalysis([query], (a) => {
semanticErrorsResult = a;
updateSignatureResolver({
procedures: Object.values(dbSchema.procedures ?? {}),
functions: Object.values(dbSchema.functions ?? {}),
});
const semanticErrorsResult = analyzeQuery(query);
const errors: SemanticAnalysisElementNoSeverity[] =

@@ -49,0 +43,0 @@ semanticErrorsResult.errors;

@@ -8,5 +8,8 @@ import { DiagnosticSeverity, Position } from 'vscode-languageserver-types';

LabelType,
ParsedFunction,
ParsedProcedure,
ParsedStatement,
parserWrapper,
} from '../parserWrapper';
import { Neo4jFunction } from '../types';
import {

@@ -24,8 +27,10 @@ SemanticAnalysisElement,

const labelName = labelOrRelType.labelText;
const normalizedLabelName = labelName.replace(/^`|`$/g, '');
const notInDatabase =
(labelOrRelType.labeltype === LabelType.nodeLabelType &&
!dbLabels.has(labelName)) ||
!dbLabels.has(normalizedLabelName)) ||
(labelOrRelType.labeltype === LabelType.relLabelType &&
!dbRelationshipTypes.has(labelName)) ||
(!dbLabels.has(labelName) && !dbRelationshipTypes.has(labelName));
!dbRelationshipTypes.has(normalizedLabelName)) ||
(!dbLabels.has(normalizedLabelName) &&
!dbRelationshipTypes.has(normalizedLabelName));

@@ -62,2 +67,78 @@ if (notInDatabase && !labelOrRelType.couldCreateNewLabel) {

function detectNonDeclaredFunction(
parsedFunction: ParsedFunction,
functionsSchema: Record<string, Neo4jFunction>,
): SyntaxDiagnostic | undefined {
const lowercaseFunctionName = parsedFunction.name.toLowerCase();
const caseInsensitiveFunctionInDatabase =
functionsSchema[lowercaseFunctionName];
// Built-in functions are case-insensitive in the database
if (
caseInsensitiveFunctionInDatabase &&
caseInsensitiveFunctionInDatabase.isBuiltIn
) {
return undefined;
}
const functionExistsWithExactName = Boolean(
functionsSchema[parsedFunction.name],
);
if (!functionExistsWithExactName) {
return generateFunctionNotFoundError(parsedFunction);
}
}
function generateFunctionNotFoundError(
parsedFunction: ParsedFunction,
): SyntaxDiagnostic {
const rawText = parsedFunction.rawText;
const nameChunks = rawText.split('\n');
const linesOffset = nameChunks.length - 1;
const lineIndex = parsedFunction.line - 1;
const startColumn = parsedFunction.column;
const endColumn =
linesOffset == 0
? startColumn + rawText.length
: nameChunks.at(-1)?.length ?? 0;
const error: SyntaxDiagnostic = {
severity: DiagnosticSeverity.Error,
range: {
start: Position.create(lineIndex, startColumn),
end: Position.create(lineIndex + linesOffset, endColumn),
},
offsets: parsedFunction.offsets,
message: `Function ${parsedFunction.name} 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`,
};
return error;
}
function generateProcedureNotFoundError(
parsedProcedure: ParsedProcedure,
): SyntaxDiagnostic {
const rawText = parsedProcedure.rawText;
const nameChunks = rawText.split('\n');
const linesOffset = nameChunks.length - 1;
const lineIndex = parsedProcedure.line - 1;
const startColumn = parsedProcedure.column;
const endColumn =
linesOffset == 0
? startColumn + rawText.length
: nameChunks.at(-1)?.length ?? 0;
const error: SyntaxDiagnostic = {
severity: DiagnosticSeverity.Error,
range: {
start: Position.create(lineIndex, startColumn),
end: Position.create(lineIndex + linesOffset, endColumn),
},
offsets: parsedProcedure.offsets,
message: `Procedure ${parsedProcedure.name} 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`,
};
return error;
}
function warnOnUndeclaredLabels(

@@ -180,3 +261,4 @@ parsingResult: ParsedStatement,

const syntaxErrors = validateSyntax(query, dbSchema);
if (syntaxErrors.length > 0) {
// 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;

@@ -186,3 +268,3 @@ }

const semanticErrors = validateSemantics(query, dbSchema);
return semanticErrors;
return syntaxErrors.concat(semanticErrors);
}

@@ -199,5 +281,5 @@

const result = statements.statementsParsing.flatMap((statement) => {
const diagnostics = statement.diagnostics;
const syntaxErrors = statement.syntaxErrors;
const labelWarnings = warnOnUndeclaredLabels(statement, dbSchema);
return diagnostics.concat(labelWarnings).sort(sortByPositionAndMessage);
return syntaxErrors.concat(labelWarnings).sort(sortByPositionAndMessage);
});

@@ -219,5 +301,11 @@

const semanticErrors = statements.flatMap((current) => {
if (current.diagnostics.length === 0) {
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 { notifications, errors } = wrappedSemanticAnalysis(

@@ -229,7 +317,9 @@ cmd.statement,

const elements = notifications.concat(errors);
const result = fixSemanticAnalysisPositions({
const semanticDiagnostics = fixSemanticAnalysisPositions({
semanticElements: elements,
parseResult: current,
}).sort(sortByPositionAndMessage);
return result;
});
return semanticDiagnostics
.concat(functionErrors, procedureErrors)
.sort(sortByPositionAndMessage);
}

@@ -245,1 +335,45 @@ }

}
function errorOnUndeclaredFunctions(
parsingResult: ParsedStatement,
dbSchema: DbSchema,
): SyntaxDiagnostic[] {
const warnings: SyntaxDiagnostic[] = [];
if (dbSchema.functions) {
const functionsInQuery = parsingResult.collectedFunctions;
functionsInQuery.forEach((parsedFunction) => {
const warning = detectNonDeclaredFunction(
parsedFunction,
dbSchema.functions,
);
if (warning) warnings.push(warning);
});
}
return warnings;
}
function errorOnUndeclaredProcedures(
parsingResult: ParsedStatement,
dbSchema: DbSchema,
): SyntaxDiagnostic[] {
const errors: SyntaxDiagnostic[] = [];
if (dbSchema.procedures) {
const proceduresInQuery = parsingResult.collectedProcedures;
proceduresInQuery.forEach((parsedProcedure) => {
const procedureExists = Boolean(
dbSchema.procedures[parsedProcedure.name],
);
if (!procedureExists) {
errors.push(generateProcedureNotFoundError(parsedProcedure));
}
});
}
return errors;
}

@@ -13,3 +13,3 @@ import {

} from 'vscode-languageserver-types';
import { CypherLexer } from '..';
import CypherLexer from '../generated-parser/CypherCmdLexer';
import CypherParser from '../generated-parser/CypherCmdParser';

@@ -16,0 +16,0 @@ import { isCommentOpener } from '../helpers';

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

const unexpectedCompletions = excluded.map((notExpectedItem) =>
const unexpectedCompletions = excluded.filter((notExpectedItem) =>
actualCompletionList.find((value) => {

@@ -65,0 +65,0 @@ // if label is left out -> only check kind and vice versa

@@ -114,2 +114,48 @@ import { CompletionItemKind } from 'vscode-languageserver-types';

test('Correctly completes database name even in a create alias statement', () => {
testCompletions({
query: 'CREATE ALIAS fo for DATABASE ',
dbSchema,
expected: [
{ label: 'db1', kind: CompletionItemKind.Value },
{ label: 'db2', kind: CompletionItemKind.Value },
{ label: 'movies', kind: CompletionItemKind.Value },
{ label: 'myMovies', kind: CompletionItemKind.Value },
{ label: 'scoped.alias', kind: CompletionItemKind.Value },
{ label: 'a.b.c.d', kind: CompletionItemKind.Value },
{ label: '$param1', kind: CompletionItemKind.Variable },
],
excluded: [
// validate invalid keyword bug isn't present
{ label: '', kind: CompletionItemKind.Keyword },
// do not suggest non-string parameters
{ label: '$param2', kind: CompletionItemKind.Variable },
{ label: '$param3', kind: CompletionItemKind.Variable },
],
});
});
test('Correctly completes database name even in a create alias statement including extra of spaces', () => {
testCompletions({
query: 'CREATE ALIAS foo FOR DATABASE ',
dbSchema,
expected: [
{ label: 'db1', kind: CompletionItemKind.Value },
{ label: 'db2', kind: CompletionItemKind.Value },
{ label: 'movies', kind: CompletionItemKind.Value },
{ label: 'myMovies', kind: CompletionItemKind.Value },
{ label: 'scoped.alias', kind: CompletionItemKind.Value },
{ label: 'a.b.c.d', kind: CompletionItemKind.Value },
{ label: '$param1', kind: CompletionItemKind.Variable },
],
excluded: [
// validate invalid keyword bug isn't present
{ label: '', kind: CompletionItemKind.Keyword },
// do not suggest non-string parameters
{ label: '$param2', kind: CompletionItemKind.Variable },
{ label: '$param3', kind: CompletionItemKind.Variable },
],
});
});
test('Suggests only aliases when dropping alias', () => {

@@ -116,0 +162,0 @@ const query = 'DROP ALIAS ';

@@ -49,11 +49,2 @@ import { CompletionItemKind } from 'vscode-languageserver-types';

describe('Auto completion of back to back keywords', () => {
test('Correctly completes OPTIONAL MATCH', () => {
const query = 'OP';
testCompletions({
query,
expected: [{ label: 'OPTIONAL MATCH', kind: CompletionItemKind.Keyword }],
});
});
test('Correctly completes DEFAULT DATABASE and HOME DATABASE', () => {

@@ -60,0 +51,0 @@ const query = 'SHOW ';

@@ -14,8 +14,11 @@ import { CompletionItemKind } from 'vscode-languageserver-types';

test('Correctly completes OPTIONAL MATCH', () => {
const query = 'OP';
test('Correctly completes OPTIONAL MATCH and OPTIONAL CALL', () => {
const query = 'OPTIONAL ';
testCompletions({
query,
expected: [{ label: 'OPTIONAL MATCH', kind: CompletionItemKind.Keyword }],
expected: [
{ label: 'MATCH', kind: CompletionItemKind.Keyword },
{ label: 'CALL', kind: CompletionItemKind.Keyword },
],
});

@@ -67,2 +70,157 @@ });

test('Correctly completes unstarted label when caret is passed', () => {
const query = 'MATCH (n:)';
testCompletions({
query,
offset: query.length - 1,
dbSchema: { labels: ['Cat', 'Person', 'Dog'] },
expected: [{ label: 'Person', kind: CompletionItemKind.TypeParameter }],
});
});
test('Correctly completes unstarted label when caret is passed and there is a space', () => {
const query = 'MATCH (n : ';
testCompletions({
query,
dbSchema: { labels: ['Cat', 'Person', 'Dog'] },
expected: [{ label: 'Person', kind: CompletionItemKind.TypeParameter }],
});
});
test('Completes label with numbers, underscores and non-english letters without backticks in MATCH', () => {
const query = 'MATCH (n:';
testCompletions({
query,
dbSchema: {
labels: ['Cat12', 'Foo_Bar', 'Glögg', 'Glühwein', '_GingerBread_'],
},
expected: [
{
label: 'Cat12',
kind: CompletionItemKind.TypeParameter,
},
{
label: 'Foo_Bar',
kind: CompletionItemKind.TypeParameter,
},
{
label: 'Glögg',
kind: CompletionItemKind.TypeParameter,
},
{
label: 'Glühwein',
kind: CompletionItemKind.TypeParameter,
},
{
label: '_GingerBread_',
kind: CompletionItemKind.TypeParameter,
},
],
});
});
test('Completes relationship types with numbers, underscores and non-english letters without backticks in MATCH', () => {
const query = 'MATCH (n)-[:';
testCompletions({
query,
dbSchema: {
relationshipTypes: [
'Cat12',
'Foo_Bar',
'Glögg',
'Glühwein',
'_GingerBread_',
],
},
expected: [
{
label: 'Cat12',
kind: CompletionItemKind.TypeParameter,
},
{
label: 'Foo_Bar',
kind: CompletionItemKind.TypeParameter,
},
{
label: 'Glögg',
kind: CompletionItemKind.TypeParameter,
},
{
label: 'Glühwein',
kind: CompletionItemKind.TypeParameter,
},
{
label: '_GingerBread_',
kind: CompletionItemKind.TypeParameter,
},
],
});
});
test('Correctly completes label with backticks in MATCH', () => {
const query = 'MATCH (n:';
testCompletions({
query,
dbSchema: { labels: ['Cat', 'Foo Bar', '1Foo'] },
expected: [
{
label: 'Cat',
kind: CompletionItemKind.TypeParameter,
},
{
label: 'Foo Bar',
insertText: '`Foo Bar`',
kind: CompletionItemKind.TypeParameter,
},
{
label: '1Foo',
insertText: '`1Foo`',
kind: CompletionItemKind.TypeParameter,
},
],
});
});
test('Correctly completes started label with backticks in MATCH', () => {
const query = 'MATCH (n:F';
testCompletions({
query,
dbSchema: { labels: ['Cat', 'Foo Bar', '1Foo'] },
expected: [
// This first completion would not be offered in a client
{
label: 'Cat',
kind: CompletionItemKind.TypeParameter,
},
{
label: 'Foo Bar',
insertText: '`Foo Bar`',
kind: CompletionItemKind.TypeParameter,
},
{
label: '1Foo',
insertText: '`1Foo`',
kind: CompletionItemKind.TypeParameter,
},
],
});
});
test('Correctly completes unstarted label for a first statement when caret is passed', () => {
const query = 'MATCH (n:); MATCH (m:)';
testCompletions({
query,
offset: 'MATCH (n:)'.length - 1,
dbSchema: { labels: ['Cat', 'Person', 'Dog'] },
expected: [{ label: 'Person', kind: CompletionItemKind.TypeParameter }],
});
});
test('Correctly completes started barred label inside a node pattern', () => {

@@ -375,2 +533,52 @@ const query = 'MATCH (n:A|B';

});
test('Correctly completes relationship type with backticks', () => {
const query = 'MATCH (n)-[r:';
testCompletions({
query,
dbSchema: { relationshipTypes: ['Rel', 'Foo Bar', '1Foo'] },
expected: [
{
label: 'Rel',
kind: CompletionItemKind.TypeParameter,
},
{
label: 'Foo Bar',
insertText: '`Foo Bar`',
kind: CompletionItemKind.TypeParameter,
},
{
label: '1Foo',
insertText: '`1Foo`',
kind: CompletionItemKind.TypeParameter,
},
],
});
});
test('Correctly completes started relationship type with backticks', () => {
const query = 'MATCH (n)-[r:Foo';
testCompletions({
query,
dbSchema: { relationshipTypes: ['Rel', 'Foo Bar', '1Foo'] },
expected: [
{
label: 'Rel',
kind: CompletionItemKind.TypeParameter,
},
{
label: 'Foo Bar',
insertText: '`Foo Bar`',
kind: CompletionItemKind.TypeParameter,
},
{
label: '1Foo',
insertText: '`1Foo`',
kind: CompletionItemKind.TypeParameter,
},
],
});
});
});

@@ -377,0 +585,0 @@

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

'db.stats.clear': procedures['db.stats.clear'],
'dbms.components': procedures['dbms.components'],
'cdc.current': procedures['cdc.current'],

@@ -292,2 +293,137 @@ 'jwt.security.requestAccess': {

});
test('Correctly completes YIELD', () => {
const query = 'CALL dbms.components() YIELD ';
testCompletions({
query,
dbSchema,
expected: [
{ label: 'name', kind: CompletionItemKind.Variable },
{ label: 'versions', kind: CompletionItemKind.Variable },
{ label: 'edition', kind: CompletionItemKind.Variable },
],
});
});
test('Correctly completes YIELD when we have extra spaces in the procedure name', () => {
const query = 'CALL dbms . components () YIELD ';
testCompletions({
query,
dbSchema,
expected: [
{ label: 'name', kind: CompletionItemKind.Variable },
{ label: 'versions', kind: CompletionItemKind.Variable },
{ label: 'edition', kind: CompletionItemKind.Variable },
],
});
});
test('Does not autocomplete duplicate variables for YIELD', () => {
const query1 = 'CALL dbms.components() YIELD name, ';
const query2 = 'CALL dbms.components() YIELD versions, ve';
testCompletions({
query: query1,
dbSchema,
excluded: [{ label: 'name', kind: CompletionItemKind.Variable }],
});
testCompletions({
query: query2,
dbSchema,
excluded: [{ label: 'versions', kind: CompletionItemKind.Variable }],
});
});
test('Correctly completes RETURN when we have variables from a YIELD statement', () => {
const query1 = 'CALL dbms.components() YIELD name, edition RETURN ';
const query2 = 'CALL dbms.components() YIELD name, versions RETURN ';
const query3 =
'CALL dbms.components() YIELD name, edition RETURN edition, nam';
testCompletions({
query: query1,
dbSchema,
expected: [
{ label: 'name', kind: CompletionItemKind.Variable },
{ label: 'edition', kind: CompletionItemKind.Variable },
],
});
testCompletions({
query: query2,
dbSchema,
expected: [
{ label: 'name', kind: CompletionItemKind.Variable },
{ label: 'versions', kind: CompletionItemKind.Variable },
],
});
testCompletions({
query: query3,
dbSchema,
expected: [{ label: 'name', kind: CompletionItemKind.Variable }],
});
});
test('Correctly completes YIELD when we have started the return value name', () => {
const query1 = 'CALL dbms.components() YIELD n';
const query2 = 'CALL dbms.components() YIELD name, ed';
const query3 = 'CALL dbms.components() YIELD name, edition,';
testCompletions({
query: query1,
dbSchema,
expected: [
{ label: 'name', kind: CompletionItemKind.Variable },
{ label: 'versions', kind: CompletionItemKind.Variable },
{ label: 'edition', kind: CompletionItemKind.Variable },
],
});
testCompletions({
query: query2,
dbSchema,
expected: [
{ label: 'versions', kind: CompletionItemKind.Variable },
{ label: 'edition', kind: CompletionItemKind.Variable },
],
});
testCompletions({
query: query3,
dbSchema,
expected: [{ label: 'versions', kind: CompletionItemKind.Variable }],
});
});
test('Correctly completes YIELD when we have backticks in the procedure name', () => {
const query1 = 'CALL `dbms`.`components`() YIELD ';
const query2 = 'CALL db.`stats`.`collect` () YIELD ';
testCompletions({
query: query1,
dbSchema,
expected: [
{ label: 'name', kind: CompletionItemKind.Variable },
{ label: 'versions', kind: CompletionItemKind.Variable },
{ label: 'edition', kind: CompletionItemKind.Variable },
],
});
testCompletions({
query: query2,
dbSchema,
expected: [
{ label: 'section', kind: CompletionItemKind.Variable },
{ label: 'success', kind: CompletionItemKind.Variable },
{ label: 'message', kind: CompletionItemKind.Variable },
],
});
});
test('Returns empty YIELD elements when procedure does not exist', () => {
const query = 'CALL does.not.exist() YIELD ';
testCompletions({
query,
dbSchema,
expected: [],
});
});
});

@@ -157,2 +157,75 @@ import { CompletionItemKind } from 'vscode-languageserver-types';

});
test('Completes node property keys with numbers, underscores and non-english letters without backticks in MATCH', () => {
const query = 'MATCH (n) WHERE n.';
testCompletions({
query,
dbSchema: {
propertyKeys: [
'Cat12',
'Foo_Bar',
'Glögg',
'Glühwein',
'_GingerBread_',
],
},
expected: [
{
label: 'Cat12',
kind: CompletionItemKind.Property,
},
{
label: 'Foo_Bar',
kind: CompletionItemKind.Property,
},
{
label: 'Glögg',
kind: CompletionItemKind.Property,
},
{
label: 'Glühwein',
kind: CompletionItemKind.Property,
},
{
label: '_GingerBread_',
kind: CompletionItemKind.Property,
},
],
});
});
test('correctly completes property keys with backticks', () => {
const dbSchema = { propertyKeys: ['foo bar', 'prop'] };
const query = 'MATCH (n) WHERE n.';
testCompletions({
query,
dbSchema,
expected: [
{ label: 'prop', kind: CompletionItemKind.Property },
{
label: 'foo bar',
insertText: '`foo bar`',
kind: CompletionItemKind.Property,
},
],
});
});
test('correctly completes started property keys with backticks', () => {
const dbSchema = { propertyKeys: ['foo bar', 'prop'] };
const query = 'MATCH (n) WHERE n.foo';
testCompletions({
query,
dbSchema,
expected: [
{ label: 'prop', kind: CompletionItemKind.Property },
{
label: 'foo bar',
insertText: '`foo bar`',
kind: CompletionItemKind.Property,
},
],
});
});
});

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

expect(
result.statementsParsing.flatMap((statement) => statement.diagnostics),
result.statementsParsing.flatMap((statement) => statement.syntaxErrors),
).toEqual([]);

@@ -34,3 +34,3 @@ expect(

result.statementsParsing
.flatMap((statement) => statement.diagnostics)
.flatMap((statement) => statement.syntaxErrors)
.map((e) => e.message),

@@ -44,2 +44,3 @@ ).toContain(msg);

});
afterAll(() => {

@@ -52,2 +53,5 @@ _internalFeatureFlags.consoleCommands = false;

expectParsedCommands(':history', [{ type: 'history' }]);
expectParsedCommands(':connect', [{ type: 'connect' }]);
expectParsedCommands(':disconnect', [{ type: 'disconnect' }]);
expectParsedCommands(':sysinfo', [{ type: 'sysinfo' }]);
});

@@ -100,2 +104,68 @@

]);
expect(applySyntaxColouring(':connect')).toEqual([
{
length: 1,
position: {
line: 0,
startCharacter: 0,
startOffset: 0,
},
token: ':',
tokenType: 'consoleCommand',
},
{
length: 7,
position: {
line: 0,
startCharacter: 1,
startOffset: 1,
},
token: 'connect',
tokenType: 'consoleCommand',
},
]);
expect(applySyntaxColouring(':disconnect')).toEqual([
{
length: 1,
position: {
line: 0,
startCharacter: 0,
startOffset: 0,
},
token: ':',
tokenType: 'consoleCommand',
},
{
length: 10,
position: {
line: 0,
startCharacter: 1,
startOffset: 1,
},
token: 'disconnect',
tokenType: 'consoleCommand',
},
]);
expect(applySyntaxColouring(':sysinfo')).toEqual([
{
length: 1,
position: {
line: 0,
startCharacter: 0,
startOffset: 0,
},
token: ':',
tokenType: 'consoleCommand',
},
{
length: 7,
position: {
line: 0,
startCharacter: 1,
startOffset: 1,
},
token: 'sysinfo',
tokenType: 'consoleCommand',
},
]);
});

@@ -105,3 +175,8 @@

expect(autocomplete(':', {})).toEqual([
{ kind: 23, label: 'server' },
{ kind: 23, label: 'use' },
{ kind: 23, label: 'sysinfo' },
{ kind: 23, label: 'welcome' },
{ kind: 23, label: 'disconnect' },
{ kind: 23, label: 'connect' },
{ kind: 23, label: 'param' },

@@ -143,3 +218,6 @@ { kind: 23, label: 'history' },

test('handles misspelled or non-existing command', () => {
expectErrorMessage(':foo', 'Expected any of param, history, clear or use');
expectErrorMessage(
':foo',
'Expected any of sysinfo, welcome, disconnect, connect, param, history, clear, server or use',
);

@@ -458,2 +536,105 @@ expectErrorMessage(':clea', 'Unexpected token. Did you mean clear?');

describe('server', () => {
beforeAll(() => {
_internalFeatureFlags.consoleCommands = true;
});
afterAll(() => {
_internalFeatureFlags.consoleCommands = false;
});
test('basic server usage', () => {
expectParsedCommands(':server connect', [
{ type: 'server', operation: 'connect' },
]);
expectParsedCommands(':server disconnect', [
{ type: 'server', operation: 'disconnect' },
]);
});
test('autocompletes operation', () => {
const mapCompletions = autocomplete(':server conn', {
functions: {
'duration.inSeconds': {
...testData.emptyFunction,
name: 'duration.inSeconds',
},
},
});
const expected = [
{ kind: 23, label: 'connect' },
{ kind: 23, label: 'disconnect' },
];
expected.forEach((completion) => {
expect(mapCompletions).toContainEqual(completion);
});
});
test('incorrect usage of :server', () => {
expectErrorMessage(':server', 'Expected connect or disconnect');
expectErrorMessage(':server xyz', 'Expected connect or disconnect');
});
test('highlights :server properly', () => {
expect(applySyntaxColouring(':server')).toEqual([
{
length: 1,
position: { line: 0, startCharacter: 0, startOffset: 0 },
token: ':',
tokenType: 'consoleCommand',
},
{
length: 6,
position: { line: 0, startCharacter: 1, startOffset: 1 },
token: 'server',
tokenType: 'consoleCommand',
},
]);
expect(applySyntaxColouring(':server connect')).toEqual([
{
length: 1,
position: { line: 0, startCharacter: 0, startOffset: 0 },
token: ':',
tokenType: 'consoleCommand',
},
{
length: 6,
position: { line: 0, startCharacter: 1, startOffset: 1 },
token: 'server',
tokenType: 'consoleCommand',
},
{
length: 7,
position: { line: 0, startCharacter: 8, startOffset: 8 },
token: 'connect',
tokenType: 'consoleCommand',
},
]);
expect(applySyntaxColouring(':server disconnect')).toEqual([
{
length: 1,
position: { line: 0, startCharacter: 0, startOffset: 0 },
token: ':',
tokenType: 'consoleCommand',
},
{
length: 6,
position: { line: 0, startCharacter: 1, startOffset: 1 },
token: 'server',
tokenType: 'consoleCommand',
},
{
length: 10,
position: { line: 0, startCharacter: 8, startOffset: 8 },
token: 'disconnect',
tokenType: 'consoleCommand',
},
]);
});
});
describe('command parser also handles cypher', () => {

@@ -460,0 +641,0 @@ beforeAll(() => {

@@ -103,2 +103,47 @@ import { _internalFeatureFlags } from '../../featureFlags';

test('Semantic errors work when we also have preparser options', () => {
const query = `EXPLAIN MATCH (n);
PROFILE MATCH (m) RETURN n`;
expect(getDiagnosticsForQuery({ query })).toEqual([
{
message:
'Query cannot conclude with MATCH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
offsets: {
end: 17,
start: 8,
},
range: {
end: {
character: 17,
line: 0,
},
start: {
character: 8,
line: 0,
},
},
severity: 1,
},
{
message: 'Variable `n` not defined',
offsets: {
end: 64,
start: 63,
},
range: {
end: {
character: 45,
line: 1,
},
start: {
character: 44,
line: 1,
},
},
severity: 1,
},
]);
});
test('Surfaces notifications correctly', () => {

@@ -117,2 +162,21 @@ const query = `

message:
'CALL subquery without a variable scope clause is now deprecated. Use CALL () { ... }',
offsets: {
end: 100,
start: 26,
},
range: {
end: {
character: 5,
line: 5,
},
start: {
character: 4,
line: 2,
},
},
severity: 2,
},
{
message:
'Variable in subquery is shadowing a variable with the same name from the outer scope. If you want to use that variable instead, it must be imported into the subquery using importing WITH clause. (the shadowing variable is: shadowed)',

@@ -139,3 +203,3 @@ offsets: {

test('Accumulates several semantic errors', () => {
const query = `CALL { MATCH (n) RETURN m} IN TRANSACTIONS OF -1 ROWS`;
const query = `CALL () { MATCH (n) RETURN m} IN TRANSACTIONS OF -1 ROWS`;

@@ -147,3 +211,3 @@ expect(getDiagnosticsForQuery({ query })).toEqual([

offsets: {
end: 53,
end: 56,
start: 0,

@@ -153,3 +217,3 @@ },

end: {
character: 53,
character: 56,
line: 0,

@@ -167,12 +231,12 @@ },

offsets: {
end: 25,
start: 24,
end: 28,
start: 27,
},
range: {
end: {
character: 25,
character: 28,
line: 0,
},
start: {
character: 24,
character: 27,
line: 0,

@@ -187,12 +251,12 @@ },

offsets: {
end: 48,
start: 46,
end: 51,
start: 49,
},
range: {
end: {
character: 48,
character: 51,
line: 0,
},
start: {
character: 46,
character: 49,
line: 0,

@@ -207,6 +271,6 @@ },

test('Shows errors for CALL IN TXs used in UNION', () => {
const query = `CALL { CREATE (x) } IN TRANSACTIONS
const query = `CALL () { CREATE (x) } IN TRANSACTIONS
RETURN 1 AS result
UNION
CALL { CREATE (x) } IN TRANSACTIONS
CALL () { CREATE (x) } IN TRANSACTIONS
RETURN 2 AS result`;

@@ -218,3 +282,3 @@

offsets: {
end: 139,
end: 145,
start: 0,

@@ -237,4 +301,4 @@ },

offsets: {
end: 139,
start: 79,
end: 145,
start: 82,
},

@@ -258,3 +322,3 @@ range: {

const query = `WITH 1 AS i
CALL {
CALL () {
WITH 2 AS i

@@ -273,4 +337,4 @@ RETURN *

offsets: {
end: 44,
start: 43,
end: 47,
start: 46,
},

@@ -292,4 +356,4 @@ range: {

offsets: {
end: 61,
start: 53,
end: 64,
start: 56,
},

@@ -309,25 +373,7 @@ range: {

{
message: 'Variable `i` already declared in outer scope',
offsets: {
end: 61,
start: 60,
},
range: {
end: {
character: 16,
line: 3,
},
start: {
character: 15,
line: 3,
},
},
severity: 1,
},
{
message:
'Variable in subquery is shadowing a variable with the same name from the outer scope. If you want to use that variable instead, it must be imported into the subquery using importing WITH clause. (the shadowing variable is: i)',
offsets: {
end: 97,
start: 96,
end: 100,
start: 99,
},

@@ -349,4 +395,4 @@ range: {

offsets: {
end: 114,
start: 106,
end: 117,
start: 109,
},

@@ -365,20 +411,2 @@ range: {

},
{
message: 'Variable `i` already declared in outer scope',
offsets: {
end: 114,
start: 113,
},
range: {
end: {
character: 16,
line: 6,
},
start: {
character: 15,
line: 6,
},
},
severity: 1,
},
]);

@@ -388,3 +416,3 @@ });

test('Shows errors for subquery with only WITH', () => {
const query = 'WITH 1 AS a CALL { WITH a } RETURN a';
const query = 'WITH 1 AS a CALL (a) { WITH a } RETURN a';

@@ -394,14 +422,14 @@ expect(getDiagnosticsForQuery({ query })).toEqual([

message:
'Query must conclude with a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD.',
'Query cannot conclude with WITH (must be a RETURN clause, a FINISH clause, an update clause, a unit subquery call, or a procedure call with no YIELD).',
offsets: {
end: 25,
start: 19,
end: 29,
start: 23,
},
range: {
end: {
character: 25,
character: 29,
line: 0,
},
start: {
character: 19,
character: 23,
line: 0,

@@ -418,3 +446,3 @@ },

WITH 1 AS a
CALL {
CALL () {
USE x

@@ -431,3 +459,3 @@ RETURN 2 AS b

WITH 1 AS a
CALL {
CALL () {
USE other

@@ -443,4 +471,4 @@ RETURN 2 AS b

offsets: {
end: 88,
start: 55,
end: 91,
start: 58,
},

@@ -666,4 +694,3 @@ range: {

UNWIND [0, 1, 2] AS x
CALL {
WITH x
CALL (x) {
RETURN x * 10 AS y

@@ -679,4 +706,4 @@ }

offsets: {
end: 137,
start: 136,
end: 120,
start: 119,
},

@@ -686,7 +713,7 @@ range: {

character: 32,
line: 5,
line: 4,
},
start: {
character: 31,
line: 5,
line: 4,
},

@@ -772,14 +799,15 @@ },

{
message: 'Path selectors such as `ANY 2 PATHS` are not supported yet',
message:
'Multiple path patterns cannot be used in the same clause in combination with a selective path selector.',
offsets: {
end: 32,
start: 21,
end: 82,
start: 16,
},
range: {
end: {
character: 26,
line: 1,
character: 31,
line: 2,
},
start: {
character: 15,
character: 10,
line: 1,

@@ -917,4 +945,3 @@ },

{
message:
'Type mismatch: p defined with conflicting type List<T> (expected Path)',
message: 'Variable `p` already declared',
offsets: {

@@ -1203,3 +1230,3 @@ end: 35,

message:
'A pattern expression should only be used in order to test the existence of a pattern. It should therefore only be used in contexts that evaluate to a boolean, e.g. inside the function exists() or in a WHERE-clause. No other uses are allowed, instead they should be replaced by a pattern comprehension.',
'A pattern expression should only be used in order to test the existence of a pattern. It can no longer be used inside the function size(), an alternative is to replace size() with COUNT {}.',
offsets: {

@@ -1452,6 +1479,9 @@ end: 29,

test('Does not provide semantic validation for pluggeable functions when schema is not available', () => {
test('Does not error on SHORTEST k', () => {
expect(
getDiagnosticsForQuery({
query: `RETURN apoc.coll.sum(['a', 'b'])`,
query: `MATCH p = SHORTEST 2 (a)-[]-+(b)
WHERE a.name = "foo" AND b.name = "bar"
RETURN [n in nodes(p) | n.name] AS stops;`,
dbSchema: testData.mockSchema,
}),

@@ -1461,147 +1491,17 @@ ).toEqual([]);

test('Provides semantic validation for built-in functions', () => {
test('Does not error on dynamic labels', () => {
expect(
getDiagnosticsForQuery({
query: `WITH character_length() AS a
WITH character_length(1) AS b, a
RETURN a,b`,
}),
).toEqual([
{
message: "Insufficient parameters for function 'character_length'",
offsets: {
end: 28,
start: 5,
},
range: {
end: {
character: 28,
line: 0,
},
start: {
character: 5,
line: 0,
},
},
severity: 1,
},
{
message: 'Type mismatch: expected String but was Integer',
offsets: {
end: 60,
start: 59,
},
range: {
end: {
character: 31,
line: 1,
},
start: {
character: 30,
line: 1,
},
},
severity: 1,
},
]);
});
test('Provides semantic validation for procedures when a schema is available', () => {
expect(
getDiagnosticsForQuery({
query: `
CALL db.awaitIndex('index', 'time')
CALL db.awaitIndex()
`,
query: `MATCH (n)
SET n:$("label")`,
dbSchema: testData.mockSchema,
}),
).toEqual([
{
message: 'Type mismatch: expected Integer but was String',
offsets: {
end: 43,
start: 37,
},
range: {
end: {
character: 42,
line: 1,
},
start: {
character: 36,
line: 1,
},
},
severity: 1,
},
{
message: `Procedure call does not provide the required number of arguments: got 0 expected at least 1 (total: 2, 1 of which have default values).
Procedure db.awaitIndex has signature: db.awaitIndex(indexName :: STRING, timeOutSeconds = 300 :: INTEGER) ::
meaning that it expects at least 1 argument of type STRING
`,
offsets: {
end: 73,
start: 53,
},
range: {
end: {
character: 28,
line: 2,
},
start: {
character: 8,
line: 2,
},
},
severity: 1,
},
]);
});
test('Shows default values correctly for external procedures', () => {
expect(
getDiagnosticsForQuery({
query: 'CALL apoc.load.xml()',
dbSchema: testData.mockSchema,
}),
).toEqual([
{
message: `Procedure call does not provide the required number of arguments: got 0 expected at least 1 (total: 4, 3 of which have default values).
Procedure apoc.load.xml has signature: apoc.load.xml(urlOrBinary :: ANY, path = / :: STRING, config = {} :: MAP, simple = false :: BOOLEAN) :: value :: MAP
meaning that it expects at least 1 argument of type ANY
`,
offsets: {
end: 20,
start: 0,
},
range: {
end: {
character: 20,
line: 0,
},
start: {
character: 0,
line: 0,
},
},
severity: 1,
},
]);
});
test('Does not fail if default arguments for procedure not provided', () => {
expect(
getDiagnosticsForQuery({
query: `CALL apoc.load.xml('url', '/path')`,
dbSchema: testData.mockSchema,
}),
).toEqual([]);
});
test('Does not fail semantic validation for functions that expect LIST<ANY>', () => {
test('Does not error on dynamic properties', () => {
expect(
getDiagnosticsForQuery({
query: `RETURN apoc.coll.max(['a'])`,
query: `MATCH (n)
SET n["prop"] = "some value"`,
dbSchema: testData.mockSchema,

@@ -1612,6 +1512,6 @@ }),

test('Provides semantic validation for functions that expect LIST<NUMBER>', () => {
test('Shows deprecation for CALL IN TXs without parentheses', () => {
expect(
getDiagnosticsForQuery({
query: `RETURN apoc.coll.sum(['a', 'b'])`,
query: `CALL { MATCH (n) RETURN n} IN TRANSACTIONS OF 50 ROWS RETURN 1`,
dbSchema: testData.mockSchema,

@@ -1622,18 +1522,18 @@ }),

message:
'Type mismatch: expected List<Float>, List<Integer> or List<Number> but was List<String>',
'CALL subquery without a variable scope clause is now deprecated. Use CALL () { ... }',
offsets: {
end: 31,
start: 21,
end: 62,
start: 0,
},
range: {
end: {
character: 31,
character: 62,
line: 0,
},
start: {
character: 21,
character: 0,
line: 0,
},
},
severity: 1,
severity: 2,
},

@@ -1643,52 +1543,12 @@ ]);

// TODO This doesn't seem to warn on deprecated
// arguments for either functions or procedures,
// needs to be solved in the database first
test('Notifies of deprecated returns in procedures', () => {
test('Shows deprecation for CALL without parentheses', () => {
expect(
getDiagnosticsForQuery({
query: `CALL apoc.meta.graphSample({})`,
dbSchema: {
functions: {},
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',
},
],
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,
},
},
},
},
query: `WITH 1 AS i
CALL {
RETURN 3 AS j
}
RETURN i
`,
dbSchema: testData.mockSchema,
}),

@@ -1698,15 +1558,15 @@ ).toEqual([

message:
"The query used a deprecated field from a procedure. ('nodes' returned by 'apoc.meta.graphSample' is deprecated.)",
'CALL subquery without a variable scope clause is now deprecated. Use CALL () { ... }',
offsets: {
end: 30,
start: 0,
end: 67,
start: 22,
},
range: {
end: {
character: 30,
line: 0,
character: 11,
line: 3,
},
start: {
character: 0,
line: 0,
character: 10,
line: 1,
},

@@ -1713,0 +1573,0 @@ },

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

message:
'Expected any of ALTER, CALL, CREATE, DEALLOCATE, DELETE, DENY, DETACH, DROP, DRYRUN, ENABLE, EXPLAIN, FINISH, FOREACH, GRANT, INSERT, LOAD, MATCH, MERGE, NODETACH, OPTIONAL, PROFILE, REALLOCATE, REMOVE, RENAME, RETURN, REVOKE, SET, SHOW, START, STOP, TERMINATE, UNWIND, USE, USING or WITH',
'Expected any of ALTER, CALL, CREATE, 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: {

@@ -120,3 +120,3 @@ end: {

message:
"Expected any of '}', AND, CALL, CREATE, DELETE, DETACH, FINISH, FOREACH, INSERT, LOAD, MATCH, MERGE, NODETACH, OPTIONAL, OR, REMOVE, RETURN, SET, UNION, UNWIND, USE, WITH, XOR or an expression",
"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: {

@@ -193,2 +193,69 @@ end: {

test('Syntax validation treats labels and relationship types with backticks correctly', () => {
const query = `MATCH (n: \`Foo Bar\`)-[r:\`RR QQ\`]->() RETURN n`;
// With spaces in the label / relationship
expect(
getDiagnosticsForQuery({
query,
dbSchema: { labels: ['Foo Bar'], relationshipTypes: ['RR QQ'] },
}),
).toEqual([]);
// Without spaces in the label / relationship
expect(
getDiagnosticsForQuery({
query: 'MATCH (n: `Foo`)-[r:`RR`]->() RETURN n',
dbSchema: { labels: ['Foo'], relationshipTypes: ['RR'] },
}),
).toEqual([]);
// With incorrect backticked label / rel type
expect(
getDiagnosticsForQuery({
query,
dbSchema: { labels: ['Foo'], relationshipTypes: ['RR'] },
}),
).toEqual([
{
message:
"Label `Foo Bar` 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: 19,
start: 10,
},
range: {
end: {
character: 19,
line: 0,
},
start: {
character: 10,
line: 0,
},
},
severity: 2,
},
{
message:
"Relationship type `RR QQ` 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: 24,
},
range: {
end: {
character: 31,
line: 0,
},
start: {
character: 24,
line: 0,
},
},
severity: 2,
},
]);
});
test('Syntax validation warns on missing rel type when database can be contacted', () => {

@@ -701,3 +768,3 @@ const query = `MATCH (n)-[:Rel]->(m) RETURN n`;

{
message: "Expected '{' or a procedure name",
message: "Expected any of '{', '(' or a procedure name",
offsets: {

@@ -819,3 +886,3 @@ end: 6,

{
message: 'Expected a string or a parameter',
message: 'Expected any of CHANGE, a string or a parameter',
offsets: {

@@ -984,2 +1051,17 @@ end: 39,

});
test.each([
`MATCH (n:Test1) RETURN n.profile`,
`CREATE (n:Test1 {explain: 'Explain'});`,
`RETURN { clear: 'Clear', params: 'params', history: 'history'}`,
])(
'Syntax validation should not fail if cmd keywords are used in map properties %s',
(query) => {
expect(
getDiagnosticsForQuery({
query,
}),
).toEqual([]);
},
);
});

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 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