fix(editor): improve ASE SQL semantic highlighting

This commit is contained in:
zipg authored and GitHub committed 2026-07-16 17:00:55 +08:00
1 parent df1d31e74f
commit 3d23a58e36
14 files changed
+360 -22

No files matched your search

+75 -1
View File
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { sqlSemanticCompletionScope, sqlSemanticLocalColumnsByTable, sqlSemanticProjectionAliasColumns } from "@/lib/sql/semantic/completion";
import { SQL_SEMANTIC_BASELINE_FIXTURES, sqlFixtureCursor } from "@/lib/sql/semantic/fixtures";
import { buildSqlSemanticModel } from "@/lib/sql/semantic/model";
import { buildSqlSemanticModel, sqlSemanticTableNameSpans } from "@/lib/sql/semantic/model";
describe("sqlSemanticModel baseline fixtures", () => {
for (const fixture of SQL_SEMANTIC_BASELINE_FIXTURES) {
@@ -255,4 +255,78 @@ describe("sqlSemanticModel baseline fixtures", () => {
expect(sqlSemanticCompletionScope(buildSqlSemanticModel(sqlite.sql, sqlite.cursor, { databaseType: "sqlite" })).useRemoteMetadata).toBe(true);
expect(buildSqlSemanticModel(duckdb.sql, duckdb.cursor, { databaseType: "duckdb" }).rowSources[0]).toEqual(expect.objectContaining({ kind: "table_function", name: "csv", alias: "csv" }));
});
it("returns only concrete table-name spans for semantic highlighting", () => {
const sql = "SELECT customer_id FROM dbo.wfAdmin AS wa WHERE wa.customer_id > 0";
const spans = sqlSemanticTableNameSpans(sql, { dialect: "sqlserver" });
expect(spans.map((span) => sql.slice(span.start, span.end))).toEqual(["wfAdmin"]);
});
it("finds table names across statements, subqueries, and comma table lists", () => {
const sql = "SELECT * FROM users u, orders o; SELECT * FROM (SELECT * FROM audit_log) a JOIN dbo.events e ON e.id = a.id";
const spans = sqlSemanticTableNameSpans(sql, { dialect: "sqlserver" });
expect(spans.map((span) => sql.slice(span.start, span.end))).toEqual(["users", "orders", "audit_log", "events"]);
});
it("highlights mutation targets but ignores strings, comments, and table functions", () => {
const sql = "UPDATE users SET name = 'FROM fake'; INSERT INTO audit_log(id) VALUES (1); -- FROM ignored\nSELECT * FROM read_csv('events.csv') e";
const spans = sqlSemanticTableNameSpans(sql);
expect(spans.map((span) => sql.slice(span.start, span.end))).toEqual(["users", "audit_log"]);
});
it("skips ASE maintenance keywords before update table targets", () => {
const statements = [
{ sql: "UPDATE STATISTICS wfAdmin", table: "wfAdmin" },
{ sql: "UPDATE INDEX STATISTICS wfAdmin ix_name", table: "wfAdmin" },
{ sql: "UPDATE TABLE STATISTICS dbo.wfAdmin", table: "wfAdmin" },
{ sql: "UPDATE ALL STATISTICS [dbo].[wfAdmin]", table: "wfAdmin" },
];
for (const { sql, table } of statements) {
const spans = sqlSemanticTableNameSpans(sql, { dialect: "sqlserver" });
const model = buildSqlSemanticModel(sql, sql.length, { dialect: "sqlserver" });
expect(spans.map((span) => sql.slice(span.start, span.end))).toEqual([sql.includes("[wfAdmin]") ? "[wfAdmin]" : table]);
expect(model.rowSources).toEqual(expect.arrayContaining([expect.objectContaining({ name: table, kind: "mutation_target" })]));
expect(model.rowSources.some((source) => ["ALL", "INDEX", "TABLE", "STATISTICS"].includes(source.name.toUpperCase()))).toBe(false);
}
});
it("does not treat MERGE branch UPDATE as a table introducer", () => {
const sql = "MERGE INTO target_table t USING source_table s ON t.id = s.id WHEN MATCHED THEN UPDATE SET t.name = s.name;";
const spans = sqlSemanticTableNameSpans(sql, { dialect: "sqlserver" });
const model = buildSqlSemanticModel(sql, sql.length - 1, { dialect: "sqlserver" });
expect(spans.map((span) => sql.slice(span.start, span.end))).toEqual(["target_table", "source_table"]);
expect(model.rowSources.some((source) => source.name.toLowerCase() === "set")).toBe(false);
});
it("does not treat MySQL upsert UPDATE as a table introducer", () => {
const sql = "INSERT INTO users (id, name) VALUES (1, 'A') ON DUPLICATE KEY UPDATE name = VALUES(name);";
const spans = sqlSemanticTableNameSpans(sql, { dialect: "mysql" });
const model = buildSqlSemanticModel(sql, sql.length - 1, { dialect: "mysql" });
expect(spans.map((span) => sql.slice(span.start, span.end))).toEqual(["users"]);
expect(model.rowSources.some((source) => source.name === "name")).toBe(false);
});
it("keeps CTE UPDATE mutation targets", () => {
const sql = "WITH candidates AS (SELECT id FROM staging) UPDATE users SET active = 1 WHERE id IN (SELECT id FROM candidates);";
const spans = sqlSemanticTableNameSpans(sql, { dialect: "sqlserver" });
const model = buildSqlSemanticModel(sql, sql.length - 1, { dialect: "sqlserver" });
expect(spans.map((span) => sql.slice(span.start, span.end))).toEqual(["staging", "users", "candidates"]);
expect(model.rowSources).toEqual(expect.arrayContaining([expect.objectContaining({ name: "users", kind: "mutation_target" })]));
});
it("recognizes SQL Server local, global, and tempdb-qualified temporary tables", () => {
const sql = "SELECT * FROM #temp; SELECT * FROM ##global_temp; SELECT * FROM tempdb..#temp";
const spans = sqlSemanticTableNameSpans(sql, { dialect: "sqlserver" });
const model = buildSqlSemanticModel(sql, sql.length, { dialect: "sqlserver" });
expect(spans.map((span) => sql.slice(span.start, span.end))).toEqual(["#temp", "##global_temp", "#temp"]);
expect(model.rowSources).toEqual(expect.arrayContaining([expect.objectContaining({ name: "#temp", kind: "table" })]));
});
});
@@ -21,6 +21,15 @@ describe("sqlSemanticTokens", () => {
expect(isSuppressedSqlSemanticContext(tokens, "select * from users".length)).toBe(false);
});
it("treats hash prefixes as identifiers for SQL Server but comments for MySQL", () => {
const sqlServerSql = "SELECT * FROM #temp; SELECT * FROM ##global_temp; SELECT * FROM tempdb..#temp";
const sqlServerTokens = tokenizeSqlSemantic(sqlServerSql, "sqlserver");
expect(sqlServerTokens.filter((token) => token.kind === "word" && token.text.startsWith("#")).map((token) => token.text)).toEqual(["#temp", "##global_temp", "#temp"]);
expect(sqlServerTokens.some((token) => token.kind === "comment")).toBe(false);
expect(tokenizeSqlSemantic("SELECT 1 # comment", "mysql").some((token) => token.kind === "comment" && token.text === "# comment")).toBe(true);
});
it("finds active statement spans across semicolon-separated scripts", () => {
const sql = "select * from users;\nselect * from orders where id = 1;";
const cursor = sql.indexOf("orders");
+14 -1
View File
@@ -1,7 +1,7 @@
import type { ConnectionConfig, DatabaseType } from "@/types/database";
import { isSchemaAware, usesDatabaseObjectTreeMode, usesTreeSchemaMode } from "@/lib/database/databaseFeatureSupport";
type JdbcDialectConnection = Pick<ConnectionConfig, "db_type"> & Partial<Pick<ConnectionConfig, "driver_profile" | "connection_string" | "jdbc_driver_class" | "jdbc_driver_paths">>;
type JdbcDialectConnection = Pick<ConnectionConfig, "db_type"> & Partial<Pick<ConnectionConfig, "driver_profile" | "driver_label" | "connection_string" | "jdbc_driver_class" | "jdbc_driver_paths" | "database_info">>;
const DATABASE_AS_EXECUTION_SCHEMA_TYPES = new Set<DatabaseType>(["hive", "spark"]);
@@ -23,6 +23,11 @@ const JDBC_DIALECT_MATCHERS: Array<{ type: DatabaseType; patterns: RegExp[] }> =
{ type: "iris", patterns: [/jdbc:(?:iris|cache):/i, /com\.intersystems\.jdbc\.(?:IRIS|Cache)Driver/i, /intersystems-jdbc/i] },
];
// ASE uses Transact-SQL, but treating it as SQL Server globally would also
// enable SQL Server metadata, pagination, and identifier rules. Keep this
// narrower matcher exclusively for editor syntax parsing.
const JDBC_ASE_PROFILE_PATTERNS = [/(?:^|[\s_-])ase(?:$|[\s_-])/i, /\bsap[\s_-]+ase\b/i, /\badaptive server enterprise\b/i];
export function inferJdbcDialect(connection?: JdbcDialectConnection): DatabaseType | undefined {
if (!connection || connection.db_type !== "jdbc") return undefined;
const haystack = [connection.driver_profile, connection.connection_string, connection.jdbc_driver_class, ...(connection.jdbc_driver_paths ?? [])].filter(Boolean).join("\n");
@@ -106,6 +111,14 @@ export function codeMirrorSqlDialect(dbType: DatabaseType | undefined): "mysql"
return "mysql";
}
export function codeMirrorSqlDialectForConnection(connection?: JdbcDialectConnection): "mysql" | "postgres" | "sqlserver" {
if (connection?.db_type === "jdbc") {
const explicitIdentity = [connection.driver_profile, connection.driver_label, connection.database_info?.productName].filter(Boolean).join("\n");
if (JDBC_ASE_PROFILE_PATTERNS.some((pattern) => pattern.test(explicitIdentity))) return "sqlserver";
}
return codeMirrorSqlDialect(effectiveDatabaseTypeForConnection(connection));
}
function isGbase8sProfile(driverProfile?: string): boolean {
return driverProfile === "gbase8s";
}
@@ -66,6 +66,7 @@ function codeMirrorBaseDialect(langSql: CodeMirrorSqlLanguageModule, dialectName
if (SQLITE_CODEMIRROR_DATABASE_TYPES.has(databaseType)) return langSql.SQLite;
if (databaseType === "sqlserver") return langSql.MSSQL;
if (databaseType === "cassandra") return langSql.Cassandra;
if (databaseType === "jdbc" && dialectName === "sqlserver") return langSql.MSSQL;
return langSql.StandardSQL;
}
return dialectName === "postgres" ? langSql.PostgreSQL : dialectName === "sqlserver" ? langSql.MSSQL : langSql.MySQL;
@@ -0,0 +1,39 @@
import { sqlSemanticTableNameSpans } from "@/lib/sql/semantic/model";
import type { SqlSemanticBuildOptions, SqlSemanticSpan } from "@/lib/sql/semantic/types";
type SqlSyntaxTree = ReturnType<typeof import("@codemirror/language").syntaxTree>;
const SUPPRESSED_NODE_NAMES = new Set(["String", "LineComment", "BlockComment"]);
export interface SqlSemanticHighlightWindow {
from: number;
to: number;
}
function maskSuppressedSyntax(sql: string, window: SqlSemanticHighlightWindow, tree: SqlSyntaxTree): string {
const ranges: SqlSemanticHighlightWindow[] = [];
tree.iterate({
from: window.from,
to: window.to,
enter(node) {
if (!SUPPRESSED_NODE_NAMES.has(node.name)) return;
ranges.push({ from: Math.max(window.from, node.from), to: Math.min(window.to, node.to) });
return false;
},
});
let cursor = window.from;
let masked = "";
for (const range of ranges) {
if (range.from < cursor) continue;
masked += sql.slice(cursor, range.from);
masked += sql.slice(range.from, range.to).replace(/[^\r\n]/g, " ");
cursor = range.to;
}
return masked + sql.slice(cursor, window.to);
}
export function sqlSemanticTableNameSpansForSyntaxTree(sql: string, window: SqlSemanticHighlightWindow, tree: SqlSyntaxTree, options: SqlSemanticBuildOptions = {}): SqlSemanticSpan[] {
const maskedSql = maskSuppressedSyntax(sql, window, tree);
return sqlSemanticTableNameSpans(maskedSql, options).map((span) => ({ start: span.start + window.from, end: span.end + window.from }));
}
+21 -4
View File
@@ -9,6 +9,7 @@ type LucideIconNode = Array<[string, Record<string, string>]>;
export const EDITOR_FONT_SIZE_CSS_VAR = "--dbx-editor-font-size";
export const EDITOR_FONT_FAMILY_CSS_VAR = "--dbx-editor-font-family";
export const SQL_TABLE_COLOR_CSS_VAR = "--dbx-sql-table-color";
const EDITOR_SELECTION_BACKGROUND_CSS_VAR = "--dbx-editor-selection-background";
export function createRunStatementButtonDom(ariaLabel = "Execute statement"): HTMLButtonElement {
@@ -21,6 +22,14 @@ export function createRunStatementButtonDom(ariaLabel = "Execute statement"): HT
return marker;
}
export function sqlSemanticHighlightTheme(EditorView: typeof import("@codemirror/view").EditorView): Extension {
return EditorView.theme({
".cm-sql-table-name, .cm-sql-table-name *": {
color: `var(${SQL_TABLE_COLOR_CSS_VAR}) !important`,
},
});
}
const SUPPORTS_COLOR_MIX = typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("color", "color-mix(in oklch, black 50%, white)");
const SUPPORTS_OKLCH = typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("color", "oklch(0.62 0.19 255)");
@@ -72,11 +81,8 @@ function createCustomTheme(EditorView: typeof import("@codemirror/view").EditorV
c.variable = colors.field;
c.property = colors.field;
}
if (colors.table) {
// 表名通常被识别为 propertyName,如果单独设置了表名颜色则覆盖
c.property = colors.table;
}
}
const tableColor = colors?.table || c.property;
const theme = EditorView.theme(
{
@@ -84,6 +90,7 @@ function createCustomTheme(EditorView: typeof import("@codemirror/view").EditorV
backgroundColor: c.background,
color: c.foreground,
[EDITOR_SELECTION_BACKGROUND_CSS_VAR]: c.selection,
[SQL_TABLE_COLOR_CSS_VAR]: tableColor,
},
".cm-content": {
caretColor: c.cursor,
@@ -212,6 +219,7 @@ type IdeEditorThemeColors = {
operator: string;
punctuation: string;
property: string;
table: string;
builtin: string;
meta: string;
invalid: string;
@@ -247,6 +255,7 @@ const IDE_EDITOR_THEMES = {
operator: "#080808",
punctuation: "#080808",
property: "#871094",
table: "#871094",
builtin: "#0033b3",
meta: "#9e880d",
invalid: "#f50000",
@@ -280,6 +289,7 @@ const IDE_EDITOR_THEMES = {
operator: "#cc7832",
punctuation: "#a9b7c6",
property: "#9876aa",
table: "#9876aa",
builtin: "#cc7832",
meta: "#bbb529",
invalid: "#ff0000",
@@ -310,6 +320,7 @@ const IDE_EDITOR_THEMES = {
operator: "#080808",
punctuation: "#080808",
property: "#871094",
table: "#871094",
builtin: "#0033b3",
meta: "#9e880d",
invalid: "#f50000",
@@ -343,6 +354,7 @@ const IDE_EDITOR_THEMES = {
operator: "#bcbec4",
punctuation: "#bcbec4",
property: "#c77dbb",
table: "#c77dbb",
builtin: "#cf8e6d",
meta: "#b3ae60",
invalid: "#f75464",
@@ -373,6 +385,7 @@ const IDE_EDITOR_THEMES = {
operator: "#b3003f",
punctuation: "#141414eb",
property: "#1f8a65",
table: "#1f8a65",
builtin: "#206595",
meta: "#1f8a65",
invalid: "#cf2d56",
@@ -403,6 +416,7 @@ const IDE_EDITOR_THEMES = {
operator: "#d6d6dd",
punctuation: "#d6d6dd",
property: "#82d2ce",
table: "#aaa0fa",
builtin: "#a8cc7c",
meta: "#a8cc7c",
invalid: "#e34671",
@@ -433,6 +447,7 @@ const IDE_EDITOR_THEMES = {
operator: "#6b5544",
punctuation: "#6b5544",
property: "#9a4f2e",
table: "#7a5aa8",
builtin: "#4f7d5d",
meta: "#8f6b2e",
invalid: "#c3493d",
@@ -463,6 +478,7 @@ const IDE_EDITOR_THEMES = {
operator: "#e0d0c0",
punctuation: "#c9b9a7",
property: "#d28a5f",
table: "#a08fcd",
builtin: "#74b195",
meta: "#d4ae63",
invalid: "#e2675f",
@@ -479,6 +495,7 @@ function createIdeEditorTheme(EditorView: typeof import("@codemirror/view").Edit
backgroundColor: c.background,
color: c.foreground,
[EDITOR_SELECTION_BACKGROUND_CSS_VAR]: c.selection,
[SQL_TABLE_COLOR_CSS_VAR]: c.table,
},
".cm-scroller": {
backgroundColor: c.background,
@@ -44,7 +44,7 @@ export function hiddenResultColumnIndexes(columns: string[], projections: Hidden
function appendSelectProjections(sql: string, expressions: string[], databaseType: DatabaseType): string | undefined {
if (expressions.length === 0) return sql;
const tokens = tokenizeSqlSemantic(sql);
const tokens = tokenizeSqlSemantic(sql, sqlSemanticDialectFor({ databaseType }).id);
const selectIndex = tokens.findIndex((token) => token.kind === "word" && token.depth === 0 && token.normalized === "select");
if (selectIndex < 0) return undefined;
const fromIndex = tokens.findIndex((token, index) => index > selectIndex && token.kind === "word" && token.depth === 0 && token.normalized === "from");
+78 -1
View File
@@ -22,6 +22,10 @@ const JOIN_MODIFIERS = new Set(["left", "right", "inner", "outer", "cross", "ful
const CLAUSE_BOUNDARIES = new Set(["where", "group", "having", "order", "limit", "offset", "union", "intersect", "except", "on", "set", "values", "returning"]);
const FROM_CLAUSE_BOUNDARIES = new Set([...CLAUSE_BOUNDARIES, "window", "qualify", "fetch", "for", "connect", "start", "model"].filter((item) => item !== "on"));
const ALIAS_BLACKLIST = new Set([...CLAUSE_BOUNDARIES, "join", "straight_join", "left", "right", "inner", "outer", "cross", "full", "natural", "as", "select", "from", "with"]);
const TABLE_TARGET_MODIFIERS = new Set(["lateral", "only"]);
const TABLE_FUNCTION_INTRODUCERS = new Set(["from", "join", "straight_join", "apply"]);
const TOP_LEVEL_STATEMENT_WORDS = new Set(["select", "insert", "delete", "merge", "create", "alter", "drop", "truncate", "call", "exec", "execute", "grant", "revoke"]);
const SQLSERVER_UPDATE_STATISTICS_SCOPES = new Set(["all", "index", "table"]);
interface ParseState {
dialect: SqlSemanticDialectAdapter;
@@ -81,6 +85,9 @@ function readQualifiedName(tokens: readonly SqlSemanticToken[], startIndex: numb
break;
}
index += 2;
if (dialect.id === "sqlserver") {
while (tokens[index]?.text === ".") index += 1;
}
}
if (parts.length === 0) return null;
return {
@@ -92,6 +99,74 @@ function readQualifiedName(tokens: readonly SqlSemanticToken[], startIndex: numb
};
}
function sqlServerMaintenanceTableTarget(tokens: readonly SqlSemanticToken[], target: number, introducer: string, dialect: SqlSemanticDialectAdapter): number {
if (dialect.id !== "sqlserver" || introducer !== "update") return target;
if (tokens[target]?.normalized === "statistics") return target + 1;
// ASE accepts UPDATE {ALL | INDEX | TABLE} STATISTICS; these scope words
// describe the maintenance operation and must never become table targets.
if (SQLSERVER_UPDATE_STATISTICS_SCOPES.has(tokens[target]?.normalized ?? "") && tokens[target + 1]?.normalized === "statistics") return target + 2;
return target;
}
function updateIntroducesMutationTarget(tokens: readonly SqlSemanticToken[], updateIndex: number): boolean {
const update = tokens[updateIndex];
if (update?.kind !== "word" || update.normalized !== "update") return false;
for (let index = updateIndex - 1; index >= 0; index -= 1) {
const item = tokens[index];
if (!item || item.depth !== update.depth) continue;
if (item.text === ";") break;
if (item.kind === "word" && (item.normalized === "update" || TOP_LEVEL_STATEMENT_WORDS.has(item.normalized))) return false;
}
return true;
}
function commaContinuesTableList(tokens: readonly SqlSemanticToken[], commaIndex: number): boolean {
const comma = tokens[commaIndex];
if (comma?.text !== ",") return false;
for (let index = commaIndex - 1; index >= 0; index -= 1) {
const item = tokens[index];
if (!item || item.depth !== comma.depth || item.kind !== "word") continue;
if (item.normalized === "from") return true;
if (item.normalized === "select" || item.normalized === "join" || TABLE_INTRODUCERS.has(item.normalized) || CLAUSE_BOUNDARIES.has(item.normalized)) return false;
}
return false;
}
/**
* Finds concrete table-name tokens for visual highlighting without consulting
* metadata. Only the final identifier in a qualified name is returned, so
* schemas/catalogs and aliases keep the regular identifier color.
*/
export function sqlSemanticTableNameSpans(sql: string, options: SqlSemanticBuildOptions = {}): SqlSemanticSpan[] {
const dialect = sqlSemanticDialectFor(options);
const tokens = significantTokens(tokenizeSqlSemantic(sql, dialect.id));
const spans: SqlSemanticSpan[] = [];
const seen = new Set<string>();
for (let index = 0; index < tokens.length; index += 1) {
const item = tokens[index];
const introduced = item?.kind === "word" && TABLE_INTRODUCERS.has(item.normalized) && (item.normalized !== "update" || updateIntroducesMutationTarget(tokens, index));
if (!introduced && !commaContinuesTableList(tokens, index)) continue;
let target = index + 1;
while (TABLE_TARGET_MODIFIERS.has(tokens[target]?.normalized ?? "")) target += 1;
target = sqlServerMaintenanceTableTarget(tokens, target, item?.normalized ?? "", dialect);
if (tokens[target]?.text === "(") continue;
const qualified = readQualifiedName(tokens, target, dialect);
const followedByParenthesis = qualified && tokens[qualified.nextIndex]?.text === "(";
if (!qualified || tokens[target]?.depth !== item?.depth || (followedByParenthesis && (!introduced || TABLE_FUNCTION_INTRODUCERS.has(item.normalized)))) continue;
const tablePart = qualified.name.parts[qualified.name.parts.length - 1];
if (!tablePart) continue;
const key = `${tablePart.span.start}:${tablePart.span.end}`;
if (seen.has(key)) continue;
seen.add(key);
spans.push(tablePart.span);
}
return spans;
}
function sourceNameFromQualifiedName(name: SqlSemanticQualifiedName): { name: string; qualifierParts: string[] } {
const parts = name.parts.map((part) => part.name);
return {
@@ -379,9 +454,11 @@ function parseRowSources(state: ParseState): SqlSemanticRowSource[] {
if (item.kind !== "word") continue;
const normalized = item.normalized;
if (!TABLE_INTRODUCERS.has(normalized)) continue;
if (normalized === "update" && !updateIntroducesMutationTarget(state.tokens, index)) continue;
if (JOIN_MODIFIERS.has(normalized)) continue;
let target = index + 1;
while (JOIN_MODIFIERS.has(state.tokens[target]?.normalized ?? "")) target += 1;
target = sqlServerMaintenanceTableTarget(state.tokens, target, normalized, state.dialect);
for (;;) {
const parsed = parseRowSource(state, target, normalized, sources.length);
if (!parsed) break;
@@ -614,7 +691,7 @@ function buildScope(statement: SqlSemanticStatement, rowSources: SqlSemanticRowS
export function buildSqlSemanticModel(sql: string, cursor: number, options: SqlSemanticBuildOptions = {}): SqlSemanticModel {
const safeCursor = Math.max(0, Math.min(cursor, sql.length));
const dialect = sqlSemanticDialectFor(options);
const allTokens = tokenizeSqlSemantic(sql);
const allTokens = tokenizeSqlSemantic(sql, dialect.id);
const statementSpan = findActiveSqlStatementSpan(sql, allTokens, safeCursor);
const tokens = significantTokens(allTokens.filter((item) => item.span.end > statementSpan.start && item.span.start < statementSpan.end));
const kind = statementKind(tokens);
+2 -2
View File
@@ -29,7 +29,7 @@ function readQuoted(input: string, start: number, open: string, close: string):
return input.length;
}
export function tokenizeSqlSemantic(input: string): SqlSemanticToken[] {
export function tokenizeSqlSemantic(input: string, dialectId = "mysql"): SqlSemanticToken[] {
const tokens: SqlSemanticToken[] = [];
let index = 0;
let depth = 0;
@@ -51,7 +51,7 @@ export function tokenizeSqlSemantic(input: string): SqlSemanticToken[] {
continue;
}
if (ch === "#") {
if (ch === "#" && dialectId === "mysql") {
index += 1;
while (index < input.length && input[index] !== "\n" && input[index] !== "\r") index += 1;
tokens.push(token("comment", input.slice(start, index), start, index, depth));