@neo4j-cypher/language-support
Advanced tools
Comparing version 2.0.0-canary-bb7e9d3 to 2.0.0-canary-c3c9599
# @neo4j-cypher/language-support | ||
## 2.0.0-next.13 | ||
### Patch Changes | ||
- 84a12fc: Added parsing of CYPHER <version> and CYPHER <optionName> = <value> | ||
- d329252: Adds deprecation warning tags on deprecated functions/procedures | ||
- b0e419e: Adds backticking when needed on autocompletions of aliases and database names | ||
## 2.0.0-next.12 | ||
### Patch Changes | ||
- 88fbe63: Modified backtick insertion to only happen when really necessary | ||
- 22081b0: Adds autocompletions following YIELD in a procedure call | ||
- 62ac442: Add support for sysinfo, welcome, connect, disconnect and server console commands | ||
## 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 | ||
@@ -4,0 +44,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { Neo4jFunction, Neo4jProcedure } from './types'; | ||
import { CypherVersion, Neo4jFunction, Neo4jProcedure } from './types'; | ||
export interface DbSchema { | ||
@@ -13,2 +13,3 @@ labels?: string[]; | ||
functions?: Record<string, Neo4jFunction>; | ||
defaultLanguage?: CypherVersion; | ||
} |
@@ -6,313 +6,316 @@ 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 AUTH = 34; | ||
static readonly BAR = 35; | ||
static readonly BINDINGS = 36; | ||
static readonly BOOL = 37; | ||
static readonly BOOLEAN = 38; | ||
static readonly BOOSTED = 39; | ||
static readonly BOTH = 40; | ||
static readonly BREAK = 41; | ||
static readonly BRIEF = 42; | ||
static readonly BTREE = 43; | ||
static readonly BUILT = 44; | ||
static readonly BY = 45; | ||
static readonly CALL = 46; | ||
static readonly CASE = 47; | ||
static readonly CHANGE = 48; | ||
static readonly CIDR = 49; | ||
static readonly COLLECT = 50; | ||
static readonly COLON = 51; | ||
static readonly COLONCOLON = 52; | ||
static readonly COMMA = 53; | ||
static readonly COMMAND = 54; | ||
static readonly COMMANDS = 55; | ||
static readonly COMMIT = 56; | ||
static readonly COMPOSITE = 57; | ||
static readonly CONCURRENT = 58; | ||
static readonly CONSTRAINT = 59; | ||
static readonly CONSTRAINTS = 60; | ||
static readonly CONTAINS = 61; | ||
static readonly COPY = 62; | ||
static readonly CONTINUE = 63; | ||
static readonly COUNT = 64; | ||
static readonly CREATE = 65; | ||
static readonly CSV = 66; | ||
static readonly CURRENT = 67; | ||
static readonly DATA = 68; | ||
static readonly DATABASE = 69; | ||
static readonly DATABASES = 70; | ||
static readonly DATE = 71; | ||
static readonly DATETIME = 72; | ||
static readonly DBMS = 73; | ||
static readonly DEALLOCATE = 74; | ||
static readonly DEFAULT = 75; | ||
static readonly DEFINED = 76; | ||
static readonly DELETE = 77; | ||
static readonly DENY = 78; | ||
static readonly DESC = 79; | ||
static readonly DESCENDING = 80; | ||
static readonly DESTROY = 81; | ||
static readonly DETACH = 82; | ||
static readonly DIFFERENT = 83; | ||
static readonly DOLLAR = 84; | ||
static readonly DISTINCT = 85; | ||
static readonly DIVIDE = 86; | ||
static readonly DOT = 87; | ||
static readonly DOTDOT = 88; | ||
static readonly DOUBLEBAR = 89; | ||
static readonly DRIVER = 90; | ||
static readonly DROP = 91; | ||
static readonly DRYRUN = 92; | ||
static readonly DUMP = 93; | ||
static readonly DURATION = 94; | ||
static readonly EACH = 95; | ||
static readonly EDGE = 96; | ||
static readonly ENABLE = 97; | ||
static readonly ELEMENT = 98; | ||
static readonly ELEMENTS = 99; | ||
static readonly ELSE = 100; | ||
static readonly ENCRYPTED = 101; | ||
static readonly END = 102; | ||
static readonly ENDS = 103; | ||
static readonly EQ = 104; | ||
static readonly EXECUTABLE = 105; | ||
static readonly EXECUTE = 106; | ||
static readonly EXIST = 107; | ||
static readonly EXISTENCE = 108; | ||
static readonly EXISTS = 109; | ||
static readonly ERROR = 110; | ||
static readonly FAIL = 111; | ||
static readonly FALSE = 112; | ||
static readonly FIELDTERMINATOR = 113; | ||
static readonly FINISH = 114; | ||
static readonly FLOAT = 115; | ||
static readonly FOR = 116; | ||
static readonly FOREACH = 117; | ||
static readonly FROM = 118; | ||
static readonly FULLTEXT = 119; | ||
static readonly FUNCTION = 120; | ||
static readonly FUNCTIONS = 121; | ||
static readonly GE = 122; | ||
static readonly GRANT = 123; | ||
static readonly GRAPH = 124; | ||
static readonly GRAPHS = 125; | ||
static readonly GROUP = 126; | ||
static readonly GROUPS = 127; | ||
static readonly GT = 128; | ||
static readonly HEADERS = 129; | ||
static readonly HOME = 130; | ||
static readonly ID = 131; | ||
static readonly IF = 132; | ||
static readonly IMPERSONATE = 133; | ||
static readonly IMMUTABLE = 134; | ||
static readonly IN = 135; | ||
static readonly INDEX = 136; | ||
static readonly INDEXES = 137; | ||
static readonly INF = 138; | ||
static readonly INFINITY = 139; | ||
static readonly INSERT = 140; | ||
static readonly INT = 141; | ||
static readonly INTEGER = 142; | ||
static readonly IS = 143; | ||
static readonly JOIN = 144; | ||
static readonly KEY = 145; | ||
static readonly LABEL = 146; | ||
static readonly LABELS = 147; | ||
static readonly AMPERSAND = 148; | ||
static readonly EXCLAMATION_MARK = 149; | ||
static readonly LBRACKET = 150; | ||
static readonly LCURLY = 151; | ||
static readonly LE = 152; | ||
static readonly LEADING = 153; | ||
static readonly LIMITROWS = 154; | ||
static readonly LIST = 155; | ||
static readonly LOAD = 156; | ||
static readonly LOCAL = 157; | ||
static readonly LOOKUP = 158; | ||
static readonly LPAREN = 159; | ||
static readonly LT = 160; | ||
static readonly MANAGEMENT = 161; | ||
static readonly MAP = 162; | ||
static readonly MATCH = 163; | ||
static readonly MERGE = 164; | ||
static readonly MINUS = 165; | ||
static readonly PERCENT = 166; | ||
static readonly INVALID_NEQ = 167; | ||
static readonly NEQ = 168; | ||
static readonly NAME = 169; | ||
static readonly NAMES = 170; | ||
static readonly NAN = 171; | ||
static readonly NFC = 172; | ||
static readonly NFD = 173; | ||
static readonly NFKC = 174; | ||
static readonly NFKD = 175; | ||
static readonly NEW = 176; | ||
static readonly NODE = 177; | ||
static readonly NODETACH = 178; | ||
static readonly NODES = 179; | ||
static readonly NONE = 180; | ||
static readonly NORMALIZE = 181; | ||
static readonly NORMALIZED = 182; | ||
static readonly NOT = 183; | ||
static readonly NOTHING = 184; | ||
static readonly NOWAIT = 185; | ||
static readonly NULL = 186; | ||
static readonly OF = 187; | ||
static readonly ON = 188; | ||
static readonly ONLY = 189; | ||
static readonly OPTIONAL = 190; | ||
static readonly OPTIONS = 191; | ||
static readonly OPTION = 192; | ||
static readonly OR = 193; | ||
static readonly ORDER = 194; | ||
static readonly OUTPUT = 195; | ||
static readonly PASSWORD = 196; | ||
static readonly PASSWORDS = 197; | ||
static readonly PATH = 198; | ||
static readonly PATHS = 199; | ||
static readonly PERIODIC = 200; | ||
static readonly PLAINTEXT = 201; | ||
static readonly PLUS = 202; | ||
static readonly PLUSEQUAL = 203; | ||
static readonly POINT = 204; | ||
static readonly POPULATED = 205; | ||
static readonly POW = 206; | ||
static readonly PRIMARY = 207; | ||
static readonly PRIMARIES = 208; | ||
static readonly PRIVILEGE = 209; | ||
static readonly PRIVILEGES = 210; | ||
static readonly PROCEDURE = 211; | ||
static readonly PROCEDURES = 212; | ||
static readonly PROPERTIES = 213; | ||
static readonly PROPERTY = 214; | ||
static readonly PROVIDER = 215; | ||
static readonly PROVIDERS = 216; | ||
static readonly QUESTION = 217; | ||
static readonly RANGE = 218; | ||
static readonly RBRACKET = 219; | ||
static readonly RCURLY = 220; | ||
static readonly READ = 221; | ||
static readonly REALLOCATE = 222; | ||
static readonly REDUCE = 223; | ||
static readonly RENAME = 224; | ||
static readonly REGEQ = 225; | ||
static readonly REL = 226; | ||
static readonly RELATIONSHIP = 227; | ||
static readonly RELATIONSHIPS = 228; | ||
static readonly REMOVE = 229; | ||
static readonly REPEATABLE = 230; | ||
static readonly REPLACE = 231; | ||
static readonly REPORT = 232; | ||
static readonly REQUIRE = 233; | ||
static readonly REQUIRED = 234; | ||
static readonly RETURN = 235; | ||
static readonly REVOKE = 236; | ||
static readonly ROLE = 237; | ||
static readonly ROLES = 238; | ||
static readonly ROW = 239; | ||
static readonly ROWS = 240; | ||
static readonly RPAREN = 241; | ||
static readonly SCAN = 242; | ||
static readonly SEC = 243; | ||
static readonly SECOND = 244; | ||
static readonly SECONDARY = 245; | ||
static readonly SECONDARIES = 246; | ||
static readonly SECONDS = 247; | ||
static readonly SEEK = 248; | ||
static readonly SEMICOLON = 249; | ||
static readonly SERVER = 250; | ||
static readonly SERVERS = 251; | ||
static readonly SET = 252; | ||
static readonly SETTING = 253; | ||
static readonly SETTINGS = 254; | ||
static readonly SHORTEST_PATH = 255; | ||
static readonly SHORTEST = 256; | ||
static readonly SHOW = 257; | ||
static readonly SIGNED = 258; | ||
static readonly SINGLE = 259; | ||
static readonly SKIPROWS = 260; | ||
static readonly START = 261; | ||
static readonly STARTS = 262; | ||
static readonly STATUS = 263; | ||
static readonly STOP = 264; | ||
static readonly STRING = 265; | ||
static readonly SUPPORTED = 266; | ||
static readonly SUSPENDED = 267; | ||
static readonly TARGET = 268; | ||
static readonly TERMINATE = 269; | ||
static readonly TEXT = 270; | ||
static readonly THEN = 271; | ||
static readonly TIME = 272; | ||
static readonly TIMES = 273; | ||
static readonly TIMESTAMP = 274; | ||
static readonly TIMEZONE = 275; | ||
static readonly TO = 276; | ||
static readonly TOPOLOGY = 277; | ||
static readonly TRAILING = 278; | ||
static readonly TRANSACTION = 279; | ||
static readonly TRANSACTIONS = 280; | ||
static readonly TRAVERSE = 281; | ||
static readonly TRIM = 282; | ||
static readonly TRUE = 283; | ||
static readonly TYPE = 284; | ||
static readonly TYPED = 285; | ||
static readonly TYPES = 286; | ||
static readonly UNION = 287; | ||
static readonly UNIQUE = 288; | ||
static readonly UNIQUENESS = 289; | ||
static readonly UNWIND = 290; | ||
static readonly URL = 291; | ||
static readonly USE = 292; | ||
static readonly USER = 293; | ||
static readonly USERS = 294; | ||
static readonly USING = 295; | ||
static readonly VALUE = 296; | ||
static readonly VARCHAR = 297; | ||
static readonly VECTOR = 298; | ||
static readonly VERBOSE = 299; | ||
static readonly VERTEX = 300; | ||
static readonly WAIT = 301; | ||
static readonly WHEN = 302; | ||
static readonly WHERE = 303; | ||
static readonly WITH = 304; | ||
static readonly WITHOUT = 305; | ||
static readonly WRITE = 306; | ||
static readonly XOR = 307; | ||
static readonly YIELD = 308; | ||
static readonly ZONED = 309; | ||
static readonly IDENTIFIER = 310; | ||
static readonly ARROW_LINE = 311; | ||
static readonly ARROW_LEFT_HEAD = 312; | ||
static readonly ARROW_RIGHT_HEAD = 313; | ||
static readonly ErrorChar = 314; | ||
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 CYPHER = 10; | ||
static readonly SPACE = 11; | ||
static readonly SINGLE_LINE_COMMENT = 12; | ||
static readonly MULTI_LINE_COMMENT = 13; | ||
static readonly DECIMAL_DOUBLE = 14; | ||
static readonly UNSIGNED_DECIMAL_INTEGER = 15; | ||
static readonly UNSIGNED_HEX_INTEGER = 16; | ||
static readonly UNSIGNED_OCTAL_INTEGER = 17; | ||
static readonly STRING_LITERAL1 = 18; | ||
static readonly STRING_LITERAL2 = 19; | ||
static readonly ESCAPED_SYMBOLIC_NAME = 20; | ||
static readonly ACCESS = 21; | ||
static readonly ACTIVE = 22; | ||
static readonly ADMIN = 23; | ||
static readonly ADMINISTRATOR = 24; | ||
static readonly ALIAS = 25; | ||
static readonly ALIASES = 26; | ||
static readonly ALL_SHORTEST_PATHS = 27; | ||
static readonly ALL = 28; | ||
static readonly ALTER = 29; | ||
static readonly AND = 30; | ||
static readonly ANY = 31; | ||
static readonly ARRAY = 32; | ||
static readonly AS = 33; | ||
static readonly ASC = 34; | ||
static readonly ASCENDING = 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 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; | ||
@@ -319,0 +322,0 @@ static readonly channelNames: string[]; |
export type { ParserRuleContext } from 'antlr4'; | ||
export { autocomplete } from './autocompletion/autocompletion'; | ||
export { shouldAutoCompleteYield } from './autocompletion/autocompletionHelpers'; | ||
export type { DbSchema } from './dbSchema'; | ||
@@ -7,8 +8,8 @@ export { _internalFeatureFlags } from './featureFlags'; | ||
export { CypherTokenType, lexerSymbols } from './lexerSymbols'; | ||
export { parse, parserWrapper } from './parserWrapper'; | ||
export { parse, parserWrapper, parseStatementsStrs } from './parserWrapper'; | ||
export { signatureHelp, toSignatureInformation } from './signatureHelp'; | ||
export { applySyntaxColouring, mapCypherToSemanticTokenIndex, syntaxColouringLegend, } from './syntaxColouring/syntaxColouring'; | ||
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'; | ||
@@ -20,1 +21,2 @@ export { textMateGrammar } from './textMateGrammar'; | ||
import CypherParser from './generated-parser/CypherCmdParser'; | ||
export { formatQuery } from './formatting/formatting'; |
@@ -23,3 +23,5 @@ export declare enum CypherTokenType { | ||
none = "none", | ||
consoleCommand = "consoleCommand" | ||
consoleCommand = "consoleCommand", | ||
settingValue = "settingValue", | ||
setting = "setting" | ||
} | ||
@@ -26,0 +28,0 @@ export declare const lexerOperators: number[]; |
import type { ParserRuleContext, Token } from 'antlr4'; | ||
import { default as CypherParser, StatementOrCommandContext, StatementsOrCommandsContext } from './generated-parser/CypherCmdParser'; | ||
import { SyntaxDiagnostic } from './syntaxValidation/syntaxValidationHelpers'; | ||
import { default as CypherParser, FunctionNameContext, ProcedureNameContext, StatementOrCommandContext, StatementsOrCommandsContext } from './generated-parser/CypherCmdParser'; | ||
import { SyntaxDiagnostic } from './syntaxValidation/syntaxValidation'; | ||
import { CypherVersion } from './types'; | ||
export interface ParsedStatement { | ||
@@ -9,3 +10,3 @@ command: ParsedCommand; | ||
ctx: StatementsOrCommandsContext; | ||
diagnostics: SyntaxDiagnostic[]; | ||
syntaxErrors: SyntaxDiagnostic[]; | ||
stopNode: ParserRuleContext; | ||
@@ -15,2 +16,4 @@ collectedLabelOrRelTypes: LabelOrRelType[]; | ||
collectedFunctions: ParsedFunction[]; | ||
collectedProcedures: ParsedProcedure[]; | ||
cypherVersion?: CypherVersion; | ||
} | ||
@@ -35,3 +38,3 @@ export interface ParsingResult { | ||
export type LabelOrRelType = { | ||
labeltype: LabelType; | ||
labelType: LabelType; | ||
labelText: string; | ||
@@ -47,3 +50,3 @@ couldCreateNewLabel: boolean; | ||
export type ParsedFunction = { | ||
parsedName: string; | ||
name: string; | ||
rawText: string; | ||
@@ -57,5 +60,8 @@ line: 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 = { | ||
@@ -91,3 +97,14 @@ type: 'cypher'; | ||
} | { | ||
type: 'connect'; | ||
} | { | ||
type: 'disconnect'; | ||
} | { | ||
type: 'server'; | ||
operation: string; | ||
} | { | ||
type: 'welcome'; | ||
} | { | ||
type: 'parse-error'; | ||
} | { | ||
type: 'sysinfo'; | ||
}; | ||
@@ -94,0 +111,0 @@ export type ParsedCommand = ParsedCommandNoPosition & RuleTokens; |
@@ -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,3 @@ import { CompletionItem as VSCodeCompletionItem } from 'vscode-languageserver-types'; | ||
}; | ||
export type CypherVersion = 'CYPHER 25' | 'CYPHER 5'; | ||
export type Neo4jStringType = string; | ||
@@ -26,2 +27,3 @@ export type ArgumentDescription = ReturnDescription & { | ||
} & Record<string, unknown>; | ||
deprecatedBy?: string; | ||
}; | ||
@@ -38,2 +40,3 @@ export type Neo4jFunction = { | ||
isDeprecated: boolean; | ||
deprecatedBy?: string; | ||
}; | ||
@@ -40,0 +43,0 @@ export type CompletionItem = VSCodeCompletionItem & { |
@@ -21,3 +21,3 @@ { | ||
], | ||
"version": "2.0.0-canary-bb7e9d3", | ||
"version": "2.0.0-canary-c3c9599", | ||
"main": "./dist/cjs/index.cjs", | ||
@@ -46,3 +46,3 @@ "module": "./dist/esm/index.mjs", | ||
"dependencies": { | ||
"antlr4": "*", | ||
"antlr4": "4.13.2", | ||
"antlr4-c3": "*", | ||
@@ -49,0 +49,0 @@ "fastest-levenshtein": "^1.0.16", |
@@ -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,73 @@ import { _internalFeatureFlags } from '../featureFlags'; | ||
const versions = ['5']; | ||
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; | ||
} | ||
} | ||
function backtickDbNameIfNeeded(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 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) => | ||
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 = ( | ||
@@ -258,11 +322,21 @@ candidateRule: CandidateRule, | ||
) | ||
.map(([paramName]) => ({ | ||
label: `$${paramName}`, | ||
kind: CompletionItemKind.Variable, | ||
})); | ||
.map(([paramName]) => { | ||
const backtickedName = backtickIfNeeded(paramName); | ||
return { | ||
label: `$${paramName}`, | ||
kind: CompletionItemKind.Variable, | ||
...(backtickedName | ||
? { insertText: `$${backtickIfNeeded(paramName)}` } | ||
: {}), | ||
}; | ||
}); | ||
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; | ||
}) ?? []; | ||
@@ -406,2 +480,5 @@ enum ExpectedParameterType { | ||
CypherParser.RULE_commandNameExpression, | ||
CypherParser.RULE_procedureResultItem, | ||
CypherParser.RULE_cypherVersion, | ||
CypherParser.RULE_cypher, | ||
@@ -414,2 +491,3 @@ // Either enable the helper rules for lexer clashes, | ||
CypherParser.RULE_listCompletionRule, | ||
CypherParser.RULE_serverCompletionRule, | ||
] | ||
@@ -442,2 +520,33 @@ : [CypherParser.RULE_consoleCommand]), | ||
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) { | ||
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) { | ||
@@ -553,2 +662,6 @@ return functionNameCompletions(candidateRule, tokens, dbSchema); | ||
if (ruleNumber === CypherParser.RULE_serverCompletionRule) { | ||
return [{ label: 'server', kind: CompletionItemKind.Event }]; | ||
} | ||
if (ruleNumber === CypherParser.RULE_leftArrow) { | ||
@@ -670,2 +783,3 @@ return [ | ||
kind: CompletionItemKind.Value, | ||
insertText: backtickDbNameIfNeeded(aliasName), | ||
})), | ||
@@ -683,2 +797,3 @@ ]; | ||
kind: CompletionItemKind.Value, | ||
insertText: backtickDbNameIfNeeded(databaseName), | ||
})), | ||
@@ -685,0 +800,0 @@ ]; |
@@ -1,2 +0,2 @@ | ||
import { Neo4jFunction, Neo4jProcedure } from './types'; | ||
import { CypherVersion, Neo4jFunction, Neo4jProcedure } from './types'; | ||
@@ -14,2 +14,3 @@ export interface DbSchema { | ||
functions?: Record<string, Neo4jFunction>; | ||
defaultLanguage?: CypherVersion; | ||
} |
@@ -98,6 +98,8 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
stopNode, | ||
(p) => p instanceof NodePatternContext, | ||
(p) => | ||
p instanceof NodePatternContext || | ||
p instanceof RelationshipPatternContext, | ||
); | ||
return isDefined(nodePattern); | ||
return nodePattern instanceof NodePatternContext; | ||
} | ||
@@ -108,6 +110,8 @@ | ||
stopNode, | ||
(p) => p instanceof RelationshipPatternContext, | ||
(p) => | ||
p instanceof NodePatternContext || | ||
p instanceof RelationshipPatternContext, | ||
); | ||
return isDefined(relPattern); | ||
return relPattern instanceof RelationshipPatternContext; | ||
} | ||
@@ -114,0 +118,0 @@ |
export type { ParserRuleContext } from 'antlr4'; | ||
export { autocomplete } from './autocompletion/autocompletion'; | ||
export { shouldAutoCompleteYield } from './autocompletion/autocompletionHelpers'; | ||
export type { DbSchema } from './dbSchema'; | ||
@@ -7,3 +8,3 @@ export { _internalFeatureFlags } from './featureFlags'; | ||
export { CypherTokenType, lexerSymbols } from './lexerSymbols'; | ||
export { parse, parserWrapper } from './parserWrapper'; | ||
export { parse, parserWrapper, parseStatementsStrs } from './parserWrapper'; | ||
export { signatureHelp, toSignatureInformation } from './signatureHelp'; | ||
@@ -16,8 +17,4 @@ export { | ||
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'; | ||
@@ -29,1 +26,2 @@ export { textMateGrammar } from './textMateGrammar'; | ||
import CypherParser from './generated-parser/CypherCmdParser'; | ||
export { formatQuery } from './formatting/formatting' |
@@ -26,2 +26,4 @@ import CypherLexer from './generated-parser/CypherCmdLexer'; | ||
consoleCommand = 'consoleCommand', | ||
settingValue = 'settingValue', | ||
setting = 'setting', | ||
} | ||
@@ -93,2 +95,3 @@ | ||
CypherLexer.ESCAPED_SYMBOLIC_NAME, | ||
CypherLexer.EXTENDED_IDENTIFIER, | ||
]; | ||
@@ -117,3 +120,2 @@ | ||
CypherLexer.ASCENDING, | ||
CypherLexer.ASSERT, | ||
CypherLexer.ASSIGN, | ||
@@ -128,7 +130,6 @@ CypherLexer.AT, | ||
CypherLexer.BREAK, | ||
CypherLexer.BRIEF, | ||
CypherLexer.BTREE, | ||
CypherLexer.BUILT, | ||
CypherLexer.BY, | ||
CypherLexer.CALL, | ||
CypherLexer.CASCADE, | ||
CypherLexer.CASE, | ||
@@ -140,3 +141,2 @@ CypherLexer.CIDR, | ||
CypherLexer.COMMANDS, | ||
CypherLexer.COMMIT, | ||
CypherLexer.COMPOSITE, | ||
@@ -190,2 +190,3 @@ CypherLexer.CONSTRAINT, | ||
CypherLexer.EXISTS, | ||
CypherLexer.EXTENDED_IDENTIFIER, | ||
CypherLexer.FAIL, | ||
@@ -255,2 +256,3 @@ CypherLexer.FALSE, | ||
CypherLexer.OF, | ||
CypherLexer.OFFSET, | ||
CypherLexer.ON, | ||
@@ -263,3 +265,2 @@ CypherLexer.ONLY, | ||
CypherLexer.ORDER, | ||
CypherLexer.OUTPUT, | ||
CypherLexer.PASSWORD, | ||
@@ -269,3 +270,2 @@ CypherLexer.PASSWORDS, | ||
CypherLexer.PATHS, | ||
CypherLexer.PERIODIC, | ||
CypherLexer.PLAINTEXT, | ||
@@ -298,2 +298,3 @@ CypherLexer.POINT, | ||
CypherLexer.REQUIRED, | ||
CypherLexer.RESTRICT, | ||
CypherLexer.RETURN, | ||
@@ -360,3 +361,2 @@ CypherLexer.REVOKE, | ||
CypherLexer.VECTOR, | ||
CypherLexer.VERBOSE, | ||
CypherLexer.VERTEX, | ||
@@ -371,2 +371,3 @@ CypherLexer.WAIT, | ||
CypherLexer.YIELD, | ||
CypherLexer.ZONE, | ||
CypherLexer.ZONED, | ||
@@ -376,2 +377,3 @@ // Preparser tokens | ||
CypherLexer.PROFILE, | ||
CypherLexer.CYPHER, | ||
]; | ||
@@ -383,2 +385,6 @@ | ||
CypherLexer.CLEAR, | ||
CypherLexer.CONNECT, | ||
CypherLexer.DISCONNECT, | ||
CypherLexer.WELCOME, | ||
CypherLexer.SYSINFO, | ||
]; | ||
@@ -385,0 +391,0 @@ |
@@ -10,7 +10,9 @@ import type { ParserRuleContext, Token } from 'antlr4'; | ||
ClauseContext, | ||
CypherVersionContext, | ||
default as CypherParser, | ||
FunctionNameContext, | ||
LabelNameContext, | ||
LabelNameIsContext, | ||
LabelOrRelTypeContext, | ||
ProcedureNameContext, | ||
ProcedureResultItemContext, | ||
StatementOrCommandContext, | ||
@@ -24,2 +26,3 @@ StatementsOrCommandsContext, | ||
findStopNode, | ||
getTokens, | ||
inNodeLabel, | ||
@@ -31,6 +34,5 @@ inRelationshipType, | ||
} from './helpers'; | ||
import { | ||
SyntaxDiagnostic, | ||
SyntaxErrorsListener, | ||
} from './syntaxValidation/syntaxValidationHelpers'; | ||
import { SyntaxDiagnostic } from './syntaxValidation/syntaxValidation'; | ||
import { SyntaxErrorsListener } from './syntaxValidation/syntaxValidationHelpers'; | ||
import { CypherVersion } from './types'; | ||
@@ -44,3 +46,3 @@ export interface ParsedStatement { | ||
ctx: StatementsOrCommandsContext; | ||
diagnostics: SyntaxDiagnostic[]; | ||
syntaxErrors: SyntaxDiagnostic[]; | ||
stopNode: ParserRuleContext; | ||
@@ -50,2 +52,4 @@ collectedLabelOrRelTypes: LabelOrRelType[]; | ||
collectedFunctions: ParsedFunction[]; | ||
collectedProcedures: ParsedProcedure[]; | ||
cypherVersion?: CypherVersion; | ||
} | ||
@@ -92,3 +96,3 @@ | ||
export type LabelOrRelType = { | ||
labeltype: LabelType; | ||
labelType: LabelType; | ||
labelText: string; | ||
@@ -105,3 +109,3 @@ couldCreateNewLabel: boolean; | ||
export type ParsedFunction = { | ||
parsedName: string; | ||
name: string; | ||
rawText: string; | ||
@@ -115,2 +119,3 @@ line: number; | ||
}; | ||
export type ParsedProcedure = ParsedFunction; | ||
@@ -141,2 +146,24 @@ export function createParsingScaffolding(query: string): ParsingScaffolding { | ||
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[] { | ||
@@ -160,3 +187,4 @@ const statementScaffolding = | ||
const variableFinder = new VariableCollector(); | ||
const functionFinder = new FunctionCollector(tokens); | ||
const methodsFinder = new MethodsCollector(tokens); | ||
const cypherVersionCollector = new CypherVersionCollector(); | ||
const errorListener = new SyntaxErrorsListener(tokens); | ||
@@ -166,3 +194,4 @@ parser._parseListeners = [ | ||
variableFinder, | ||
functionFinder, | ||
methodsFinder, | ||
cypherVersionCollector, | ||
]; | ||
@@ -176,7 +205,10 @@ parser.addErrorListener(errorListener); | ||
) === undefined; | ||
const diagnostics = isEmptyStatement ? [] : errorListener.errors; | ||
const collectedCommand = parseToCommand(ctx, isEmptyStatement); | ||
const collectedCommand = parseToCommand(ctx, tokens, isEmptyStatement); | ||
const syntaxErrors = | ||
collectedCommand.type !== 'cypher' && !isEmptyStatement | ||
? errorListener.errors | ||
: []; | ||
if (!_internalFeatureFlags.consoleCommands) { | ||
diagnostics.push(...errorOnNonCypherCommands(collectedCommand)); | ||
syntaxErrors.push(...errorOnNonCypherCommands(collectedCommand)); | ||
} | ||
@@ -188,3 +220,3 @@ | ||
tokens: tokens, | ||
diagnostics: diagnostics, | ||
syntaxErrors: syntaxErrors, | ||
ctx: ctx, | ||
@@ -194,3 +226,5 @@ stopNode: findStopNode(ctx), | ||
collectedVariables: variableFinder.variables, | ||
collectedFunctions: functionFinder.functions, | ||
collectedFunctions: methodsFinder.functions, | ||
collectedProcedures: methodsFinder.procedures, | ||
cypherVersion: cypherVersionCollector.cypherVersion, | ||
}; | ||
@@ -222,3 +256,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, | ||
@@ -230,3 +264,3 @@ // it means the parser recovered from an error reading the label | ||
this.labelOrRelTypes.push({ | ||
labeltype: getLabelType(ctx), | ||
labelType: getLabelType(ctx), | ||
labelText: ctx.getText(), | ||
@@ -251,3 +285,3 @@ couldCreateNewLabel: couldCreateNewLabel(ctx), | ||
this.labelOrRelTypes.push({ | ||
labeltype: getLabelType(ctx), | ||
labelType: getLabelType(ctx), | ||
labelText: symbolicName.start.text, | ||
@@ -284,3 +318,3 @@ couldCreateNewLabel: couldCreateNewLabel(ctx), | ||
if (ctx instanceof VariableContext) { | ||
const variable = ctx.symbolicNameString().getText(); | ||
const variable = ctx.symbolicVariableNameString().getText(); | ||
// To avoid suggesting the variable that is currently being typed | ||
@@ -305,7 +339,41 @@ // For example RETURN a| <- we don't want to suggest "a" as a variable | ||
} | ||
if (ctx instanceof ProcedureResultItemContext) { | ||
const variable = ctx.getText(); | ||
if (variable) { | ||
this.variables.push(variable); | ||
} | ||
} | ||
} | ||
} | ||
// This listener collects all functions | ||
class FunctionCollector extends ParseTreeListener { | ||
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[] = []; | ||
@@ -330,4 +398,7 @@ private tokens: Token[]; | ||
exitEveryRule(ctx: unknown) { | ||
if (ctx instanceof FunctionNameContext) { | ||
const functionName = this.getNormalizedFunctionName(ctx); | ||
if ( | ||
ctx instanceof FunctionNameContext || | ||
ctx instanceof ProcedureNameContext | ||
) { | ||
const methodName = getMethodName(ctx); | ||
@@ -344,4 +415,4 @@ const startTokenIndex = ctx.start.tokenIndex; | ||
this.functions.push({ | ||
parsedName: functionName, | ||
const result = { | ||
name: methodName, | ||
rawText: rawText, | ||
@@ -354,29 +425,39 @@ line: ctx.start.line, | ||
}, | ||
}); | ||
}; | ||
if (ctx instanceof FunctionNameContext) { | ||
this.functions.push(result); | ||
} else { | ||
this.procedures.push(result); | ||
} | ||
} | ||
} | ||
} | ||
private getNormalizedFunctionName(ctx: FunctionNameContext): string { | ||
const namespaces = ctx.namespace().symbolicNameString_list(); | ||
const functionName = ctx.symbolicNameString(); | ||
class CypherVersionCollector extends ParseTreeListener { | ||
public cypherVersion: CypherVersion; | ||
const normalizedName = [...namespaces, functionName] | ||
.map((symbolicName) => { | ||
return this.getFunctionNamespaceString(symbolicName); | ||
}) | ||
.join('.'); | ||
constructor() { | ||
super(); | ||
} | ||
return normalizedName; | ||
enterEveryRule() { | ||
/* no-op */ | ||
} | ||
visitTerminal() { | ||
/* no-op */ | ||
} | ||
visitErrorNode() { | ||
/* no-op */ | ||
} | ||
private getFunctionNamespaceString(ctx: SymbolicNameStringContext): string { | ||
const text = ctx.getText(); | ||
const isEscaped = Boolean(ctx.escapedSymbolicNameString()); | ||
const hasDot = text.includes('.'); | ||
if (isEscaped && !hasDot) { | ||
return text.slice(1, -1); | ||
exitEveryRule(ctx: unknown) { | ||
if (ctx instanceof CypherVersionContext) { | ||
this.cypherVersion = | ||
ctx.getText() === '5' | ||
? 'CYPHER 5' | ||
: ctx.getText() === '25' | ||
? 'CYPHER 25' | ||
: undefined; | ||
} | ||
return text; | ||
} | ||
@@ -403,3 +484,8 @@ } | ||
| { type: 'clear-parameters' } | ||
| { type: 'parse-error' }; | ||
| { type: 'connect' } | ||
| { type: 'disconnect' } | ||
| { type: 'server'; operation: string } | ||
| { type: 'welcome' } | ||
| { type: 'parse-error' } | ||
| { type: 'sysinfo' }; | ||
@@ -410,2 +496,3 @@ export type ParsedCommand = ParsedCommandNoPosition & RuleTokens; | ||
stmts: StatementsOrCommandsContext, | ||
tokens: Token[], | ||
isEmptyStatement: boolean, | ||
@@ -416,11 +503,17 @@ ): ParsedCommand { | ||
if (stmt) { | ||
const { start, stop } = stmt; | ||
const start = stmts.start; | ||
let stop = stmts.stop; | ||
const cypherStmt = stmt.preparsedStatement(); | ||
if (stop && stop.type === CypherLexer.SEMICOLON) { | ||
stop = tokens[stop.tokenIndex - 1]; | ||
} | ||
const inputstream = start.getInputStream(); | ||
const cypherStmt = stmt.preparsedStatement()?.statement(); | ||
if (cypherStmt) { | ||
// we get the original text input to preserve whitespace | ||
const inputstream = start.getInputStream(); | ||
const statement = inputstream.getText(start.start, stop.stop); | ||
// stripping the preparser part of it | ||
const stmtStart = cypherStmt.start; | ||
const statement = inputstream.getText(stmtStart.start, stop.stop); | ||
return { type: 'cypher', statement, start, stop }; | ||
return { type: 'cypher', statement, start: stmtStart, stop: stop }; | ||
} | ||
@@ -487,3 +580,3 @@ | ||
const lambda = paramArgs.lambda(); | ||
const name = lambda?.unescapedSymbolicNameString()?.getText(); | ||
const name = lambda?.parameterName()?.getText(); | ||
const expression = lambda?.expression()?.getText(); | ||
@@ -510,5 +603,40 @@ if (name && expression) { | ||
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 }; | ||
} | ||
return { type: 'parse-error', start, stop }; | ||
const statement = inputstream.getText(start.start, stop.stop); | ||
return { type: 'cypher', statement, start: start, stop: stop }; | ||
} | ||
@@ -535,3 +663,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( | ||
@@ -538,0 +666,0 @@ ({ start, stop }): SyntaxDiagnostic => ({ |
@@ -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 @@ } |
@@ -7,6 +7,7 @@ import { ParseTreeWalker, TerminalNode, Token } from 'antlr4'; | ||
ConsoleCommandContext, | ||
CypherOptionNameContext, | ||
CypherOptionValueContext, | ||
FunctionNameContext, | ||
KeywordLiteralContext, | ||
LabelNameContext, | ||
LabelNameIsContext, | ||
LabelOrRelTypeContext, | ||
@@ -18,2 +19,3 @@ LabelTypeContext, | ||
ParameterContext, | ||
ParameterNameContext, | ||
ParamsArgsContext, | ||
@@ -25,2 +27,3 @@ ProcedureNameContext, | ||
RightArrowContext, | ||
ServerCompletionRuleContext, | ||
StringLiteralContext, | ||
@@ -37,3 +40,3 @@ StringsLiteralContext, | ||
} from 'vscode-languageserver-types'; | ||
import { CypherLexer } from '..'; | ||
import CypherLexer from '../generated-parser/CypherCmdLexer'; | ||
import CypherParserListener from '../generated-parser/CypherCmdParserListener'; | ||
@@ -80,2 +83,4 @@ import { CypherTokenType } from '../lexerSymbols'; | ||
[CypherTokenType.property]: SemanticTokenTypes.property, | ||
[CypherTokenType.setting]: SemanticTokenTypes.enum, | ||
[CypherTokenType.settingValue]: SemanticTokenTypes.enumMember, | ||
[CypherTokenType.label]: SemanticTokenTypes.type, | ||
@@ -117,7 +122,11 @@ [CypherTokenType.variable]: SemanticTokenTypes.variable, | ||
exitLabelName = (ctx: LabelNameContext) => { | ||
this.addToken(ctx.start, CypherTokenType.label, ctx.getText()); | ||
exitCypherOptionValue = (ctx: CypherOptionValueContext) => { | ||
this.addToken(ctx.start, CypherTokenType.settingValue, ctx.getText()); | ||
}; | ||
exitLabelNameIs = (ctx: LabelNameIsContext) => { | ||
exitCypherOptionName = (ctx: CypherOptionNameContext) => { | ||
this.addToken(ctx.start, CypherTokenType.setting, ctx.getText()); | ||
}; | ||
exitLabelName = (ctx: LabelNameContext) => { | ||
this.addToken(ctx.start, CypherTokenType.label, ctx.getText()); | ||
@@ -184,2 +193,6 @@ }; | ||
exitParameterName = (ctx: ParameterNameContext) => { | ||
this.addToken(ctx.start, CypherTokenType.variable, ctx.getText()); | ||
}; | ||
exitProcedureResultItem = (ctx: ProcedureResultItemContext) => { | ||
@@ -259,2 +272,12 @@ this.addToken(ctx.start, CypherTokenType.variable, ctx.getText()); | ||
exitServerCompletionRule = (ctx: ServerCompletionRuleContext) => { | ||
const server = ctx.SERVER(); | ||
this.addToken( | ||
server.symbol, | ||
CypherTokenType.consoleCommand, | ||
server.getText(), | ||
); | ||
}; | ||
exitParamsArgs = (ctx: ParamsArgsContext) => { | ||
@@ -338,3 +361,3 @@ const clear = ctx.CLEAR(); | ||
This allows to correclty colour things that are | ||
This allows to correctly colour things that are | ||
recognized as keywords by the lexer in positions | ||
@@ -341,0 +364,0 @@ where they are not keywords (e.g. MATCH (MATCH: MATCH)) |
@@ -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', | ||
@@ -54,2 +53,3 @@ [CypherParser.RULE_stringLiteral]: 'a string', | ||
[CypherParser.RULE_listCompletionRule]: 'list', | ||
[CypherParser.RULE_serverCompletionRule]: 'server', | ||
} | ||
@@ -131,10 +131,2 @@ : { [CypherParser.RULE_consoleCommand]: null }), | ||
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 +133,0 @@ } |
@@ -5,17 +5,18 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ | ||
import { DiagnosticSeverity } from 'vscode-languageserver-types'; | ||
import { DiagnosticSeverity, Position } from 'vscode-languageserver-types'; | ||
import { DbSchema } from '../dbSchema'; | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
import { semanticAnalysis, updateSignatureResolver } from './semanticAnalysis'; | ||
import { CypherVersion } from '../types'; | ||
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,48 +26,67 @@ line: number; | ||
}; | ||
endPosition: { | ||
offset: number; | ||
line: number; | ||
column: number; | ||
}; | ||
} | ||
type SemanticAnalysisElementNoSeverity = Omit< | ||
SemanticAnalysisElement, | ||
'severity' | ||
>; | ||
let previousSchema: DbSchema | undefined = undefined; | ||
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, | ||
}, | ||
})); | ||
} | ||
export function wrappedSemanticAnalysis( | ||
query: string, | ||
dbSchema: DbSchema, | ||
parsedVersion?: CypherVersion, | ||
): SemanticAnalysisResult { | ||
try { | ||
let semanticErrorsResult = undefined; | ||
if (dbSchema.functions && dbSchema.procedures) { | ||
if (JSON.stringify(dbSchema) !== JSON.stringify(previousSchema)) { | ||
previousSchema = dbSchema; | ||
const procedures = Object.values(dbSchema.procedures ?? {}); | ||
const functions = Object.values(dbSchema.functions ?? {}); | ||
updateSignatureResolver({ | ||
procedures: Object.values(dbSchema.procedures), | ||
functions: Object.values(dbSchema.functions), | ||
procedures: procedures, | ||
functions: functions, | ||
}); | ||
} | ||
semanticAnalysis([query], (a) => { | ||
semanticErrorsResult = a; | ||
}); | ||
const errors: SemanticAnalysisElementNoSeverity[] = | ||
semanticErrorsResult.errors; | ||
const notifications: SemanticAnalysisElementNoSeverity[] = | ||
let cypherVersion = 'CYPHER 5'; | ||
const defaultVersion = dbSchema.defaultLanguage?.toUpperCase(); | ||
if (parsedVersion) { | ||
cypherVersion = parsedVersion; | ||
} else if (dbSchema.defaultLanguage) { | ||
cypherVersion = defaultVersion; | ||
} | ||
const semanticErrorsResult = analyzeQuery( | ||
query, | ||
cypherVersion.toLowerCase(), | ||
); | ||
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, | ||
), | ||
}; | ||
@@ -73,0 +93,0 @@ } catch (e) { |
@@ -1,4 +0,8 @@ | ||
import { DiagnosticSeverity, Position } from 'vscode-languageserver-types'; | ||
import { | ||
Diagnostic, | ||
DiagnosticSeverity, | ||
DiagnosticTag, | ||
Position, | ||
} from 'vscode-languageserver-types'; | ||
import { ParserRuleContext, ParseTree, Token } from 'antlr4'; | ||
import { DbSchema } from '../dbSchema'; | ||
@@ -9,12 +13,13 @@ import { | ||
ParsedFunction, | ||
ParsedProcedure, | ||
ParsedStatement, | ||
parserWrapper, | ||
} 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( | ||
@@ -26,34 +31,23 @@ labelOrRelType: LabelOrRelType, | ||
const labelName = labelOrRelType.labelText; | ||
const normalizedLabelName = labelName.replace(/^`|`$/g, ''); | ||
const notInDatabase = | ||
(labelOrRelType.labeltype === LabelType.nodeLabelType && | ||
!dbLabels.has(labelName)) || | ||
(labelOrRelType.labeltype === LabelType.relLabelType && | ||
!dbRelationshipTypes.has(labelName)) || | ||
(!dbLabels.has(labelName) && !dbRelationshipTypes.has(labelName)); | ||
(labelOrRelType.labelType === LabelType.nodeLabelType && | ||
!dbLabels.has(normalizedLabelName)) || | ||
(labelOrRelType.labelType === LabelType.relLabelType && | ||
!dbRelationshipTypes.has(normalizedLabelName)) || | ||
(!dbLabels.has(normalizedLabelName) && | ||
!dbRelationshipTypes.has(normalizedLabelName)); | ||
if (notInDatabase && !labelOrRelType.couldCreateNewLabel) { | ||
const labelChunks = labelName.split('\n'); | ||
const linesOffset = labelChunks.length - 1; | ||
const lineIndex = labelOrRelType.line - 1; | ||
const startColumn = labelOrRelType.column; | ||
const endColumn = | ||
linesOffset == 0 | ||
? startColumn + labelName.length | ||
: labelChunks.at(-1)?.length ?? 0; | ||
const warning: SyntaxDiagnostic = { | ||
severity: DiagnosticSeverity.Warning, | ||
range: { | ||
start: Position.create(lineIndex, startColumn), | ||
end: Position.create(lineIndex + linesOffset, endColumn), | ||
}, | ||
offsets: labelOrRelType.offsets, | ||
message: | ||
labelOrRelType.labeltype + | ||
' ' + | ||
labelName + | ||
" 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 warning; | ||
const message = | ||
labelOrRelType.labelType + | ||
' ' + | ||
labelName + | ||
" 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 generateSyntaxDiagnostic( | ||
labelName, | ||
labelOrRelType, | ||
DiagnosticSeverity.Warning, | ||
message, | ||
); | ||
} | ||
@@ -64,7 +58,58 @@ | ||
function generateSyntaxDiagnostic( | ||
rawText: string, | ||
parsedText: ParsedProcedure | LabelOrRelType, | ||
severity: DiagnosticSeverity, | ||
message: string, | ||
deprecation: boolean = false, | ||
): SyntaxDiagnostic { | ||
const nameChunks = rawText.split('\n'); | ||
const linesOffset = nameChunks.length - 1; | ||
const lineIndex = parsedText.line - 1; | ||
const startColumn = parsedText.column; | ||
const endColumn = | ||
linesOffset == 0 | ||
? startColumn + rawText.length | ||
: nameChunks.at(-1)?.length ?? 0; | ||
const error: SyntaxDiagnostic = { | ||
severity, | ||
range: { | ||
start: Position.create(lineIndex, startColumn), | ||
end: Position.create(lineIndex + linesOffset, endColumn), | ||
}, | ||
offsets: parsedText.offsets, | ||
message, | ||
...(deprecation ? { tags: [DiagnosticTag.Deprecated] } : {}), | ||
}; | ||
return error; | ||
} | ||
function detectNonDeclaredFunction( | ||
parsedFunction: ParsedFunction, | ||
functionsSchema: Record<string, Neo4jFunction>, | ||
procedureSchema: Record<string, Neo4jProcedure>, | ||
): SyntaxDiagnostic | undefined { | ||
const lowercaseFunctionName = parsedFunction.parsedName.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 = | ||
@@ -74,43 +119,85 @@ 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.parsedName], | ||
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 generateFunctionNotFoundWarning(parsedFunction); | ||
} | ||
} | ||
function generateFunctionNotFoundWarning( | ||
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; | ||
return generateSyntaxDiagnostic( | ||
parsedFunction.rawText, | ||
parsedFunction, | ||
DiagnosticSeverity.Error, | ||
`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`, | ||
); | ||
} | ||
const warning: SyntaxDiagnostic = { | ||
severity: DiagnosticSeverity.Warning, | ||
range: { | ||
start: Position.create(lineIndex, startColumn), | ||
end: Position.create(lineIndex + linesOffset, endColumn), | ||
}, | ||
offsets: parsedFunction.offsets, | ||
message: `Function ${parsedFunction.parsedName} 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`, | ||
}; | ||
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?`, | ||
); | ||
} | ||
return warning; | ||
function generateProcedureNotFoundError( | ||
parsedProcedure: ParsedProcedure, | ||
): SyntaxDiagnostic { | ||
return generateSyntaxDiagnostic( | ||
parsedProcedure.rawText, | ||
parsedProcedure, | ||
DiagnosticSeverity.Error, | ||
`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`, | ||
); | ||
} | ||
function generateProcedureDeprecatedWarning( | ||
parsedProcedure: ParsedProcedure, | ||
deprecatedBy: string | undefined, | ||
): SyntaxDiagnostic { | ||
const procDeprecatedWarning = `Procedure ${parsedProcedure.name} is deprecated.`; | ||
return generateSyntaxDiagnostic( | ||
parsedProcedure.rawText, | ||
parsedProcedure, | ||
DiagnosticSeverity.Warning, | ||
deprecatedBy | ||
? procDeprecatedWarning + ` Alternative: ${deprecatedBy}` | ||
: procDeprecatedWarning, | ||
true, | ||
); | ||
} | ||
function generateFunctionDeprecatedWarning( | ||
parsedFunction: ParsedFunction, | ||
deprecatedBy: string | undefined, | ||
): SyntaxDiagnostic { | ||
const funcDeprecatedWarning = `Function ${parsedFunction.name} is deprecated.`; | ||
return generateSyntaxDiagnostic( | ||
parsedFunction.rawText, | ||
parsedFunction, | ||
DiagnosticSeverity.Warning, | ||
deprecatedBy | ||
? funcDeprecatedWarning + ` Alternative: ${deprecatedBy}` | ||
: funcDeprecatedWarning, | ||
true, | ||
); | ||
} | ||
function warnOnUndeclaredLabels( | ||
@@ -141,89 +228,51 @@ parsingResult: ParsedStatement, | ||
export function sortByPositionAndMessage( | ||
a: SyntaxDiagnostic, | ||
b: SyntaxDiagnostic, | ||
) { | ||
const lineDiff = a.range.start.line - b.range.start.line; | ||
if (lineDiff !== 0) return lineDiff; | ||
const columnDiff = a.range.start.character - b.range.start.character; | ||
if (columnDiff !== 0) return columnDiff; | ||
return a.message > b.message ? 1 : -1; | ||
} | ||
type FixSemanticPositionsArgs = { | ||
semanticElements: SemanticAnalysisElement[]; | ||
semanticDiagnostics: SyntaxDiagnostic[]; | ||
parseResult: ParsedStatement; | ||
}; | ||
function fixSemanticAnalysisPositions({ | ||
semanticElements, | ||
function fixOffsets({ | ||
semanticDiagnostics, | ||
parseResult, | ||
}: FixSemanticPositionsArgs): SyntaxDiagnostic[] { | ||
const cmd = parseResult.command; | ||
return semanticElements.map((e) => { | ||
let token: Token | undefined = undefined; | ||
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; | ||
const start = Position.create( | ||
e.position.line - 1 + cmd.start.line - 1, | ||
e.position.column - 1 + (e.position.line === 1 ? cmd.start.column : 0), | ||
range.start.line + lineAdjust, | ||
range.start.character + colAdjust, | ||
); | ||
const startOffset = e.position.offset + cmd.start.start; | ||
const toExplore: ParseTree[] = [parseResult.ctx]; | ||
const end = Position.create( | ||
range.end.line + lineAdjust, | ||
range.end.character + colAdjust, | ||
); | ||
while (toExplore.length > 0) { | ||
const current: ParseTree = toExplore.pop(); | ||
const adjustedRange = { start, end }; | ||
const adjustedOffset = { | ||
start: offsets.start + offsetAdjust, | ||
end: offsets.end + offsetAdjust, | ||
}; | ||
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, | ||
}, | ||
}; | ||
} | ||
return { ...e, range: adjustedRange, offsets: adjustedOffset }; | ||
}); | ||
} | ||
export function sortByPositionAndMessage( | ||
a: SyntaxDiagnostic, | ||
b: SyntaxDiagnostic, | ||
) { | ||
const lineDiff = a.range.start.line - b.range.start.line; | ||
if (lineDiff !== 0) return lineDiff; | ||
const columnDiff = a.range.start.character - b.range.start.character; | ||
if (columnDiff !== 0) return columnDiff; | ||
return a.message > b.message ? 1 : -1; | ||
} | ||
export function lintCypherQuery( | ||
@@ -233,69 +282,95 @@ query: string, | ||
): SyntaxDiagnostic[] { | ||
const syntaxErrors = validateSyntax(query, dbSchema); | ||
if (syntaxErrors.length > 0) { | ||
return syntaxErrors; | ||
if (query.length > 0) { | ||
const cachedParse = parserWrapper.parse(query); | ||
const statements = cachedParse.statementsParsing; | ||
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, | ||
current.cypherVersion, | ||
); | ||
// 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); | ||
} | ||
// There could be console command errors | ||
return current.syntaxErrors; | ||
}); | ||
return errors; | ||
} | ||
const semanticErrors = validateSemantics(query, dbSchema); | ||
return semanticErrors; | ||
return []; | ||
} | ||
export function validateSyntax( | ||
query: string, | ||
function warningOnDeprecatedProcedure( | ||
parsingResult: ParsedStatement, | ||
dbSchema: DbSchema, | ||
): SyntaxDiagnostic[] { | ||
if (query.length === 0) { | ||
return []; | ||
const warnings: SyntaxDiagnostic[] = []; | ||
if (dbSchema.procedures) { | ||
const proceduresInQuery = parsingResult.collectedProcedures; | ||
proceduresInQuery.forEach((parsedProcedure) => { | ||
const proc = dbSchema.procedures?.[parsedProcedure.name]; | ||
const procedureDeprecated = proc?.option?.deprecated; | ||
const deprecatedBy = proc?.deprecatedBy; | ||
if (deprecatedBy) | ||
if (procedureDeprecated) { | ||
warnings.push( | ||
generateProcedureDeprecatedWarning(parsedProcedure, deprecatedBy), | ||
); | ||
} | ||
}); | ||
} | ||
const statements = parserWrapper.parse(query); | ||
const result = statements.statementsParsing.flatMap((statement) => { | ||
const diagnostics = statement.diagnostics; | ||
const labelWarnings = warnOnUndeclaredLabels(statement, dbSchema); | ||
const functionWarnings = warnOnUndeclaredFunctions(statement, dbSchema); | ||
return diagnostics | ||
.concat(labelWarnings, functionWarnings) | ||
.sort(sortByPositionAndMessage); | ||
}); | ||
return result; | ||
return warnings; | ||
} | ||
/** | ||
* Assumes the provided query has no parse errors | ||
*/ | ||
export function validateSemantics( | ||
query: string, | ||
function warningOnDeprecatedFunction( | ||
parsingResult: ParsedStatement, | ||
dbSchema: DbSchema, | ||
): SyntaxDiagnostic[] { | ||
if (query.length > 0) { | ||
const cachedParse = parserWrapper.parse(query); | ||
const statements = cachedParse.statementsParsing; | ||
const semanticErrors = statements.flatMap((current) => { | ||
if (current.diagnostics.length === 0) { | ||
const cmd = current.command; | ||
if (cmd.type === 'cypher' && cmd.statement.length > 0) { | ||
const { notifications, errors } = wrappedSemanticAnalysis( | ||
cmd.statement, | ||
dbSchema, | ||
); | ||
const elements = notifications.concat(errors); | ||
const result = fixSemanticAnalysisPositions({ | ||
semanticElements: elements, | ||
parseResult: current, | ||
}).sort(sortByPositionAndMessage); | ||
return result; | ||
} | ||
const warnings: SyntaxDiagnostic[] = []; | ||
if (dbSchema.functions) { | ||
const functionsInQuery = parsingResult.collectedFunctions; | ||
functionsInQuery.forEach((parsedFunction) => { | ||
const fn = dbSchema.functions?.[parsedFunction.name]; | ||
const functionDeprecated = fn?.isDeprecated; | ||
const deprecatedBy = fn?.deprecatedBy; | ||
if (functionDeprecated) { | ||
warnings.push( | ||
generateFunctionDeprecatedWarning(parsedFunction, deprecatedBy), | ||
); | ||
} | ||
return []; | ||
}); | ||
return semanticErrors; | ||
} | ||
return []; | ||
return warnings; | ||
} | ||
function warnOnUndeclaredFunctions( | ||
function errorOnUndeclaredFunctions( | ||
parsingResult: ParsedStatement, | ||
@@ -313,2 +388,3 @@ dbSchema: DbSchema, | ||
dbSchema.functions, | ||
dbSchema.procedures, | ||
); | ||
@@ -322,1 +398,31 @@ | ||
} | ||
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) { | ||
const existsAsFunction = functionExists( | ||
parsedProcedure, | ||
dbSchema.functions, | ||
); | ||
if (existsAsFunction) { | ||
errors.push(generateFunctionUsedAsProcedureError(parsedProcedure)); | ||
} else { | ||
errors.push(generateProcedureNotFoundError(parsedProcedure)); | ||
} | ||
} | ||
}); | ||
} | ||
return errors; | ||
} |
@@ -8,16 +8,9 @@ import { | ||
import type { ParserRuleContext } from 'antlr4-c3'; | ||
import { | ||
Diagnostic, | ||
DiagnosticSeverity, | ||
Position, | ||
} from 'vscode-languageserver-types'; | ||
import { CypherLexer } from '..'; | ||
import { DiagnosticSeverity, Position } from 'vscode-languageserver-types'; | ||
import CypherLexer from '../generated-parser/CypherCmdLexer'; | ||
import CypherParser from '../generated-parser/CypherCmdParser'; | ||
import { isCommentOpener } from '../helpers'; | ||
import { completionCoreErrormessage } from './completionCoreErrors'; | ||
import { SyntaxDiagnostic } from './syntaxValidation'; | ||
export type SyntaxDiagnostic = Diagnostic & { | ||
offsets: { start: number; end: number }; | ||
}; | ||
export class SyntaxErrorsListener implements ANTLRErrorListener<CommonToken> { | ||
@@ -24,0 +17,0 @@ errors: SyntaxDiagnostic[]; |
@@ -7,4 +7,16 @@ import { CompletionItemKind } from 'vscode-languageserver-types'; | ||
const dbSchema: DbSchema = { | ||
databaseNames: ['db1', 'db2', 'movies'], | ||
aliasNames: ['myMovies', 'scoped.alias', 'a.b.c.d'], | ||
databaseNames: [ | ||
'db1', | ||
'db2', | ||
'movies', | ||
'financial reports', | ||
'neo4j_server', | ||
], | ||
aliasNames: [ | ||
'myMovies', | ||
'scoped.alias', | ||
'a.b.c.d', | ||
'panama papers1', | ||
'office-db', | ||
], | ||
parameters: { | ||
@@ -16,5 +28,63 @@ param1: 'something', | ||
}, | ||
'param 4': 'something else', | ||
}, | ||
}; | ||
test('Correctly completes database names/aliases with special symbols using backticks for SHOW DATABASE', () => { | ||
const query = 'SHOW DATABASE '; | ||
testCompletions({ | ||
query, | ||
dbSchema, | ||
expected: [ | ||
{ | ||
label: 'financial reports', | ||
kind: CompletionItemKind.Value, | ||
insertText: '`financial reports`', | ||
}, | ||
{ | ||
label: 'panama papers1', | ||
kind: CompletionItemKind.Value, | ||
insertText: '`panama papers1`', | ||
}, | ||
{ | ||
label: 'office-db', | ||
kind: CompletionItemKind.Value, | ||
insertText: '`office-db`', | ||
}, | ||
{ | ||
label: '$param 4', | ||
kind: CompletionItemKind.Variable, | ||
insertText: '$`param 4`', | ||
}, | ||
], | ||
}); | ||
}); | ||
test('Correctly completes aliases with special symbols using backticks for SHOW ALIAS', () => { | ||
const query = 'SHOW ALIAS '; | ||
testCompletions({ | ||
query, | ||
dbSchema, | ||
expected: [ | ||
{ | ||
label: 'panama papers1', | ||
kind: CompletionItemKind.Value, | ||
insertText: '`panama papers1`', | ||
}, | ||
{ | ||
label: 'office-db', | ||
kind: CompletionItemKind.Value, | ||
insertText: '`office-db`', | ||
}, | ||
{ | ||
label: '$param 4', | ||
kind: CompletionItemKind.Variable, | ||
insertText: '$`param 4`', | ||
}, | ||
], | ||
}); | ||
}); | ||
test('Correctly completes database names and aliases in SHOW DATABASE', () => { | ||
@@ -32,2 +102,3 @@ const query = 'SHOW DATABASE '; | ||
{ label: 'movies', kind: CompletionItemKind.Value }, | ||
{ label: 'neo4j_server', kind: CompletionItemKind.Value }, | ||
{ label: 'myMovies', kind: CompletionItemKind.Value }, | ||
@@ -73,3 +144,3 @@ { label: 'scoped.alias', kind: CompletionItemKind.Value }, | ||
test("Doesn't suggest existing database names or aliases when createing database", () => { | ||
test("Doesn't suggest existing database names or aliases when creating database", () => { | ||
const query = 'CREATE DATABASE '; | ||
@@ -76,0 +147,0 @@ |
@@ -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 '; |
@@ -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, |
@@ -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 }, | ||
], | ||
}); | ||
@@ -43,2 +46,23 @@ }); | ||
test('Correctly completes even with preparser options', () => { | ||
const query = 'CYPHER 25 runtime=pipelined MATCH (n:P'; | ||
testCompletions({ | ||
query, | ||
dbSchema: { labels: ['Cat', 'Person', 'Dog'] }, | ||
expected: [{ label: 'Person', kind: CompletionItemKind.TypeParameter }], | ||
}); | ||
const query2 = 'CYPHER 25 runtime=pipelined MATCH (n:Person)-[:W'; | ||
testCompletions({ | ||
query: query2, | ||
dbSchema: { | ||
labels: ['Cat', 'Person', 'Dog'], | ||
relationshipTypes: ['WALKS', 'OWNS'], | ||
}, | ||
expected: [{ label: 'WALKS', kind: CompletionItemKind.TypeParameter }], | ||
}); | ||
}); | ||
test("Doesn't complete label before : is entered", () => { | ||
@@ -89,2 +113,125 @@ const query = 'MATCH (n'; | ||
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', () => { | ||
@@ -408,2 +555,52 @@ const query = 'MATCH (n:); MATCH (m:)'; | ||
}); | ||
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, | ||
}, | ||
], | ||
}); | ||
}); | ||
}); | ||
@@ -410,0 +607,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, | ||
}, | ||
], | ||
}); | ||
}); | ||
}); |
@@ -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 () { |
@@ -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', | ||
); | ||
@@ -249,2 +327,21 @@ expectErrorMessage(':clea', 'Unexpected token. Did you mean clear?'); | ||
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', () => { | ||
@@ -459,2 +556,105 @@ const arrowCompletions = autocomplete(':param foo => ', { | ||
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', () => { | ||
@@ -461,0 +661,0 @@ beforeAll(() => { |
import { applySyntaxColouring } from '../../syntaxColouring/syntaxColouring'; | ||
describe('Preparser syntax colouring', () => { | ||
test('Correctly colours cypher versions', () => { | ||
const query = 'CYPHER 25 MATCH (m) RETURN m'; | ||
expect(applySyntaxColouring(query)).toEqual([ | ||
{ | ||
bracketInfo: undefined, | ||
length: 6, | ||
position: { | ||
line: 0, | ||
startCharacter: 0, | ||
startOffset: 0, | ||
}, | ||
token: 'CYPHER', | ||
tokenType: 'keyword', | ||
}, | ||
{ | ||
bracketInfo: undefined, | ||
length: 2, | ||
position: { | ||
line: 0, | ||
startCharacter: 7, | ||
startOffset: 7, | ||
}, | ||
token: '25', | ||
tokenType: 'numberLiteral', | ||
}, | ||
{ | ||
bracketInfo: undefined, | ||
length: 5, | ||
position: { | ||
line: 0, | ||
startCharacter: 10, | ||
startOffset: 10, | ||
}, | ||
token: 'MATCH', | ||
tokenType: 'keyword', | ||
}, | ||
{ | ||
bracketInfo: { | ||
bracketLevel: 0, | ||
bracketType: 'parenthesis', | ||
}, | ||
length: 1, | ||
position: { | ||
line: 0, | ||
startCharacter: 16, | ||
startOffset: 16, | ||
}, | ||
token: '(', | ||
tokenType: 'bracket', | ||
}, | ||
{ | ||
bracketInfo: undefined, | ||
length: 1, | ||
position: { | ||
line: 0, | ||
startCharacter: 17, | ||
startOffset: 17, | ||
}, | ||
token: 'm', | ||
tokenType: 'variable', | ||
}, | ||
{ | ||
bracketInfo: { | ||
bracketLevel: 0, | ||
bracketType: 'parenthesis', | ||
}, | ||
length: 1, | ||
position: { | ||
line: 0, | ||
startCharacter: 18, | ||
startOffset: 18, | ||
}, | ||
token: ')', | ||
tokenType: 'bracket', | ||
}, | ||
{ | ||
bracketInfo: undefined, | ||
length: 6, | ||
position: { | ||
line: 0, | ||
startCharacter: 20, | ||
startOffset: 20, | ||
}, | ||
token: 'RETURN', | ||
tokenType: 'keyword', | ||
}, | ||
{ | ||
bracketInfo: undefined, | ||
length: 1, | ||
position: { | ||
line: 0, | ||
startCharacter: 27, | ||
startOffset: 27, | ||
}, | ||
token: 'm', | ||
tokenType: 'variable', | ||
}, | ||
]); | ||
}); | ||
test('Correctly colours CYPHER <setting> = <setting-value> options', () => { | ||
const query = 'CYPHER runtime = slotted RETURN ""'; | ||
expect(applySyntaxColouring(query)).toEqual([ | ||
{ | ||
bracketInfo: undefined, | ||
length: 6, | ||
position: { | ||
line: 0, | ||
startCharacter: 0, | ||
startOffset: 0, | ||
}, | ||
token: 'CYPHER', | ||
tokenType: 'keyword', | ||
}, | ||
{ | ||
bracketInfo: undefined, | ||
length: 7, | ||
position: { | ||
line: 0, | ||
startCharacter: 7, | ||
startOffset: 7, | ||
}, | ||
token: 'runtime', | ||
tokenType: 'setting', | ||
}, | ||
{ | ||
bracketInfo: undefined, | ||
length: 1, | ||
position: { | ||
line: 0, | ||
startCharacter: 15, | ||
startOffset: 15, | ||
}, | ||
token: '=', | ||
tokenType: 'operator', | ||
}, | ||
{ | ||
bracketInfo: undefined, | ||
length: 7, | ||
position: { | ||
line: 0, | ||
startCharacter: 17, | ||
startOffset: 17, | ||
}, | ||
token: 'slotted', | ||
tokenType: 'settingValue', | ||
}, | ||
{ | ||
bracketInfo: undefined, | ||
length: 6, | ||
position: { | ||
line: 0, | ||
startCharacter: 25, | ||
startOffset: 25, | ||
}, | ||
token: 'RETURN', | ||
tokenType: 'keyword', | ||
}, | ||
{ | ||
bracketInfo: undefined, | ||
length: 2, | ||
position: { | ||
line: 0, | ||
startCharacter: 32, | ||
startOffset: 32, | ||
}, | ||
token: '""', | ||
tokenType: 'stringLiteral', | ||
}, | ||
]); | ||
}); | ||
test('Correctly colours PROFILE', () => { | ||
@@ -367,2 +538,242 @@ const query = 'PROFILE MATCH (n) RETURN n'; | ||
}); | ||
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', | ||
}, | ||
]); | ||
}); | ||
}); |
@@ -6,2 +6,213 @@ import { _internalFeatureFlags } from '../../featureFlags'; | ||
describe('Semantic validation spec', () => { | ||
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 +223,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: { | ||
@@ -105,2 +317,227 @@ end: 5, | ||
test('Semantic errors work using empty preparser options. Like CYPHER <rest of query', () => { | ||
const query = `CYPHER MATCH (n); | ||
CYPHER 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: 16, | ||
start: 7, | ||
}, | ||
range: { | ||
end: { | ||
character: 16, | ||
line: 0, | ||
}, | ||
start: { | ||
character: 7, | ||
line: 0, | ||
}, | ||
}, | ||
severity: 1, | ||
}, | ||
{ | ||
message: 'Variable `n` not defined', | ||
offsets: { | ||
end: 62, | ||
start: 61, | ||
}, | ||
range: { | ||
end: { | ||
character: 44, | ||
line: 1, | ||
}, | ||
start: { | ||
character: 43, | ||
line: 1, | ||
}, | ||
}, | ||
severity: 1, | ||
}, | ||
]); | ||
}); | ||
test('Semantic errors work using both version syntax and key-value syntax for preparser options. Like CYPHER <version> <option> = <value>', () => { | ||
const query = `CYPHER 25 runtime = pipelined timeout = 1000 MATCH (n); | ||
CYPHER 5 timeout= 555 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: 54, | ||
start: 45, | ||
}, | ||
range: { | ||
end: { | ||
character: 54, | ||
line: 0, | ||
}, | ||
start: { | ||
character: 45, | ||
line: 0, | ||
}, | ||
}, | ||
severity: 1, | ||
}, | ||
{ | ||
message: 'Variable `n` not defined', | ||
offsets: { | ||
end: 115, | ||
start: 114, | ||
}, | ||
range: { | ||
end: { | ||
character: 59, | ||
line: 1, | ||
}, | ||
start: { | ||
character: 58, | ||
line: 1, | ||
}, | ||
}, | ||
severity: 1, | ||
}, | ||
]); | ||
}); | ||
test('Semantic errors work with preparser option CYPHER <option> = <value>', () => { | ||
const query = `CYPHER runtime = pipelined timeout = 1000 MATCH (n); | ||
CYPHER planner=cost 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: 53, | ||
start: 44, | ||
}, | ||
range: { | ||
end: { | ||
character: 53, | ||
line: 0, | ||
}, | ||
start: { | ||
character: 44, | ||
line: 0, | ||
}, | ||
}, | ||
severity: 1, | ||
}, | ||
{ | ||
message: 'Variable `n` not defined', | ||
offsets: { | ||
end: 112, | ||
start: 111, | ||
}, | ||
range: { | ||
end: { | ||
character: 57, | ||
line: 1, | ||
}, | ||
start: { | ||
character: 56, | ||
line: 1, | ||
}, | ||
}, | ||
severity: 1, | ||
}, | ||
]); | ||
}); | ||
test('Semantic errors work with preparser option CYPHER <version>', () => { | ||
const query = `CYPHER 25 MATCH (n); | ||
CYPHER 5 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: 19, | ||
start: 10, | ||
}, | ||
range: { | ||
end: { | ||
character: 19, | ||
line: 0, | ||
}, | ||
start: { | ||
character: 10, | ||
line: 0, | ||
}, | ||
}, | ||
severity: 1, | ||
}, | ||
{ | ||
message: 'Variable `n` not defined', | ||
offsets: { | ||
end: 67, | ||
start: 66, | ||
}, | ||
range: { | ||
end: { | ||
character: 46, | ||
line: 1, | ||
}, | ||
start: { | ||
character: 45, | ||
line: 1, | ||
}, | ||
}, | ||
severity: 1, | ||
}, | ||
]); | ||
}); | ||
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', () => { | ||
@@ -119,2 +556,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)', | ||
@@ -141,3 +597,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`; | ||
@@ -149,3 +605,3 @@ expect(getDiagnosticsForQuery({ query })).toEqual([ | ||
offsets: { | ||
end: 53, | ||
end: 56, | ||
start: 0, | ||
@@ -155,3 +611,3 @@ }, | ||
end: { | ||
character: 53, | ||
character: 56, | ||
line: 0, | ||
@@ -169,12 +625,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, | ||
@@ -189,12 +645,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, | ||
@@ -209,6 +665,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`; | ||
@@ -220,3 +676,3 @@ | ||
offsets: { | ||
end: 139, | ||
end: 145, | ||
start: 0, | ||
@@ -239,4 +695,4 @@ }, | ||
offsets: { | ||
end: 139, | ||
start: 79, | ||
end: 145, | ||
start: 82, | ||
}, | ||
@@ -260,3 +716,3 @@ range: { | ||
const query = `WITH 1 AS i | ||
CALL { | ||
CALL () { | ||
WITH 2 AS i | ||
@@ -275,4 +731,4 @@ RETURN * | ||
offsets: { | ||
end: 44, | ||
start: 43, | ||
end: 47, | ||
start: 46, | ||
}, | ||
@@ -294,4 +750,4 @@ range: { | ||
offsets: { | ||
end: 61, | ||
start: 53, | ||
end: 64, | ||
start: 56, | ||
}, | ||
@@ -311,25 +767,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, | ||
}, | ||
@@ -351,4 +789,4 @@ range: { | ||
offsets: { | ||
end: 114, | ||
start: 106, | ||
end: 117, | ||
start: 109, | ||
}, | ||
@@ -367,20 +805,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, | ||
}, | ||
]); | ||
@@ -390,3 +810,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'; | ||
@@ -396,14 +816,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, | ||
@@ -420,3 +840,3 @@ }, | ||
WITH 1 AS a | ||
CALL { | ||
CALL () { | ||
USE x | ||
@@ -433,3 +853,3 @@ RETURN 2 AS b | ||
WITH 1 AS a | ||
CALL { | ||
CALL () { | ||
USE other | ||
@@ -445,4 +865,4 @@ RETURN 2 AS b | ||
offsets: { | ||
end: 88, | ||
start: 55, | ||
end: 91, | ||
start: 58, | ||
}, | ||
@@ -668,4 +1088,3 @@ range: { | ||
UNWIND [0, 1, 2] AS x | ||
CALL { | ||
WITH x | ||
CALL (x) { | ||
RETURN x * 10 AS y | ||
@@ -681,4 +1100,4 @@ } | ||
offsets: { | ||
end: 137, | ||
start: 136, | ||
end: 120, | ||
start: 119, | ||
}, | ||
@@ -688,7 +1107,7 @@ range: { | ||
character: 32, | ||
line: 5, | ||
line: 4, | ||
}, | ||
start: { | ||
character: 31, | ||
line: 5, | ||
line: 4, | ||
}, | ||
@@ -774,14 +1193,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, | ||
@@ -919,4 +1339,3 @@ }, | ||
{ | ||
message: | ||
'Type mismatch: p defined with conflicting type List<T> (expected Path)', | ||
message: 'Variable `p` already declared', | ||
offsets: { | ||
@@ -1205,3 +1624,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: { | ||
@@ -1454,6 +1873,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, | ||
}), | ||
@@ -1463,147 +1885,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, | ||
@@ -1614,6 +1906,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, | ||
@@ -1624,18 +1916,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, | ||
}, | ||
@@ -1645,52 +1937,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, | ||
}), | ||
@@ -1700,15 +1952,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, | ||
}, | ||
@@ -1715,0 +1967,0 @@ }, |
@@ -1,2 +0,1 @@ | ||
import { testData } from '../testData'; | ||
import { getDiagnosticsForQuery } from './helpers'; | ||
@@ -14,3 +13,4 @@ | ||
}, | ||
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: { | ||
@@ -36,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: { | ||
@@ -41,4 +43,2 @@ end: 3, | ||
}, | ||
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', | ||
range: { | ||
@@ -64,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: { | ||
@@ -69,3 +71,2 @@ end: 21, | ||
}, | ||
message: 'Unexpected token. Did you mean WHERE?', | ||
range: { | ||
@@ -91,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: { | ||
@@ -96,3 +99,2 @@ end: 21, | ||
}, | ||
message: 'Unexpected token. Did you mean WHERE?', | ||
range: { | ||
@@ -122,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: { | ||
@@ -127,4 +131,2 @@ end: 114, | ||
}, | ||
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", | ||
range: { | ||
@@ -145,23 +147,29 @@ end: { | ||
test('Misspelt keyword in the middle of the statement', () => { | ||
const query = "MATCH (n:Person) WERE n.name = 'foo'"; | ||
test('Syntax validation warns on missing label when database can be contacted', () => { | ||
const query = `MATCH (n: Person) RETURN n`; | ||
expect(getDiagnosticsForQuery({ query })).toEqual([ | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { labels: ['Dog', 'Cat'], relationshipTypes: ['Person'] }, | ||
}), | ||
).toEqual([ | ||
{ | ||
offsets: { | ||
end: 21, | ||
start: 17, | ||
end: 16, | ||
start: 10, | ||
}, | ||
message: 'Unexpected token. Did you mean WHERE?', | ||
message: | ||
"Label Person 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", | ||
range: { | ||
end: { | ||
character: 21, | ||
character: 16, | ||
line: 0, | ||
}, | ||
start: { | ||
character: 17, | ||
character: 10, | ||
line: 0, | ||
}, | ||
}, | ||
severity: 1, | ||
severity: 2, | ||
}, | ||
@@ -171,21 +179,38 @@ ]); | ||
test('Syntax validation warns on missing label when database can be contacted', () => { | ||
const query = `MATCH (n: Person) RETURN n`; | ||
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: ['Dog', 'Cat'], relationshipTypes: ['Person'] }, | ||
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: 16, | ||
end: 19, | ||
start: 10, | ||
}, | ||
message: | ||
"Label Person 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", | ||
range: { | ||
end: { | ||
character: 16, | ||
character: 19, | ||
line: 0, | ||
@@ -200,2 +225,21 @@ }, | ||
}, | ||
{ | ||
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, | ||
}, | ||
]); | ||
@@ -408,3 +452,4 @@ }); | ||
{ | ||
message: 'Expected a node label / rel type', | ||
message: | ||
"Invalid input ')': expected a node label/relationship type name, '$', '%' or '('", | ||
offsets: { | ||
@@ -502,2 +547,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: { | ||
@@ -507,4 +554,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: { | ||
@@ -544,3 +589,4 @@ end: { | ||
{ | ||
message: 'Unfinished string literal', | ||
message: | ||
'Failed to parse string literal. The query must contain an even number of non-escaped quotes.', | ||
offsets: { | ||
@@ -565,3 +611,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 | ||
@@ -577,3 +623,4 @@ foo | ||
{ | ||
message: 'Unfinished string literal', | ||
message: | ||
'Failed to parse string literal. The query must contain an even number of non-escaped quotes.', | ||
offsets: { | ||
@@ -598,7 +645,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`; | ||
@@ -611,14 +657,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, | ||
@@ -632,2 +679,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', () => { | ||
@@ -644,3 +726,4 @@ const query = `/* something | ||
{ | ||
message: 'Unfinished comment', | ||
message: | ||
'Failed to parse comment. A comment starting on `/*` must have a closing `*/`.', | ||
offsets: { | ||
@@ -674,3 +757,3 @@ end: 34, | ||
{ | ||
message: "Expected any of '*', DISTINCT or an expression", | ||
message: "Invalid input '}': expected an expression, '*' or 'DISTINCT'", | ||
offsets: { | ||
@@ -692,24 +775,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`; | ||
@@ -723,3 +790,3 @@ | ||
{ | ||
message: "Expected '{' or a procedure name", | ||
message: "Expected any of '{', '(' or a procedure name", | ||
offsets: { | ||
@@ -753,3 +820,3 @@ end: 6, | ||
{ | ||
message: "Expected '}' or an identifier", | ||
message: "Invalid input '[': expected an identifier or '}'", | ||
offsets: { | ||
@@ -783,3 +850,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: { | ||
@@ -813,3 +880,3 @@ end: 14, | ||
{ | ||
message: 'Expected an identifier', | ||
message: "Invalid input ''bar'': expected an identifier", | ||
offsets: { | ||
@@ -844,3 +911,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: { | ||
@@ -874,3 +942,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: { | ||
@@ -904,3 +972,3 @@ end: 27, | ||
{ | ||
message: "Expected '}' or an unsigned integer", | ||
message: `Invalid input '"foo"': expected '}' or an integer value`, | ||
offsets: { | ||
@@ -934,3 +1002,4 @@ end: 40, | ||
{ | ||
message: 'Expected a node label / rel type', | ||
message: | ||
"Invalid input ''Person'': expected a node label/relationship type name, '$', '%' or '('", | ||
offsets: { | ||
@@ -964,3 +1033,3 @@ end: 18, | ||
{ | ||
message: 'Expected a node label / rel type', | ||
message: `Invalid input ''Person'': expected an identifier, '$', '%' or '('`, | ||
offsets: { | ||
@@ -997,3 +1066,3 @@ end: 20, | ||
end: 1, | ||
start: 1, | ||
start: 0, | ||
}, | ||
@@ -1006,3 +1075,3 @@ range: { | ||
start: { | ||
character: 1, | ||
character: 0, | ||
line: 0, | ||
@@ -1016,40 +1085,21 @@ }, | ||
test('Syntax validation warns on missing function when database can be contacted', () => { | ||
const query = `RETURN dontpanic("marvin")`; | ||
test.each([ | ||
`MATCH (n:Test1) RETURN n.profile`, | ||
`MATCH (n:MyLabel) RETURN n.CYPHER`, | ||
`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([]); | ||
}, | ||
); | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([ | ||
{ | ||
offsets: { | ||
end: 16, | ||
start: 7, | ||
}, | ||
message: | ||
"Function dontpanic 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", | ||
range: { | ||
end: { | ||
character: 16, | ||
line: 0, | ||
}, | ||
start: { | ||
character: 7, | ||
line: 0, | ||
}, | ||
}, | ||
severity: 2, | ||
}, | ||
]); | ||
}); | ||
test('Existing relationship types are not incorrectly reported inside node predicates', () => { | ||
const query = `MATCH ()-[]->*(n WHERE COUNT{ (n)-[:R]->()} = 0) RETURN n`; | ||
test('Syntax validation does not warn on existing function when database can be contacted', () => { | ||
const query = `RETURN abs(4)`; | ||
expect( | ||
@@ -1059,5 +1109,4 @@ getDiagnosticsForQuery({ | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
labels: [], | ||
relationshipTypes: ['R'], | ||
}, | ||
@@ -1068,4 +1117,4 @@ }), | ||
test('Syntax validation warns on missing function with namespace when database can be contacted', () => { | ||
const query = `RETURN test.dontpanic("marvin")`; | ||
test('Existing node labels are not incorrectly reported inside relationship predicates', () => { | ||
const query = `MATCH ()-[r WHERE COUNT{ (n:A)-[]->()} = 0]->() RETURN r`; | ||
@@ -1076,399 +1125,8 @@ expect( | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
labels: ['A'], | ||
relationshipTypes: [], | ||
}, | ||
}), | ||
).toEqual([ | ||
{ | ||
offsets: { | ||
end: 21, | ||
start: 7, | ||
}, | ||
message: | ||
"Function test.dontpanic 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", | ||
range: { | ||
end: { | ||
character: 21, | ||
line: 0, | ||
}, | ||
start: { | ||
character: 7, | ||
line: 0, | ||
}, | ||
}, | ||
severity: 2, | ||
}, | ||
]); | ||
}); | ||
test('Syntax validation does not warn on existing function with namespace when database can be contacted', () => { | ||
const query = `RETURN apoc.text.decapitalize("Marvin")`; | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([]); | ||
}); | ||
test('Syntax validation warns on missing function with namespace split in multiple lines when database can be contacted', () => { | ||
const query = `RETURN test. | ||
dontpanic | ||
("marvin") | ||
`; | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([ | ||
{ | ||
offsets: { | ||
end: 26, | ||
start: 7, | ||
}, | ||
message: | ||
"Function test.dontpanic 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", | ||
range: { | ||
end: { | ||
character: 13, | ||
line: 1, | ||
}, | ||
start: { | ||
character: 7, | ||
line: 0, | ||
}, | ||
}, | ||
severity: 2, | ||
}, | ||
]); | ||
}); | ||
test('Syntax validation does not warn on existing function with namespace split in multiple lines when database can be contacted', () => { | ||
const query = ` | ||
RETURN apoc.text. | ||
capitalize | ||
("marvin") | ||
`; | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([]); | ||
}); | ||
test('Syntax validation is case insensitive on built-in functions', () => { | ||
const query = ` | ||
RETURN aBS(123) | ||
`; | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([]); | ||
}); | ||
test('Syntax validation is not case insensitive on user defined functions', () => { | ||
const query = ` | ||
RETURN apoc.text.capiTALize("marvin") | ||
`; | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([ | ||
{ | ||
message: | ||
"Function apoc.text.capiTALize 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: 32, | ||
start: 12, | ||
}, | ||
range: { | ||
end: { | ||
character: 31, | ||
line: 1, | ||
}, | ||
start: { | ||
character: 11, | ||
line: 1, | ||
}, | ||
}, | ||
severity: 2, | ||
}, | ||
]); | ||
}); | ||
test('Syntax validation does not warn on existing function with spaces when database can be contacted', () => { | ||
const query = `RETURN apoc. text. decapitalize ("Marvin")`; | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([]); | ||
}); | ||
test('Syntax validation warns with correct positions with spaces when database can be contacted', () => { | ||
const query = `RETURN apoc. text. dontpanic ("Marvin")`; | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([ | ||
{ | ||
message: | ||
"Function apoc.text.dontpanic 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: 7, | ||
}, | ||
range: { | ||
end: { | ||
character: 31, | ||
line: 0, | ||
}, | ||
start: { | ||
character: 7, | ||
line: 0, | ||
}, | ||
}, | ||
severity: 2, | ||
}, | ||
]); | ||
}); | ||
test('Syntax validation does not warn on existing function with new lines when database can be contacted', () => { | ||
const query = `RETURN apoc. | ||
text. | ||
decapitalize | ||
("Marvin")`; | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([]); | ||
}); | ||
test('Syntax validation warns with correct positions with spaces when database can be contacted', () => { | ||
const query = `RETURN apoc. text. | ||
dontpanic | ||
("Marvin")`; | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([ | ||
{ | ||
message: | ||
"Function apoc.text.dontpanic 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: 36, | ||
start: 7, | ||
}, | ||
range: { | ||
end: { | ||
character: 14, | ||
line: 1, | ||
}, | ||
start: { | ||
character: 7, | ||
line: 0, | ||
}, | ||
}, | ||
severity: 2, | ||
}, | ||
]); | ||
}); | ||
test('Syntax validation should recognize escaped function names', () => { | ||
const query = ` | ||
RETURN \`abs\`(123) | ||
`; | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([]); | ||
}); | ||
test('Syntax validation should fail on escaped function names that do not exist', () => { | ||
const query = ` | ||
RETURN \`dontpanic\`(123) | ||
`; | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([ | ||
{ | ||
message: | ||
"Function dontpanic 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: 23, | ||
start: 12, | ||
}, | ||
range: { | ||
end: { | ||
character: 22, | ||
line: 1, | ||
}, | ||
start: { | ||
character: 11, | ||
line: 1, | ||
}, | ||
}, | ||
severity: 2, | ||
}, | ||
]); | ||
}); | ||
test('Syntax validation should pass on escaped function names with namespaces', () => { | ||
const query = "RETURN `apoc`.text.`capitalize`('Marvin');"; | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([]); | ||
}); | ||
test('Syntax validation should fail on escaped function names with namespaces that do not exist', () => { | ||
const query = "RETURN `apoc`.text.`dontpanic`('Marvin');"; | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([ | ||
{ | ||
message: | ||
"Function apoc.text.dontpanic 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: 30, | ||
start: 7, | ||
}, | ||
range: { | ||
end: { | ||
character: 30, | ||
line: 0, | ||
}, | ||
start: { | ||
character: 7, | ||
line: 0, | ||
}, | ||
}, | ||
severity: 2, | ||
}, | ||
]); | ||
}); | ||
test('Syntax validation should fail if whole name and namespaces are escaped', () => { | ||
const query = "RETURN `apoc.text.capitalize`('Marvin');"; | ||
expect( | ||
getDiagnosticsForQuery({ | ||
query, | ||
dbSchema: { | ||
labels: ['Dog', 'Cat'], | ||
relationshipTypes: ['Person'], | ||
functions: testData.mockSchema.functions, | ||
}, | ||
}), | ||
).toEqual([ | ||
{ | ||
message: | ||
"Function `apoc.text.capitalize` 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: 29, | ||
start: 7, | ||
}, | ||
range: { | ||
end: { | ||
character: 29, | ||
line: 0, | ||
}, | ||
start: { | ||
character: 7, | ||
line: 0, | ||
}, | ||
}, | ||
severity: 2, | ||
}, | ||
]); | ||
}); | ||
}); |
@@ -10,2 +10,4 @@ import { CompletionItem as VSCodeCompletionItem } from 'vscode-languageserver-types'; | ||
export type CypherVersion = 'CYPHER 25' | 'CYPHER 5'; | ||
// we could parse this string for better types in the future | ||
@@ -29,2 +31,3 @@ export type Neo4jStringType = string; | ||
option: { deprecated: boolean } & Record<string, unknown>; | ||
deprecatedBy?: string; | ||
}; | ||
@@ -42,2 +45,3 @@ | ||
isDeprecated: boolean; | ||
deprecatedBy?: string; | ||
}; | ||
@@ -44,0 +48,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 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
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 1 instance in 1 package
142
1
136649477
132130
Updatedantlr4@4.13.2