From 1e07c5c6e958bf0ba8a6c00a472860aba5d21465 Mon Sep 17 00:00:00 2001 From: liuxy0551 Date: Fri, 27 Sep 2024 15:55:13 +0800 Subject: [PATCH 1/3] test: complete after error syntax --- .../completeAfterSyntaxError.test.ts | 66 ++++++++++++++++++ .../completeAfterSyntaxError.test.ts | 67 +++++++++++++++++++ .../completeAfterSyntaxError.test.ts | 67 +++++++++++++++++++ .../completeAfterSyntaxError.test.ts | 66 ++++++++++++++++++ .../completeAfterSyntaxError.test.ts | 66 ++++++++++++++++++ .../completeAfterSyntaxError.test.ts | 67 +++++++++++++++++++ .../completeAfterSyntaxError.test.ts | 66 ++++++++++++++++++ 7 files changed, 465 insertions(+) create mode 100644 test/parser/flink/suggestion/completeAfterSyntaxError.test.ts create mode 100644 test/parser/hive/suggestion/completeAfterSyntaxError.test.ts create mode 100644 test/parser/impala/suggestion/completeAfterSyntaxError.test.ts create mode 100644 test/parser/mysql/suggestion/completeAfterSyntaxError.test.ts create mode 100644 test/parser/postgresql/suggestion/completeAfterSyntaxError.test.ts create mode 100644 test/parser/spark/suggestion/completeAfterSyntaxError.test.ts create mode 100644 test/parser/trino/suggestion/completeAfterSyntaxError.test.ts diff --git a/test/parser/flink/suggestion/completeAfterSyntaxError.test.ts b/test/parser/flink/suggestion/completeAfterSyntaxError.test.ts new file mode 100644 index 00000000..bc2b1ce4 --- /dev/null +++ b/test/parser/flink/suggestion/completeAfterSyntaxError.test.ts @@ -0,0 +1,66 @@ +import { FlinkSQL } from 'src/parser/flink'; +import { CaretPosition, EntityContextType } from 'src/parser/common/types'; + +describe('FlinkSQL Complete After Syntax Error', () => { + const flink = new FlinkSQL(); + + const sql1 = `SELECT FROM tb2;\nINSERT INTO `; + const sql2 = `SELECT FROM tb3;\nCREATE TABLE `; + const sql3 = `SELECT FROM t1;\nSL`; + + test('Syntax error but end with semi, should suggest tableName', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 13, + }; + const suggestion = flink.getSuggestionAtCaretPosition(sql1, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(1); + expect(syntaxes[0].syntaxContextType).toBe(EntityContextType.TABLE); + + // keyword + const keywords = suggestion?.keywords; + expect(keywords.length).toBe(0); + }); + + test('Syntax error but end with semi, should suggest tableNameCreate', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 14, + }; + const suggestion = flink.getSuggestionAtCaretPosition(sql2, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(1); + expect(syntaxes[0].syntaxContextType).toBe(EntityContextType.TABLE_CREATE); + + // keyword + const keywords = suggestion?.keywords; + expect(keywords.length).toBe(1); + expect(keywords[0]).toBe('IF'); + }); + + test('Syntax error but end with semi, should suggest filter token', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 2, + }; + const suggestion = flink.getSuggestionAtCaretPosition(sql3, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(0); + + // keyword + const filterKeywords = suggestion?.keywords?.filter( + (item) => item.startsWith('S') && /S(?=.*L)/.test(item) + ); + expect(filterKeywords).toMatchUnorderedArray(['SELECT']); + }); +}); diff --git a/test/parser/hive/suggestion/completeAfterSyntaxError.test.ts b/test/parser/hive/suggestion/completeAfterSyntaxError.test.ts new file mode 100644 index 00000000..7ecad81c --- /dev/null +++ b/test/parser/hive/suggestion/completeAfterSyntaxError.test.ts @@ -0,0 +1,67 @@ +import { HiveSQL } from 'src/parser/hive'; +import { CaretPosition, EntityContextType } from 'src/parser/common/types'; + +describe('HiveSQL Complete After Syntax Error', () => { + const hive = new HiveSQL(); + + const sql1 = `SELECT FROM tb2;\nINSERT INTO `; + const sql2 = `SELECT FROM tb3;\nCREATE TABLE `; + const sql3 = `SELECT FROM t1;\nSL`; + + test('Syntax error but end with semi, should suggest tableName', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 13, + }; + const suggestion = hive.getSuggestionAtCaretPosition(sql1, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(1); + expect(syntaxes[0].syntaxContextType).toBe(EntityContextType.TABLE); + + // keyword + const keywords = suggestion?.keywords; + expect(keywords.length).toBe(1); + expect(keywords[0]).toBe('TABLE'); + }); + + test('Syntax error but end with semi, should suggest tableNameCreate', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 14, + }; + const suggestion = hive.getSuggestionAtCaretPosition(sql2, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(1); + expect(syntaxes[0].syntaxContextType).toBe(EntityContextType.TABLE_CREATE); + + // keyword + const keywords = suggestion?.keywords; + expect(keywords.length).toBe(1); + expect(keywords[0]).toBe('IF'); + }); + + test('Syntax error but end with semi, should suggest filter token', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 2, + }; + const suggestion = hive.getSuggestionAtCaretPosition(sql3, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(0); + + // keyword + const filterKeywords = suggestion?.keywords?.filter( + (item) => item.startsWith('S') && /S(?=.*L)/.test(item) + ); + expect(filterKeywords).toMatchUnorderedArray(['SELECT']); + }); +}); diff --git a/test/parser/impala/suggestion/completeAfterSyntaxError.test.ts b/test/parser/impala/suggestion/completeAfterSyntaxError.test.ts new file mode 100644 index 00000000..4b509101 --- /dev/null +++ b/test/parser/impala/suggestion/completeAfterSyntaxError.test.ts @@ -0,0 +1,67 @@ +import { ImpalaSQL } from 'src/parser/impala'; +import { CaretPosition, EntityContextType } from 'src/parser/common/types'; + +describe('ImpalaSQL Complete After Syntax Error', () => { + const impala = new ImpalaSQL(); + + const sql1 = `SELECT FROM tb2;\nINSERT INTO `; + const sql2 = `SELECT FROM tb3;\nCREATE TABLE `; + const sql3 = `SELECT FROM t1;\nSL`; + + test('Syntax error but end with semi, should suggest tableName', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 13, + }; + const suggestion = impala.getSuggestionAtCaretPosition(sql1, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(1); + expect(syntaxes[0].syntaxContextType).toBe(EntityContextType.TABLE); + + // keyword + const keywords = suggestion?.keywords; + expect(keywords.length).toBe(1); + expect(keywords[0]).toBe('TABLE'); + }); + + test('Syntax error but end with semi, should suggest tableNameCreate', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 14, + }; + const suggestion = impala.getSuggestionAtCaretPosition(sql2, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(1); + expect(syntaxes[0].syntaxContextType).toBe(EntityContextType.TABLE_CREATE); + + // keyword + const keywords = suggestion?.keywords; + expect(keywords.length).toBe(1); + expect(keywords[0]).toBe('IF'); + }); + + test('Syntax error but end with semi, should suggest filter token', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 2, + }; + const suggestion = impala.getSuggestionAtCaretPosition(sql3, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(0); + + // keyword + const filterKeywords = suggestion?.keywords?.filter( + (item) => item.startsWith('S') && /S(?=.*L)/.test(item) + ); + expect(filterKeywords).toMatchUnorderedArray(['SELECT']); + }); +}); diff --git a/test/parser/mysql/suggestion/completeAfterSyntaxError.test.ts b/test/parser/mysql/suggestion/completeAfterSyntaxError.test.ts new file mode 100644 index 00000000..3f5efcdf --- /dev/null +++ b/test/parser/mysql/suggestion/completeAfterSyntaxError.test.ts @@ -0,0 +1,66 @@ +import { MySQL } from 'src/parser/mysql'; +import { CaretPosition, EntityContextType } from 'src/parser/common/types'; + +describe('MySQL Complete After Syntax Error', () => { + const mysql = new MySQL(); + + const sql1 = `SELECT FROM tb2;\nINSERT INTO `; + const sql2 = `SELECT FROM tb3;\nCREATE TABLE `; + const sql3 = `SELECT FROM t1;\nSL`; + + test('Syntax error but end with semi, should suggest tableName', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 13, + }; + const suggestion = mysql.getSuggestionAtCaretPosition(sql1, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(1); + expect(syntaxes[0].syntaxContextType).toBe(EntityContextType.TABLE); + + // keyword + const keywords = suggestion?.keywords; + expect(keywords.length).toBe(0); + }); + + test('Syntax error but end with semi, should suggest tableNameCreate', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 14, + }; + const suggestion = mysql.getSuggestionAtCaretPosition(sql2, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(1); + expect(syntaxes[0].syntaxContextType).toBe(EntityContextType.TABLE_CREATE); + + // keyword + const keywords = suggestion?.keywords; + expect(keywords.length).toBe(1); + expect(keywords[0]).toBe('IF'); + }); + + test('Syntax error but end with semi, should suggest filter token', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 2, + }; + const suggestion = mysql.getSuggestionAtCaretPosition(sql3, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(0); + + // keyword + const filterKeywords = suggestion?.keywords?.filter( + (item) => item.startsWith('S') && /S(?=.*L)/.test(item) + ); + expect(filterKeywords).toMatchUnorderedArray(['SELECT', 'SIGNAL']); + }); +}); diff --git a/test/parser/postgresql/suggestion/completeAfterSyntaxError.test.ts b/test/parser/postgresql/suggestion/completeAfterSyntaxError.test.ts new file mode 100644 index 00000000..6cf6ea50 --- /dev/null +++ b/test/parser/postgresql/suggestion/completeAfterSyntaxError.test.ts @@ -0,0 +1,66 @@ +import { PostgreSQL } from 'src/parser/postgresql'; +import { CaretPosition, EntityContextType } from 'src/parser/common/types'; + +describe('PostgreSQL Complete After Syntax Error', () => { + const postgresql = new PostgreSQL(); + + const sql1 = `SELECT FROM tb2;\nINSERT INTO `; + const sql2 = `SELECT FROM tb3;\nCREATE TABLE `; + const sql3 = `SELECT FROM t1;\nSL`; + + test('Syntax error but end with semi, should suggest tableName', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 13, + }; + const suggestion = postgresql.getSuggestionAtCaretPosition(sql1, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(1); + expect(syntaxes[0].syntaxContextType).toBe(EntityContextType.TABLE); + + // keyword + const keywords = suggestion?.keywords; + expect(keywords.length).toBe(0); + }); + + test('Syntax error but end with semi, should suggest tableNameCreate', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 14, + }; + const suggestion = postgresql.getSuggestionAtCaretPosition(sql2, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(1); + expect(syntaxes[0].syntaxContextType).toBe(EntityContextType.TABLE_CREATE); + + // keyword + const keywords = suggestion?.keywords; + expect(keywords.length).toBe(1); + expect(keywords[0]).toBe('IF'); + }); + + test('Syntax error but end with semi, should suggest filter token', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 2, + }; + const suggestion = postgresql.getSuggestionAtCaretPosition(sql3, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(0); + + // keyword + const filterKeywords = suggestion?.keywords?.filter( + (item) => item.startsWith('S') && /S(?=.*L)/.test(item) + ); + expect(filterKeywords).toMatchUnorderedArray(['SELECT']); + }); +}); diff --git a/test/parser/spark/suggestion/completeAfterSyntaxError.test.ts b/test/parser/spark/suggestion/completeAfterSyntaxError.test.ts new file mode 100644 index 00000000..ae8b8349 --- /dev/null +++ b/test/parser/spark/suggestion/completeAfterSyntaxError.test.ts @@ -0,0 +1,67 @@ +import { SparkSQL } from 'src/parser/spark'; +import { CaretPosition, EntityContextType } from 'src/parser/common/types'; + +describe('SparkSQL Complete After Syntax Error', () => { + const spark = new SparkSQL(); + + const sql1 = `SELECT FROM tb2;\nINSERT INTO `; + const sql2 = `SELECT FROM tb3;\nCREATE TABLE `; + const sql3 = `SELECT FROM t1;\nSL`; + + test('Syntax error but end with semi, should suggest tableName', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 13, + }; + const suggestion = spark.getSuggestionAtCaretPosition(sql1, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(1); + expect(syntaxes[0].syntaxContextType).toBe(EntityContextType.TABLE); + + // keyword + const keywords = suggestion?.keywords; + expect(keywords.length).toBe(1); + expect(keywords[0]).toBe('TABLE'); + }); + + test('Syntax error but end with semi, should suggest tableNameCreate', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 14, + }; + const suggestion = spark.getSuggestionAtCaretPosition(sql2, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(1); + expect(syntaxes[0].syntaxContextType).toBe(EntityContextType.TABLE_CREATE); + + // keyword + const keywords = suggestion?.keywords; + expect(keywords.length).toBe(1); + expect(keywords[0]).toBe('IF'); + }); + + test('Syntax error but end with semi, should suggest filter token', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 2, + }; + const suggestion = spark.getSuggestionAtCaretPosition(sql3, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(0); + + // keyword + const filterKeywords = suggestion?.keywords?.filter( + (item) => item.startsWith('S') && /S(?=.*L)/.test(item) + ); + expect(filterKeywords).toMatchUnorderedArray(['SELECT']); + }); +}); diff --git a/test/parser/trino/suggestion/completeAfterSyntaxError.test.ts b/test/parser/trino/suggestion/completeAfterSyntaxError.test.ts new file mode 100644 index 00000000..a6115065 --- /dev/null +++ b/test/parser/trino/suggestion/completeAfterSyntaxError.test.ts @@ -0,0 +1,66 @@ +import { TrinoSQL } from 'src/parser/trino'; +import { CaretPosition, EntityContextType } from 'src/parser/common/types'; + +describe('TrinoSQL Complete After Syntax Error', () => { + const trino = new TrinoSQL(); + + const sql1 = `SELECT FROM tb2;\nINSERT INTO `; + const sql2 = `SELECT FROM tb3;\nCREATE TABLE `; + const sql3 = `SELECT FROM t1;\nSL`; + + test('Syntax error but end with semi, should suggest tableName', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 13, + }; + const suggestion = trino.getSuggestionAtCaretPosition(sql1, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(1); + expect(syntaxes[0].syntaxContextType).toBe(EntityContextType.TABLE); + + // keyword + const keywords = suggestion?.keywords; + expect(keywords.length).toBe(0); + }); + + test('Syntax error but end with semi, should suggest tableNameCreate', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 14, + }; + const suggestion = trino.getSuggestionAtCaretPosition(sql2, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(1); + expect(syntaxes[0].syntaxContextType).toBe(EntityContextType.TABLE_CREATE); + + // keyword + const keywords = suggestion?.keywords; + expect(keywords.length).toBe(1); + expect(keywords[0]).toBe('IF'); + }); + + test('Syntax error but end with semi, should suggest filter token', () => { + const pos: CaretPosition = { + lineNumber: 2, + column: 2, + }; + const suggestion = trino.getSuggestionAtCaretPosition(sql3, pos); + expect(suggestion).not.toBeUndefined(); + + // syntax + const syntaxes = suggestion?.syntax; + expect(syntaxes.length).toBe(0); + + // keyword + const filterKeywords = suggestion?.keywords?.filter( + (item) => item.startsWith('S') && /S(?=.*L)/.test(item) + ); + expect(filterKeywords).toMatchUnorderedArray(['SELECT']); + }); +}); From 68c00c2d887df29305153ceefa2daa05ee256c49 Mon Sep 17 00:00:00 2001 From: liuxy0551 Date: Tue, 15 Oct 2024 22:13:53 +0800 Subject: [PATCH 2/3] test: suggestion wordRanges with range --- .../suggestion/multipleStatement.test.ts | 20 ++++++++++++- .../flink/suggestion/syntaxSuggestion.test.ts | 12 +++++++- .../hive/suggestion/multipleStatement.test.ts | 20 ++++++++++++- .../hive/suggestion/syntaxSuggestion.test.ts | 28 ++++++++++++++++++- .../suggestion/multipleStatement.test.ts | 20 ++++++++++++- .../suggestion/syntaxSuggestion.test.ts | 28 ++++++++++++++++++- .../suggestion/multipleStatement.test.ts | 20 ++++++++++++- .../mysql/suggestion/syntaxSuggestion.test.ts | 28 ++++++++++++++++++- .../suggestion/multipleStatement.test.ts | 20 ++++++++++++- .../suggestion/suggestionWithEntity.test.ts | 12 +++++++- .../suggestion/syntaxSuggestion.test.ts | 28 ++++++++++++++++++- .../suggestion/multipleStatement.test.ts | 20 ++++++++++++- .../spark/suggestion/syntaxSuggestion.test.ts | 28 ++++++++++++++++++- .../suggestion/multipleStatement.test.ts | 20 ++++++++++++- .../trino/suggestion/syntaxSuggestion.test.ts | 28 ++++++++++++++++++- 15 files changed, 317 insertions(+), 15 deletions(-) diff --git a/test/parser/flink/suggestion/multipleStatement.test.ts b/test/parser/flink/suggestion/multipleStatement.test.ts index e89e4bdf..e0ecd308 100644 --- a/test/parser/flink/suggestion/multipleStatement.test.ts +++ b/test/parser/flink/suggestion/multipleStatement.test.ts @@ -36,7 +36,25 @@ describe('FlinkSQL Multiple Statements Syntax Suggestion', () => { ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + expect(suggestion?.wordRanges?.length).toBe(2); + expect(suggestion?.wordRanges).toEqual([ + { + text: 'db', + line: 16, + startIndex: 306, + endIndex: 307, + startColumn: 14, + endColumn: 16, + }, + { + text: '.', + line: 16, + startIndex: 308, + endIndex: 308, + startColumn: 16, + endColumn: 17, + }, + ]); }); test('Insert into table ', () => { diff --git a/test/parser/flink/suggestion/syntaxSuggestion.test.ts b/test/parser/flink/suggestion/syntaxSuggestion.test.ts index a017c641..4192a5ac 100644 --- a/test/parser/flink/suggestion/syntaxSuggestion.test.ts +++ b/test/parser/flink/suggestion/syntaxSuggestion.test.ts @@ -32,7 +32,17 @@ describe('Flink SQL Syntax Suggestion', () => { ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['cat']); + expect(suggestion?.wordRanges?.length).toBe(1); + expect(suggestion?.wordRanges).toEqual([ + { + text: 'cat', + line: 1, + startIndex: 13, + endIndex: 15, + startColumn: 14, + endColumn: 17, + }, + ]); }); test('Select table', () => { diff --git a/test/parser/hive/suggestion/multipleStatement.test.ts b/test/parser/hive/suggestion/multipleStatement.test.ts index 0c1d36fe..223ab846 100644 --- a/test/parser/hive/suggestion/multipleStatement.test.ts +++ b/test/parser/hive/suggestion/multipleStatement.test.ts @@ -36,7 +36,25 @@ describe('HiveSQL Multiple Statements Syntax Suggestion', () => { ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + expect(suggestion?.wordRanges?.length).toBe(2); + expect(suggestion?.wordRanges).toEqual([ + { + text: 'db', + line: 9, + startIndex: 272, + endIndex: 273, + startColumn: 14, + endColumn: 16, + }, + { + text: '.', + line: 9, + startIndex: 274, + endIndex: 274, + startColumn: 16, + endColumn: 17, + }, + ]); }); test('Insert into table ', () => { diff --git a/test/parser/hive/suggestion/syntaxSuggestion.test.ts b/test/parser/hive/suggestion/syntaxSuggestion.test.ts index f2066a8f..541464aa 100644 --- a/test/parser/hive/suggestion/syntaxSuggestion.test.ts +++ b/test/parser/hive/suggestion/syntaxSuggestion.test.ts @@ -32,7 +32,33 @@ describe('Hive SQL Syntax Suggestion', () => { ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.', 'tb']); + expect(suggestion?.wordRanges?.length).toBe(3); + expect(suggestion?.wordRanges).toEqual([ + { + text: 'db', + line: 1, + startIndex: 12, + endIndex: 13, + startColumn: 13, + endColumn: 15, + }, + { + text: '.', + line: 1, + startIndex: 14, + endIndex: 14, + startColumn: 15, + endColumn: 16, + }, + { + text: 'tb', + line: 1, + startIndex: 15, + endIndex: 16, + startColumn: 16, + endColumn: 18, + }, + ]); }); test('Select table ', () => { diff --git a/test/parser/impala/suggestion/multipleStatement.test.ts b/test/parser/impala/suggestion/multipleStatement.test.ts index 115d2012..d9a00b02 100644 --- a/test/parser/impala/suggestion/multipleStatement.test.ts +++ b/test/parser/impala/suggestion/multipleStatement.test.ts @@ -36,7 +36,25 @@ describe('ImpalaSQL Multiple Statements Syntax Suggestion', () => { ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + expect(suggestion?.wordRanges?.length).toBe(2); + expect(suggestion?.wordRanges).toEqual([ + { + text: 'db', + line: 9, + startIndex: 203, + endIndex: 204, + startColumn: 14, + endColumn: 16, + }, + { + text: '.', + line: 9, + startIndex: 205, + endIndex: 205, + startColumn: 16, + endColumn: 17, + }, + ]); }); test('Insert into table ', () => { diff --git a/test/parser/impala/suggestion/syntaxSuggestion.test.ts b/test/parser/impala/suggestion/syntaxSuggestion.test.ts index 08629187..02cf8d4d 100644 --- a/test/parser/impala/suggestion/syntaxSuggestion.test.ts +++ b/test/parser/impala/suggestion/syntaxSuggestion.test.ts @@ -26,7 +26,33 @@ describe('Impala SQL Syntax Suggestion', () => { ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['cat', '.', 'a']); + expect(suggestion?.wordRanges?.length).toBe(3); + expect(suggestion?.wordRanges).toEqual([ + { + text: 'cat', + line: 1, + startIndex: 14, + endIndex: 16, + startColumn: 15, + endColumn: 18, + }, + { + text: '.', + line: 1, + startIndex: 17, + endIndex: 17, + startColumn: 18, + endColumn: 19, + }, + { + text: 'a', + line: 1, + startIndex: 18, + endIndex: 18, + startColumn: 19, + endColumn: 20, + }, + ]); }); test('Function call', () => { diff --git a/test/parser/mysql/suggestion/multipleStatement.test.ts b/test/parser/mysql/suggestion/multipleStatement.test.ts index 48efa713..d08099b8 100644 --- a/test/parser/mysql/suggestion/multipleStatement.test.ts +++ b/test/parser/mysql/suggestion/multipleStatement.test.ts @@ -36,7 +36,25 @@ describe('MySQL Multiple Statements Syntax Suggestion', () => { ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + expect(suggestion?.wordRanges?.length).toBe(2); + expect(suggestion?.wordRanges).toEqual([ + { + text: 'db', + line: 9, + startIndex: 306, + endIndex: 307, + startColumn: 14, + endColumn: 16, + }, + { + text: '.', + line: 9, + startIndex: 308, + endIndex: 308, + startColumn: 16, + endColumn: 17, + }, + ]); }); test('Insert into table ', () => { diff --git a/test/parser/mysql/suggestion/syntaxSuggestion.test.ts b/test/parser/mysql/suggestion/syntaxSuggestion.test.ts index 03ac43a9..a8977e61 100644 --- a/test/parser/mysql/suggestion/syntaxSuggestion.test.ts +++ b/test/parser/mysql/suggestion/syntaxSuggestion.test.ts @@ -30,7 +30,33 @@ describe('MySQL Syntax Suggestion', () => { ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.', 'tb']); + expect(suggestion?.wordRanges?.length).toBe(3); + expect(suggestion?.wordRanges).toEqual([ + { + text: 'db', + line: 1, + startIndex: 12, + endIndex: 13, + startColumn: 13, + endColumn: 15, + }, + { + text: '.', + line: 1, + startIndex: 14, + endIndex: 14, + startColumn: 15, + endColumn: 16, + }, + { + text: 'tb', + line: 1, + startIndex: 15, + endIndex: 16, + startColumn: 16, + endColumn: 18, + }, + ]); }); test('Select table ', () => { diff --git a/test/parser/postgresql/suggestion/multipleStatement.test.ts b/test/parser/postgresql/suggestion/multipleStatement.test.ts index 48e199a8..a688db41 100644 --- a/test/parser/postgresql/suggestion/multipleStatement.test.ts +++ b/test/parser/postgresql/suggestion/multipleStatement.test.ts @@ -69,6 +69,24 @@ describe('PgSQL Multiple Statements Syntax Suggestion', () => { ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + expect(suggestion?.wordRanges?.length).toBe(2); + expect(suggestion?.wordRanges).toEqual([ + { + text: 'db', + line: 21, + startIndex: 682, + endIndex: 683, + startColumn: 62, + endColumn: 64, + }, + { + text: '.', + line: 21, + startIndex: 684, + endIndex: 684, + startColumn: 64, + endColumn: 65, + }, + ]); }); }); diff --git a/test/parser/postgresql/suggestion/suggestionWithEntity.test.ts b/test/parser/postgresql/suggestion/suggestionWithEntity.test.ts index ab840ba6..2477f585 100644 --- a/test/parser/postgresql/suggestion/suggestionWithEntity.test.ts +++ b/test/parser/postgresql/suggestion/suggestionWithEntity.test.ts @@ -166,7 +166,17 @@ describe('PostgreSql Syntax Suggestion with collect entity', () => { (syn) => syn.syntaxContextType === EntityContextType.COLUMN ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['a_column']); + expect(suggestion?.wordRanges?.length).toBe(1); + expect(suggestion?.wordRanges).toEqual([ + { + text: 'a_column', + line: 13, + startIndex: 399, + endIndex: 406, + startColumn: 27, + endColumn: 35, + }, + ]); const entities = postgre.getAllEntities(sql, pos); expect(entities.length).toBe(1); diff --git a/test/parser/postgresql/suggestion/syntaxSuggestion.test.ts b/test/parser/postgresql/suggestion/syntaxSuggestion.test.ts index 664b2b52..3a7f2266 100644 --- a/test/parser/postgresql/suggestion/syntaxSuggestion.test.ts +++ b/test/parser/postgresql/suggestion/syntaxSuggestion.test.ts @@ -32,7 +32,33 @@ describe('Postgre SQL Syntax Suggestion', () => { ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.', 'tb']); + expect(suggestion?.wordRanges?.length).toBe(3); + expect(suggestion?.wordRanges).toEqual([ + { + text: 'db', + line: 3, + startIndex: 88, + endIndex: 89, + startColumn: 13, + endColumn: 15, + }, + { + text: '.', + line: 3, + startIndex: 90, + endIndex: 90, + startColumn: 15, + endColumn: 16, + }, + { + text: 'tb', + line: 3, + startIndex: 91, + endIndex: 92, + startColumn: 16, + endColumn: 18, + }, + ]); }); test('Alter table ', () => { diff --git a/test/parser/spark/suggestion/multipleStatement.test.ts b/test/parser/spark/suggestion/multipleStatement.test.ts index a65b020e..04ee292f 100644 --- a/test/parser/spark/suggestion/multipleStatement.test.ts +++ b/test/parser/spark/suggestion/multipleStatement.test.ts @@ -36,7 +36,25 @@ describe('SparkSQL Multiple Statements Syntax Suggestion', () => { ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + expect(suggestion?.wordRanges?.length).toBe(2); + expect(suggestion?.wordRanges).toEqual([ + { + endColumn: 17, + endIndex: 258, + line: 9, + startColumn: 15, + startIndex: 257, + text: 'db', + }, + { + endColumn: 18, + endIndex: 259, + line: 9, + startColumn: 17, + startIndex: 259, + text: '.', + }, + ]); }); test('Insert into table ', () => { diff --git a/test/parser/spark/suggestion/syntaxSuggestion.test.ts b/test/parser/spark/suggestion/syntaxSuggestion.test.ts index 2ed6c655..e6a2e85d 100644 --- a/test/parser/spark/suggestion/syntaxSuggestion.test.ts +++ b/test/parser/spark/suggestion/syntaxSuggestion.test.ts @@ -32,7 +32,33 @@ describe('Spark SQL Syntax Suggestion', () => { ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.', 'tb']); + expect(suggestion?.wordRanges?.length).toBe(3); + expect(suggestion?.wordRanges).toEqual([ + { + text: 'db', + line: 1, + startIndex: 12, + endIndex: 13, + startColumn: 13, + endColumn: 15, + }, + { + text: '.', + line: 1, + startIndex: 14, + endIndex: 14, + startColumn: 15, + endColumn: 16, + }, + { + text: 'tb', + line: 1, + startIndex: 15, + endIndex: 16, + startColumn: 16, + endColumn: 18, + }, + ]); }); test('Select table ', () => { diff --git a/test/parser/trino/suggestion/multipleStatement.test.ts b/test/parser/trino/suggestion/multipleStatement.test.ts index a224030d..1d8c1fb9 100644 --- a/test/parser/trino/suggestion/multipleStatement.test.ts +++ b/test/parser/trino/suggestion/multipleStatement.test.ts @@ -36,7 +36,25 @@ describe('TrinoSQL Multiple Statements Syntax Suggestion', () => { ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + expect(suggestion?.wordRanges?.length).toBe(2); + expect(suggestion?.wordRanges).toEqual([ + { + text: 'db', + line: 9, + startIndex: 137, + endIndex: 138, + startColumn: 17, + endColumn: 19, + }, + { + text: '.', + line: 9, + startIndex: 139, + endIndex: 139, + startColumn: 19, + endColumn: 20, + }, + ]); }); test('Insert into table ', () => { diff --git a/test/parser/trino/suggestion/syntaxSuggestion.test.ts b/test/parser/trino/suggestion/syntaxSuggestion.test.ts index 74bbd084..2d51fe0e 100644 --- a/test/parser/trino/suggestion/syntaxSuggestion.test.ts +++ b/test/parser/trino/suggestion/syntaxSuggestion.test.ts @@ -32,7 +32,33 @@ describe('Trino SQL Syntax Suggestion', () => { (syn) => syn.syntaxContextType === EntityContextType.TABLE ); expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.', 'tb']); + expect(suggestion?.wordRanges?.length).toBe(3); + expect(suggestion?.wordRanges).toEqual([ + { + text: 'db', + line: 1, + startIndex: 12, + endIndex: 13, + startColumn: 13, + endColumn: 15, + }, + { + text: '.', + line: 1, + startIndex: 14, + endIndex: 14, + startColumn: 15, + endColumn: 16, + }, + { + text: 'tb', + line: 1, + startIndex: 15, + endIndex: 16, + startColumn: 16, + endColumn: 18, + }, + ]); }); test('Select table ', () => { From de3b760b75a03d190ff7be3056f36db1cb8e711e Mon Sep 17 00:00:00 2001 From: liuxy0551 Date: Tue, 15 Oct 2024 22:14:11 +0800 Subject: [PATCH 3/3] feat: add splitInputBySeparator --- src/parser/common/basicSQL.ts | 127 ++++++++++++++++++++++++++++----- src/parser/flink/index.ts | 10 +-- src/parser/hive/index.ts | 10 +-- src/parser/impala/index.ts | 10 +-- src/parser/mysql/index.ts | 10 +-- src/parser/postgresql/index.ts | 10 +-- src/parser/spark/index.ts | 10 +-- src/parser/trino/index.ts | 10 +-- 8 files changed, 129 insertions(+), 68 deletions(-) diff --git a/src/parser/common/basicSQL.ts b/src/parser/common/basicSQL.ts index b835979a..981b3207 100644 --- a/src/parser/common/basicSQL.ts +++ b/src/parser/common/basicSQL.ts @@ -21,6 +21,8 @@ import type { SplitListener } from './splitListener'; import type { EntityCollector } from './entityCollector'; import { EntityContext } from './entityCollector'; +const SEPARATOR: string = ';'; + /** * Basic SQL class, every sql needs extends it. */ @@ -65,13 +67,11 @@ export abstract class BasicSQL< * @param candidates candidate list * @param allTokens all tokens from input * @param caretTokenIndex tokenIndex of caretPosition - * @param tokenIndexOffset offset of the tokenIndex in the candidates compared to the tokenIndex in allTokens */ protected abstract processCandidates( candidates: CandidatesCollection, allTokens: Token[], - caretTokenIndex: number, - tokenIndexOffset: number + caretTokenIndex: number ): Suggestions; /** @@ -251,6 +251,78 @@ export abstract class BasicSQL< return res; } + /** + * Get the smaller range of input + * @param input string + * @param allTokens all tokens from input + * @param tokenIndexOffset offset of the tokenIndex in the range of input + * @param caretTokenIndex tokenIndex of caretPosition + * @returns inputSlice: string, caretTokenIndex: number + */ + private splitInputBySeparator( + input: string, + allTokens: Token[], + tokenIndexOffset: number, + caretTokenIndex: number + ): { inputSlice: string; allTokens: Token[]; caretTokenIndex: number } { + const tokens = allTokens.slice(tokenIndexOffset); + /** + * Set startToken + */ + let startToken: Token | null = null; + for (let tokenIndex = caretTokenIndex - tokenIndexOffset; tokenIndex >= 0; tokenIndex--) { + const token = tokens[tokenIndex]; + if (token?.text === SEPARATOR) { + startToken = tokens[tokenIndex + 1]; + break; + } + } + if (startToken === null) { + startToken = tokens[0]; + } + + /** + * Set stopToken + */ + let stopToken: Token | null = null; + for ( + let tokenIndex = caretTokenIndex - tokenIndexOffset; + tokenIndex < tokens.length; + tokenIndex++ + ) { + const token = tokens[tokenIndex]; + if (token?.text === SEPARATOR) { + stopToken = token; + break; + } + } + if (stopToken === null) { + stopToken = tokens[tokens.length - 1]; + } + + const indexOffset = tokens[0].start; + let startIndex = startToken.start - indexOffset; + let stopIndex = stopToken.stop + 1 - indexOffset; + + /** + * Save offset of the tokenIndex in the range of input + * compared to the tokenIndex in the whole input + */ + const _tokenIndexOffset = startToken.tokenIndex; + const _caretTokenIndex = caretTokenIndex - _tokenIndexOffset; + + /** + * Get the smaller range of _input + */ + const _input = input.slice(startIndex, stopIndex); + + return { + inputSlice: _input, + allTokens: allTokens.slice(_tokenIndexOffset), + caretTokenIndex: _caretTokenIndex, + }; + } + /** * Get suggestions of syntax and token at caretPosition * @param input source string @@ -262,12 +334,13 @@ export abstract class BasicSQL< caretPosition: CaretPosition ): Suggestions | null { const splitListener = this.splitListener; + let inputSlice = input; - this.parseWithCache(input); + this.parseWithCache(inputSlice); if (!this._parseTree) return null; let sqlParserIns = this._parser; - const allTokens = this.getAllTokens(input); + let allTokens = this.getAllTokens(inputSlice); let caretTokenIndex = findCaretTokenIndex(caretPosition, allTokens); let c3Context: ParserRuleContext = this._parseTree; let tokenIndexOffset: number = 0; @@ -321,22 +394,43 @@ export abstract class BasicSQL< } // A boundary consisting of the index of the input. - const startIndex = startStatement?.start?.start ?? 0; - const stopIndex = stopStatement?.stop?.stop ?? input.length - 1; + let startIndex = startStatement?.start?.start ?? 0; + let stopIndex = stopStatement?.stop?.stop ?? inputSlice.length - 1; /** * Save offset of the tokenIndex in the range of input * compared to the tokenIndex in the whole input */ tokenIndexOffset = startStatement?.start?.tokenIndex ?? 0; - caretTokenIndex = caretTokenIndex - tokenIndexOffset; + inputSlice = inputSlice.slice(startIndex, stopIndex); + } - /** - * Reparse the input fragment, - * and c3 will collect candidates in the newly generated parseTree. - */ - const inputSlice = input.slice(startIndex, stopIndex); + /** + * Split the inputSlice by separator to get the smaller range of inputSlice. + */ + if (inputSlice.includes(SEPARATOR)) { + const { + inputSlice: _input, + allTokens: _allTokens, + caretTokenIndex: _caretTokenIndex, + } = this.splitInputBySeparator( + inputSlice, + allTokens, + tokenIndexOffset, + caretTokenIndex + ); + + allTokens = _allTokens; + caretTokenIndex = _caretTokenIndex; + inputSlice = _input; + } else { + caretTokenIndex = caretTokenIndex - tokenIndexOffset; + } + /** + * Reparse the input fragment, and c3 will collect candidates in the newly generated parseTree when input changed. + */ + if (inputSlice !== input) { const lexer = this.createLexer(inputSlice); lexer.removeErrorListeners(); const tokenStream = new CommonTokenStream(lexer); @@ -356,12 +450,7 @@ export abstract class BasicSQL< core.preferredRules = this.preferredRules; const candidates = core.collectCandidates(caretTokenIndex, c3Context); - const originalSuggestions = this.processCandidates( - candidates, - allTokens, - caretTokenIndex, - tokenIndexOffset - ); + const originalSuggestions = this.processCandidates(candidates, allTokens, caretTokenIndex); const syntaxSuggestions: SyntaxSuggestion[] = originalSuggestions.syntax.map( (syntaxCtx) => { diff --git a/src/parser/flink/index.ts b/src/parser/flink/index.ts index 61d19d22..33c424b2 100644 --- a/src/parser/flink/index.ts +++ b/src/parser/flink/index.ts @@ -50,19 +50,15 @@ export class FlinkSQL extends BasicSQL { const originalSyntaxSuggestions: SyntaxSuggestion[] = []; const keywords: string[] = []; for (let candidate of candidates.rules) { const [ruleType, candidateRule] = candidate; - const startTokenIndex = candidateRule.startTokenIndex + tokenIndexOffset; - const tokenRanges = allTokens.slice( - startTokenIndex, - caretTokenIndex + tokenIndexOffset + 1 - ); + const startTokenIndex = candidateRule.startTokenIndex; + const tokenRanges = allTokens.slice(startTokenIndex, caretTokenIndex + 1); let syntaxContextType: EntityContextType | StmtContextType | undefined = void 0; switch (ruleType) { diff --git a/src/parser/hive/index.ts b/src/parser/hive/index.ts index 7bac2468..53deabce 100644 --- a/src/parser/hive/index.ts +++ b/src/parser/hive/index.ts @@ -51,18 +51,14 @@ export class HiveSQL extends BasicSQL { const originalSyntaxSuggestions: SyntaxSuggestion[] = []; const keywords: string[] = []; for (let candidate of candidates.rules) { const [ruleType, candidateRule] = candidate; - const startTokenIndex = candidateRule.startTokenIndex + tokenIndexOffset; - const tokenRanges = allTokens.slice( - startTokenIndex, - caretTokenIndex + tokenIndexOffset + 1 - ); + const startTokenIndex = candidateRule.startTokenIndex; + const tokenRanges = allTokens.slice(startTokenIndex, caretTokenIndex + 1); let syntaxContextType: EntityContextType | StmtContextType | undefined = void 0; switch (ruleType) { diff --git a/src/parser/impala/index.ts b/src/parser/impala/index.ts index 9902507e..9958e377 100644 --- a/src/parser/impala/index.ts +++ b/src/parser/impala/index.ts @@ -49,18 +49,14 @@ export class ImpalaSQL extends BasicSQL { const originalSyntaxSuggestions: SyntaxSuggestion[] = []; const keywords: string[] = []; for (let candidate of candidates.rules) { const [ruleType, candidateRule] = candidate; - const startTokenIndex = candidateRule.startTokenIndex + tokenIndexOffset; - const tokenRanges = allTokens.slice( - startTokenIndex, - caretTokenIndex + tokenIndexOffset + 1 - ); + const startTokenIndex = candidateRule.startTokenIndex; + const tokenRanges = allTokens.slice(startTokenIndex, caretTokenIndex + 1); let syntaxContextType: EntityContextType | StmtContextType | undefined = void 0; switch (ruleType) { diff --git a/src/parser/mysql/index.ts b/src/parser/mysql/index.ts index 5ded33cc..3b999a03 100644 --- a/src/parser/mysql/index.ts +++ b/src/parser/mysql/index.ts @@ -49,19 +49,15 @@ export class MySQL extends BasicSQL { protected processCandidates( candidates: CandidatesCollection, allTokens: Token[], - caretTokenIndex: number, - tokenIndexOffset: number + caretTokenIndex: number ): Suggestions { const originalSyntaxSuggestions: SyntaxSuggestion[] = []; const keywords: string[] = []; for (const candidate of candidates.rules) { const [ruleType, candidateRule] = candidate; - const startTokenIndex = candidateRule.startTokenIndex + tokenIndexOffset; - const tokenRanges = allTokens.slice( - startTokenIndex, - caretTokenIndex + tokenIndexOffset + 1 - ); + const startTokenIndex = candidateRule.startTokenIndex; + const tokenRanges = allTokens.slice(startTokenIndex, caretTokenIndex + 1); let syntaxContextType: EntityContextType | StmtContextType | undefined = void 0; switch (ruleType) { diff --git a/src/parser/postgresql/index.ts b/src/parser/postgresql/index.ts index 63820000..8453f398 100644 --- a/src/parser/postgresql/index.ts +++ b/src/parser/postgresql/index.ts @@ -54,18 +54,14 @@ export class PostgreSQL extends BasicSQL { const originalSyntaxSuggestions: SyntaxSuggestion[] = []; const keywords: string[] = []; for (let candidate of candidates.rules) { const [ruleType, candidateRule] = candidate; - const startTokenIndex = candidateRule.startTokenIndex + tokenIndexOffset; - const tokenRanges = allTokens.slice( - startTokenIndex, - caretTokenIndex + tokenIndexOffset + 1 - ); + const startTokenIndex = candidateRule.startTokenIndex; + const tokenRanges = allTokens.slice(startTokenIndex, caretTokenIndex + 1); let syntaxContextType: EntityContextType | StmtContextType | undefined = void 0; switch (ruleType) { diff --git a/src/parser/spark/index.ts b/src/parser/spark/index.ts index 1b847fd8..3b167479 100644 --- a/src/parser/spark/index.ts +++ b/src/parser/spark/index.ts @@ -49,19 +49,15 @@ export class SparkSQL extends BasicSQL { const originalSyntaxSuggestions: SyntaxSuggestion[] = []; const keywords: string[] = []; for (const candidate of candidates.rules) { const [ruleType, candidateRule] = candidate; - const startTokenIndex = candidateRule.startTokenIndex + tokenIndexOffset; - const tokenRanges = allTokens.slice( - startTokenIndex, - caretTokenIndex + tokenIndexOffset + 1 - ); + const startTokenIndex = candidateRule.startTokenIndex; + const tokenRanges = allTokens.slice(startTokenIndex, caretTokenIndex + 1); let syntaxContextType: EntityContextType | StmtContextType | undefined = void 0; switch (ruleType) { diff --git a/src/parser/trino/index.ts b/src/parser/trino/index.ts index cf1f9db4..452e2cce 100644 --- a/src/parser/trino/index.ts +++ b/src/parser/trino/index.ts @@ -51,19 +51,15 @@ export class TrinoSQL extends BasicSQL { const originalSyntaxSuggestions: SyntaxSuggestion[] = []; const keywords: string[] = []; for (let candidate of candidates.rules) { const [ruleType, candidateRule] = candidate; - const startTokenIndex = candidateRule.startTokenIndex + tokenIndexOffset; - const tokenRanges = allTokens.slice( - startTokenIndex, - caretTokenIndex + tokenIndexOffset + 1 - ); + const startTokenIndex = candidateRule.startTokenIndex; + const tokenRanges = allTokens.slice(startTokenIndex, caretTokenIndex + 1); let syntaxContextType: EntityContextType | StmtContextType | undefined = void 0; switch (ruleType) {