mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
This commit is contained in:
@@ -145,4 +145,26 @@ describe("codemirrorSqlDialect", () => {
|
||||
expect(nodeNameAt(dialect, doubleQuoteStatement, '"escaped\\"quote"')).toBe("String");
|
||||
expect(nodeNameAt(dialect, doubleQuoteStatement, "col")).toBe("Identifier");
|
||||
});
|
||||
|
||||
it("tags QuotedIdentifier as tags.name so quoted columns/tables match unquoted identifiers (#12122)", async () => {
|
||||
const { highlightTree, tags } = await import("@lezer/highlight");
|
||||
const { HighlightStyle } = await import("@codemirror/language");
|
||||
|
||||
const style = HighlightStyle.define([
|
||||
{ tag: tags.keyword, color: "keyword" },
|
||||
{ tag: tags.string, color: "string" },
|
||||
{ tag: tags.name, color: "identifier" },
|
||||
]);
|
||||
|
||||
const mysqlDialect = createDbxCodeMirrorSqlDialect(langSql, "mysql", "mysql");
|
||||
const doc = "SELECT `quoted_col`, plain_col, 'literal_string' FROM `tbl`";
|
||||
const tree = mysqlDialect.language.parser.parse(doc);
|
||||
const classes = new Map<string, string>();
|
||||
highlightTree(tree, style, (from, to, cls) => classes.set(doc.slice(from, to), cls));
|
||||
|
||||
expect(classes.get("`quoted_col`")).toBeDefined();
|
||||
expect(classes.get("`quoted_col`")).toBe(classes.get("plain_col"));
|
||||
expect(classes.get("`quoted_col`")).toBe(classes.get("`tbl`"));
|
||||
expect(classes.get("`quoted_col`")).not.toBe(classes.get("'literal_string'"));
|
||||
});
|
||||
});
|
||||
|
||||
+50
-1
@@ -1,5 +1,17 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildEditorFontThemeRules, buildSqlCompletionThemeRules, EDITOR_FONT_FAMILY_CSS_VAR, editorDiagnosticColors, editorThemeAppearanceFor, IDE_EDITOR_THEMES, loadEditorTheme, resolveCustomThemeBackgrounds, resolveEditorTheme, SQL_BUILTIN_HIGHLIGHT_TAG } from "@/lib/editor/editorThemes";
|
||||
import {
|
||||
buildEditorFontThemeRules,
|
||||
buildSqlCompletionThemeRules,
|
||||
EDITOR_FONT_FAMILY_CSS_VAR,
|
||||
editorDiagnosticColors,
|
||||
editorThemeAppearanceFor,
|
||||
IDE_EDITOR_THEMES,
|
||||
loadEditorTheme,
|
||||
resolveCustomThemeBackgrounds,
|
||||
resolveEditorTheme,
|
||||
SQL_BUILTIN_HIGHLIGHT_TAG,
|
||||
SQL_QUOTED_IDENTIFIER_HIGHLIGHT_TAG,
|
||||
} from "@/lib/editor/editorThemes";
|
||||
import { DEFAULT_APP_CUSTOM_UI_COLORS, wcagContrastRatio, type AppThemePalette } from "@/lib/app/appTheme";
|
||||
import { DEFAULT_CUSTOM_THEME_COLORS, type CustomThemeColors, type EditorTheme } from "@/stores/settingsStore";
|
||||
import { createDbxCodeMirrorSqlDialect } from "@/lib/editor/codemirrorSqlDialect";
|
||||
@@ -306,6 +318,43 @@ describe("SQL builtin highlight tag", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("SQL quoted identifier highlighting (#12122)", () => {
|
||||
it("highlights quoted identifiers with identifier/variable color, distinct from string literals", async () => {
|
||||
const { highlightTree, tags } = await import("@lezer/highlight");
|
||||
const { HighlightStyle } = await import("@codemirror/language");
|
||||
const style = HighlightStyle.define([
|
||||
{ tag: tags.keyword, color: "keyword" },
|
||||
{ tag: tags.string, color: "string" },
|
||||
{ tag: [tags.name, tags.variableName, SQL_QUOTED_IDENTIFIER_HIGHLIGHT_TAG], color: "variable" },
|
||||
]);
|
||||
|
||||
const dialects = [
|
||||
{ name: "mysql", sql: "SELECT `col`, col, 'val' FROM `tbl`" },
|
||||
{ name: "postgres", sql: 'SELECT "col", col, \'val\' FROM "tbl"' },
|
||||
{ name: "sqlserver", sql: "SELECT [col], col, 'val' FROM [tbl]" },
|
||||
];
|
||||
|
||||
for (const { name, sql } of dialects) {
|
||||
const dialect = createDbxCodeMirrorSqlDialect(langSql, name as any, name as any);
|
||||
const tree = dialect.language.parser.parse(sql);
|
||||
const classesByToken = new Map<string, string>();
|
||||
highlightTree(tree, style, (from, to, cls) => classesByToken.set(sql.slice(from, to), cls));
|
||||
|
||||
const quotedCol = name === "mysql" ? "`col`" : name === "postgres" ? '"col"' : "[col]";
|
||||
const quotedTbl = name === "mysql" ? "`tbl`" : name === "postgres" ? '"tbl"' : "[tbl]";
|
||||
|
||||
expect(classesByToken.get(quotedCol), `${name} quoted col`).toBe(classesByToken.get("col"));
|
||||
expect(classesByToken.get(quotedCol), `${name} quoted col vs string`).not.toBe(classesByToken.get("'val'"));
|
||||
expect(classesByToken.get(quotedTbl), `${name} quoted tbl`).toBe(classesByToken.get("col"));
|
||||
}
|
||||
});
|
||||
|
||||
it("maps SQL_QUOTED_IDENTIFIER_HIGHLIGHT_TAG to special(string) for theme compatibility", async () => {
|
||||
const { tags } = await import("@lezer/highlight");
|
||||
expect(SQL_QUOTED_IDENTIFIER_HIGHLIGHT_TAG).toBe(tags.special(tags.string));
|
||||
});
|
||||
});
|
||||
|
||||
describe("editor gutters", () => {
|
||||
it("does not let the gutter minimum height cycle against the scroller content height", () => {
|
||||
const rules = buildEditorFontThemeRules();
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { styleTags, tags } from "@lezer/highlight";
|
||||
import type { SQLDialect } from "@codemirror/lang-sql";
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
import { driverProfileSqlBuiltinTerms } from "@/lib/database/driverProfileExtensions";
|
||||
|
||||
// In SQL, quoted identifiers (e.g. `col`, "col", [col]) represent column, table,
|
||||
// and schema names — not string literals (#12122). CodeMirror's SQL grammar tags
|
||||
// QuotedIdentifier as special(string) by default, which themes highlight as a
|
||||
// string literal. We retag QuotedIdentifier as tags.name so all themes (both DBX
|
||||
// custom/IDE themes and standard third-party themes) highlight quoted identifiers
|
||||
// identically to unquoted identifiers.
|
||||
const SQL_DIALECT_LANGUAGE_PROPS = [styleTags({ QuotedIdentifier: tags.name })];
|
||||
|
||||
export type CodeMirrorSqlDialectName = "mysql" | "postgres" | "sqlserver" | "clickhouse" | "soql";
|
||||
|
||||
export function supportsQueryEditorSqlLanguage(databaseType?: DatabaseType): boolean {
|
||||
@@ -368,6 +377,8 @@ export function createDbxCodeMirrorSqlDialect(langSql: CodeMirrorSqlLanguageModu
|
||||
caseInsensitiveIdentifiers: true,
|
||||
identifierQuotes: "",
|
||||
doubleDollarQuotedStrings: false,
|
||||
}).configureLanguage({
|
||||
props: SQL_DIALECT_LANGUAGE_PROPS,
|
||||
});
|
||||
}
|
||||
const baseDialect = codeMirrorBaseDialect(langSql, dialectName, databaseType);
|
||||
@@ -409,5 +420,7 @@ export function createDbxCodeMirrorSqlDialect(langSql: CodeMirrorSqlLanguageModu
|
||||
: {}),
|
||||
...(isPlsql ? { doubleQuotedStrings: false } : {}),
|
||||
doubleDollarQuotedStrings: false,
|
||||
}).configureLanguage({
|
||||
props: SQL_DIALECT_LANGUAGE_PROPS,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,6 +10,13 @@ import { tags } from "@lezer/highlight";
|
||||
// sync with what the SQL tokenizer actually emits.
|
||||
export const SQL_BUILTIN_HIGHLIGHT_TAG = tags.standard(tags.name);
|
||||
|
||||
// @codemirror/lang-sql tags quoted identifiers (e.g. `col`, "col", [col]) as
|
||||
// special(string) by default. In SQL, quoted identifiers represent columns, tables,
|
||||
// and database objects — not string literals (#12122). Both the SQL dialect parser
|
||||
// configuration and DBX theme highlight styles map this token to identifier/variable
|
||||
// color instead of string literal color.
|
||||
export const SQL_QUOTED_IDENTIFIER_HIGHLIGHT_TAG = tags.special(tags.string);
|
||||
|
||||
type CodeMirrorStyleSpec = Parameters<typeof import("@codemirror/view").EditorView.theme>[0];
|
||||
type LucideIconNode = Array<[string, Record<string, string>]>;
|
||||
|
||||
@@ -160,7 +167,7 @@ function createCustomTheme(EditorView: typeof import("@codemirror/view").EditorV
|
||||
{ tag: tags.moduleKeyword, color: c.keyword },
|
||||
{ tag: tags.operatorKeyword, color: c.keyword },
|
||||
{ tag: tags.string, color: c.string },
|
||||
{ tag: tags.special(tags.string), color: c.string },
|
||||
{ tag: SQL_QUOTED_IDENTIFIER_HIGHLIGHT_TAG, color: c.variable },
|
||||
{ tag: tags.number, color: c.number },
|
||||
{ tag: tags.integer, color: c.number },
|
||||
{ tag: tags.float, color: c.number },
|
||||
@@ -204,7 +211,6 @@ function createCustomTheme(EditorView: typeof import("@codemirror/view").EditorV
|
||||
{ tag: tags.namespace, color: c.className },
|
||||
{ tag: tags.macroName, color: c.function },
|
||||
{ tag: tags.literal, color: c.string },
|
||||
{ tag: tags.special(tags.string), color: c.string },
|
||||
{ tag: tags.regexp, color: c.string },
|
||||
{ tag: tags.escape, color: c.string },
|
||||
{ tag: tags.processingInstruction, color: c.keyword },
|
||||
@@ -569,11 +575,11 @@ function createIdeEditorTheme(EditorView: typeof import("@codemirror/view").Edit
|
||||
|
||||
const highlightStyle = HighlightStyle.define([
|
||||
{ tag: [tags.keyword, tags.controlKeyword, tags.definitionKeyword, tags.moduleKeyword, tags.operatorKeyword, tags.modifier, tags.bool, tags.null], color: c.keyword, ...(c.keywordBold ? { fontWeight: "bold" } : {}) },
|
||||
{ tag: [tags.string, tags.special(tags.string), tags.regexp, tags.escape, tags.inserted], color: c.string, ...(c.stringBold ? { fontWeight: "bold" } : {}) },
|
||||
{ tag: [tags.string, tags.regexp, tags.escape, tags.inserted], color: c.string, ...(c.stringBold ? { fontWeight: "bold" } : {}) },
|
||||
{ tag: [tags.number, tags.integer, tags.float], color: c.number, ...(c.numberBold ? { fontWeight: "bold" } : {}) },
|
||||
{ tag: [tags.comment, tags.lineComment, tags.blockComment, tags.quote], color: c.comment, fontStyle: "italic" },
|
||||
{ tag: [tags.typeName, tags.typeOperator, tags.unit], color: c.type },
|
||||
{ tag: [tags.name, tags.variableName, tags.definition(tags.variableName)], color: c.variable },
|
||||
{ tag: [tags.name, tags.variableName, tags.definition(tags.variableName), SQL_QUOTED_IDENTIFIER_HIGHLIGHT_TAG], color: c.variable },
|
||||
{ tag: [tags.function(tags.variableName), tags.function(tags.propertyName), tags.function(tags.name), tags.macroName], color: c.function },
|
||||
{ tag: [SQL_BUILTIN_HIGHLIGHT_TAG, tags.special(tags.name)], color: c.builtin },
|
||||
{ tag: [tags.propertyName, tags.labelName, tags.annotation], color: c.property },
|
||||
|
||||
Reference in New Issue
Block a user