@@ -48,6 +48,11 @@ export abstract class BasicSQL<
48
48
*/
49
49
protected abstract preferredRules : Set < number > ;
50
50
51
+ /**
52
+ * keywords which can start a single statement
53
+ */
54
+ protected abstract statementStartKeywords : Set < string > ;
55
+
51
56
/**
52
57
* Create a antlr4 Lexer instance.
53
58
* @param input source string
@@ -251,6 +256,63 @@ export abstract class BasicSQL<
251
256
return res ;
252
257
}
253
258
259
+ /**
260
+ * Try to get a small range as possible after syntax error.
261
+ * @param allTokens all tokens from input
262
+ * @param caretTokenIndex tokenIndex of caretPosition
263
+ * @returns { startToken: Token; stopToken: Token }
264
+ */
265
+ private splitInputAfterSyntaxError (
266
+ allTokens : Token [ ] ,
267
+ caretTokenIndex : number
268
+ ) : { startToken : Token ; stopToken : Token } {
269
+ let startToken : Token | null = null ;
270
+ for ( let tokenIndex = caretTokenIndex ; tokenIndex >= 0 ; tokenIndex -- ) {
271
+ const token = allTokens [ tokenIndex ] ;
272
+ // end with semi
273
+ if ( token ?. text === ';' ) {
274
+ startToken = allTokens [ tokenIndex + 1 ] ;
275
+ break ;
276
+ }
277
+ // keywords which can start a single statement
278
+ if (
279
+ Array . from ( this . statementStartKeywords ) . some ( ( item ) => item === token ?. text ) &&
280
+ tokenIndex !== 0
281
+ ) {
282
+ startToken = allTokens [ tokenIndex - 1 ] ;
283
+ break ;
284
+ }
285
+ }
286
+ // If there is no semicolon, start from the first token
287
+ if ( startToken === null ) {
288
+ startToken = allTokens [ 0 ] ;
289
+ }
290
+
291
+ let stopToken : Token | null = null ;
292
+ for ( let tokenIndex = caretTokenIndex ; tokenIndex < allTokens . length ; tokenIndex ++ ) {
293
+ const token = allTokens [ tokenIndex ] ;
294
+ // end with semi
295
+ if ( token ?. text === ';' ) {
296
+ stopToken = token ;
297
+ break ;
298
+ }
299
+ // keywords which can start a single statement
300
+ if (
301
+ Array . from ( this . statementStartKeywords ) . some ( ( item ) => item === token ?. text ) &&
302
+ tokenIndex !== 0
303
+ ) {
304
+ stopToken = allTokens [ tokenIndex - 1 ] ;
305
+ break ;
306
+ }
307
+ }
308
+ // If there is no semicolon, start from the first token
309
+ if ( stopToken === null ) {
310
+ stopToken = allTokens [ allTokens . length - 1 ] ;
311
+ }
312
+
313
+ return { startToken, stopToken } ;
314
+ }
315
+
254
316
/**
255
317
* Get suggestions of syntax and token at caretPosition
256
318
* @param input source string
@@ -282,53 +344,98 @@ export abstract class BasicSQL<
282
344
const statementCount = splitListener . statementsContext ?. length ;
283
345
const statementsContext = splitListener . statementsContext ;
284
346
285
- // If there are multiple statements.
286
- if ( statementCount > 1 ) {
287
- /**
288
- * Find a minimum valid range, reparse the fragment, and provide a new parse tree to C3.
289
- * The boundaries of this range must be statements with no syntax errors.
290
- * This can ensure the stable performance of the C3.
291
- */
292
- let startStatement : ParserRuleContext | null = null ;
293
- let stopStatement : ParserRuleContext | null = null ;
347
+ const { startToken, stopToken } = this . splitInputAfterSyntaxError (
348
+ allTokens ,
349
+ caretTokenIndex
350
+ ) ;
294
351
295
- for ( let index = 0 ; index < statementCount ; index ++ ) {
296
- const ctx = statementsContext [ index ] ;
297
- const isCurrentCtxValid = ! ctx . exception ;
298
- if ( ! isCurrentCtxValid ) continue ;
352
+ let startIndex : number = 0 ;
353
+ let stopIndex : number = 0 ;
299
354
355
+ /**
356
+ * If there is no semi
357
+ * and if there is no keyword which can start a single statement
358
+ * and if there are multiple statements
359
+ */
360
+ if ( startToken . tokenIndex === 1 && stopToken . tokenIndex === allTokens . length - 1 ) {
361
+ if ( statementCount > 1 ) {
300
362
/**
301
- * Ensure that the statementContext before the left boundary
302
- * and the last statementContext on the right boundary are qualified SQL statements.
363
+ * Find a minimum valid range, reparse the fragment, and provide a new parse tree to C3.
364
+ * The boundaries of this range must be statements with no syntax errors.
365
+ * This can ensure the stable performance of the C3.
303
366
*/
304
- const isPrevCtxValid = index === 0 || ! statementsContext [ index - 1 ] ?. exception ;
305
- const isNextCtxValid =
306
- index === statementCount - 1 || ! statementsContext [ index + 1 ] ?. exception ;
307
-
308
- if ( ctx . stop && ctx . stop . tokenIndex < caretTokenIndex && isPrevCtxValid ) {
309
- startStatement = ctx ;
367
+ let startStatement : ParserRuleContext | null = null ;
368
+ let stopStatement : ParserRuleContext | null = null ;
369
+
370
+ for ( let index = 0 ; index < statementCount ; index ++ ) {
371
+ const ctx = statementsContext [ index ] ;
372
+ const isCurrentCtxValid = ! ctx . exception ;
373
+ if ( ! isCurrentCtxValid ) continue ;
374
+
375
+ /**
376
+ * Ensure that the statementContext before the left boundary
377
+ * and the last statementContext on the right boundary are qualified SQL statements.
378
+ */
379
+ const isPrevCtxValid = index === 0 || ! statementsContext [ index - 1 ] ?. exception ;
380
+ const isNextCtxValid =
381
+ index === statementCount - 1 || ! statementsContext [ index + 1 ] ?. exception ;
382
+
383
+ if ( ctx . stop && ctx . stop . tokenIndex < caretTokenIndex && isPrevCtxValid ) {
384
+ startStatement = ctx ;
385
+ }
386
+
387
+ if (
388
+ ctx . start &&
389
+ ! stopStatement &&
390
+ ctx . start . tokenIndex > caretTokenIndex &&
391
+ isNextCtxValid
392
+ ) {
393
+ stopStatement = ctx ;
394
+ break ;
395
+ }
310
396
}
311
397
312
- if (
313
- ctx . start &&
314
- ! stopStatement &&
315
- ctx . start . tokenIndex > caretTokenIndex &&
316
- isNextCtxValid
317
- ) {
318
- stopStatement = ctx ;
319
- break ;
320
- }
321
- }
398
+ // A boundary consisting of the index of the input.
399
+ startIndex = startStatement ?. start ?. start ?? 0 ;
400
+ stopIndex = stopStatement ?. stop ?. stop ?? input . length - 1 ;
322
401
402
+ /**
403
+ * Save offset of the tokenIndex in the range of input
404
+ * compared to the tokenIndex in the whole input
405
+ */
406
+ tokenIndexOffset = startStatement ?. start ?. tokenIndex ?? 0 ;
407
+ caretTokenIndex = caretTokenIndex - tokenIndexOffset ;
408
+
409
+ /**
410
+ * Reparse the input fragment,
411
+ * and c3 will collect candidates in the newly generated parseTree.
412
+ */
413
+ const inputSlice = input . slice ( startIndex , stopIndex ) ;
414
+
415
+ const lexer = this . createLexer ( inputSlice ) ;
416
+ lexer . removeErrorListeners ( ) ;
417
+ const tokenStream = new CommonTokenStream ( lexer ) ;
418
+ tokenStream . fill ( ) ;
419
+
420
+ const parser = this . createParserFromTokenStream ( tokenStream ) ;
421
+ parser . interpreter . predictionMode = PredictionMode . SLL ;
422
+ parser . removeErrorListeners ( ) ;
423
+ parser . buildParseTrees = true ;
424
+ parser . errorHandler = new ErrorStrategy ( ) ;
425
+
426
+ sqlParserIns = parser ;
427
+ c3Context = parser . program ( ) ;
428
+ }
429
+ } else {
323
430
// A boundary consisting of the index of the input.
324
- const startIndex = startStatement ?. start ?. start ?? 0 ;
325
- const stopIndex = stopStatement ?. stop ?. stop ?? input . length - 1 ;
431
+ startIndex = startToken ?. start ?? 0 ;
432
+ stopIndex = stopToken ?. stop + 1 ?? input . length ;
326
433
327
434
/**
328
435
* Save offset of the tokenIndex in the range of input
329
436
* compared to the tokenIndex in the whole input
330
437
*/
331
- tokenIndexOffset = startStatement ?. start ?. tokenIndex ?? 0 ;
438
+ tokenIndexOffset = startToken ?. tokenIndex ?? 0 ;
332
439
caretTokenIndex = caretTokenIndex - tokenIndexOffset ;
333
440
334
441
/**
0 commit comments