feat(sql): add placeholders to built-in snippets
This commit is contained in:
parent
c8464bf842
commit
fa29d4bbde
|
|
@ -7,14 +7,74 @@ const TEST_SNIPPETS: SqlSnippet[] = [
|
|||
{ id: "2", label: "insert row", prefix: "ins", body: "INSERT INTO my_table VALUES (1);" },
|
||||
];
|
||||
|
||||
const BUILTIN_SELECT: SqlSnippet = { id: "builtin-sel", label: "select *", prefix: "sel", body: "SELECT *\nFROM table\nLIMIT 100;" };
|
||||
const BUILTIN_CREATE_TABLE: SqlSnippet = { id: "builtin-ct", label: "create table", prefix: "ct", body: "CREATE TABLE table (\n column type\n);" };
|
||||
const BUILTIN_UPDATE: SqlSnippet = { id: "builtin-upd", label: "update set", prefix: "upd", body: "UPDATE table\nSET column = value\nWHERE condition;" };
|
||||
const BUILTIN_CTE: SqlSnippet = { id: "builtin-cte", label: "common table expression", prefix: "cte", body: "WITH name AS (\n SELECT columns\n FROM table\n)\nSELECT *\nFROM name;" };
|
||||
const BUILTIN_INSERT: SqlSnippet = { id: "builtin-ins", label: "insert into", prefix: "ins", body: "INSERT INTO table (columns)\nVALUES (values);" };
|
||||
const BUILTIN_JOIN: SqlSnippet = { id: "builtin-join", label: "join", prefix: "join", body: "JOIN table ON left_column = right_column" };
|
||||
const BUILTIN_CASE: SqlSnippet = { id: "builtin-case", label: "case when", prefix: "case", body: "CASE\n WHEN condition THEN value\n ELSE default\nEND" };
|
||||
const BUILTIN_CREATE_INDEX: SqlSnippet = { id: "builtin-ci", label: "create index", prefix: "ci", body: "CREATE INDEX idx_name\nON table (column);" };
|
||||
|
||||
describe("buildSnippetItems", () => {
|
||||
it("returns matching snippet by prefix", () => {
|
||||
const items = buildSnippetItemsForTest("sel", TEST_SNIPPETS);
|
||||
expect(items).toHaveLength(1);
|
||||
expect(items[0].label).toBe("select all");
|
||||
expect(items[0].detail).toBe("SELECT *\nFROM my_table;");
|
||||
expect(items[0].apply).toBe("SELECT *\nFROM my_table;");
|
||||
});
|
||||
|
||||
it("uses CodeMirror placeholders for the built-in select snippet", () => {
|
||||
const items = buildSnippetItemsForTest("sel", [BUILTIN_SELECT]);
|
||||
|
||||
expect(items[0].detail).toBe("SELECT *\nFROM table\nLIMIT 100;");
|
||||
expect(items[0].apply).toBe("SELECT *\nFROM ${table}\nLIMIT 100;");
|
||||
});
|
||||
|
||||
it("replaces placeholder words but preserves uppercase SQL keywords", () => {
|
||||
const items = buildSnippetItemsForTest("ct", [BUILTIN_CREATE_TABLE]);
|
||||
|
||||
expect(items[0].apply).toBe("CREATE TABLE ${table} (\n ${column} ${type}\n);");
|
||||
});
|
||||
|
||||
it("adds placeholders across built-in update snippets", () => {
|
||||
const items = buildSnippetItemsForTest("upd", [BUILTIN_UPDATE]);
|
||||
|
||||
expect(items[0].apply).toBe("UPDATE ${table}\nSET ${column} = ${value}\nWHERE ${condition};");
|
||||
});
|
||||
|
||||
it("keeps keyword casing separate from editable placeholders", () => {
|
||||
const items = buildSnippetItemsForTest("sel", [BUILTIN_SELECT], "lower");
|
||||
|
||||
expect(items[0].detail).toBe("select *\nfrom table\nlimit 100;");
|
||||
expect(items[0].apply).toBe("select *\nfrom ${table}\nlimit 100;");
|
||||
});
|
||||
|
||||
it("determines placeholders from original casing even when keywords are lowercased", () => {
|
||||
const items = buildSnippetItemsForTest("ct", [BUILTIN_CREATE_TABLE], "lower");
|
||||
|
||||
expect(items[0].detail).toBe("create table table (\n column type\n);");
|
||||
expect(items[0].apply).toBe("create table ${table} (\n ${column} ${type}\n);");
|
||||
});
|
||||
|
||||
it("wraps repeated placeholder words in CTE snippets", () => {
|
||||
const items = buildSnippetItemsForTest("cte", [BUILTIN_CTE]);
|
||||
|
||||
expect(items[0].apply).toBe("WITH ${name} AS (\n SELECT ${columns}\n FROM ${table}\n)\nSELECT *\nFROM ${name};");
|
||||
});
|
||||
|
||||
it.each([
|
||||
[BUILTIN_INSERT, "INSERT INTO ${table} (${columns})\nVALUES (${values});"],
|
||||
[BUILTIN_JOIN, "JOIN ${table} ON ${left_column} = ${right_column}"],
|
||||
[BUILTIN_CASE, "CASE\n WHEN ${condition} THEN ${value}\n ELSE ${default}\nEND"],
|
||||
[BUILTIN_CREATE_INDEX, "CREATE INDEX ${idx_name}\nON ${table} (${column});"],
|
||||
])("adds placeholders for built-in %s snippet", (snippet, expectedApply) => {
|
||||
const items = buildSnippetItemsForTest(snippet.prefix, [snippet]);
|
||||
|
||||
expect(items[0].apply).toBe(expectedApply);
|
||||
});
|
||||
|
||||
it("returns matching snippet by label substring", () => {
|
||||
const items = buildSnippetItemsForTest("select", TEST_SNIPPETS);
|
||||
expect(items).toHaveLength(1);
|
||||
|
|
|
|||
|
|
@ -2523,10 +2523,17 @@ function shouldFormatBuiltinSnippet(snippet: SqlSnippet): boolean {
|
|||
return snippet.id.startsWith("builtin-");
|
||||
}
|
||||
|
||||
function applyBuiltinSnippetKeywordCase(snippet: SqlSnippet, keywordCase?: SqlKeywordCase): string {
|
||||
function applyBuiltinSnippetKeywordCase(snippet: SqlSnippet, text: string, keywordCase?: SqlKeywordCase): string {
|
||||
if (!shouldFormatBuiltinSnippet(snippet)) return text;
|
||||
if (keywordCase === "lower") return text.toLowerCase();
|
||||
return text;
|
||||
}
|
||||
|
||||
const BUILTIN_SNIPPET_PLACEHOLDER_RE = /\b(idx_name|left_column|right_column|columns|values|condition|column|default|value|name|type|table)\b/g;
|
||||
|
||||
function applyBuiltinSnippetPlaceholders(snippet: SqlSnippet): string {
|
||||
if (!shouldFormatBuiltinSnippet(snippet)) return snippet.body;
|
||||
if (keywordCase === "lower") return snippet.body.toLowerCase();
|
||||
return snippet.body;
|
||||
return snippet.body.replace(BUILTIN_SNIPPET_PLACEHOLDER_RE, (match) => `\${${match}}`);
|
||||
}
|
||||
|
||||
function buildPreferredKeywordItems(prefix: string, keywords: string[], keywordCase?: SqlKeywordCase): SqlCompletionItem[] {
|
||||
|
|
@ -3209,8 +3216,8 @@ function singularTableName(name: string): string {
|
|||
return lower;
|
||||
}
|
||||
|
||||
export function buildSnippetItemsForTest(prefix: string, snippets: SqlSnippet[]): SqlCompletionItem[] {
|
||||
return buildSnippetItems(prefix, snippets);
|
||||
export function buildSnippetItemsForTest(prefix: string, snippets: SqlSnippet[], keywordCase?: SqlKeywordCase): SqlCompletionItem[] {
|
||||
return buildSnippetItems(prefix, snippets, keywordCase);
|
||||
}
|
||||
|
||||
function buildSnippetItems(prefix: string, snippets: SqlSnippet[], keywordCase?: SqlKeywordCase): SqlCompletionItem[] {
|
||||
|
|
@ -3229,12 +3236,15 @@ function buildSnippetItems(prefix: string, snippets: SqlSnippet[], keywordCase?:
|
|||
// they are likely typing the actual keyword — reduce the base boost so
|
||||
// the real keyword can rank higher.
|
||||
const baseBoost = matchesByPrefix ? 4000 : 0;
|
||||
const body = applyBuiltinSnippetKeywordCase(snippet, keywordCase);
|
||||
// Placeholder replacement runs on the original (UPPER-case) body first,
|
||||
// then keyword casing is applied to both variants uniformly.
|
||||
const body = applyBuiltinSnippetKeywordCase(snippet, snippet.body, keywordCase);
|
||||
const apply = applyBuiltinSnippetKeywordCase(snippet, applyBuiltinSnippetPlaceholders(snippet), keywordCase);
|
||||
return {
|
||||
label: snippet.label,
|
||||
type: "snippet" as const,
|
||||
detail: body,
|
||||
apply: body,
|
||||
apply,
|
||||
boost: Math.max(boostByPrefix, boostByLabel) + baseBoost,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -788,7 +788,8 @@ test("suggests SQL snippets for common abbreviations", () => {
|
|||
|
||||
const snippet = items.find((item) => item.type === "snippet" && item.label === "select *");
|
||||
assert.ok(snippet);
|
||||
assert.equal(snippet.apply, "SELECT *\nFROM table\nLIMIT 100;");
|
||||
assert.equal(snippet.detail, "SELECT *\nFROM table\nLIMIT 100;");
|
||||
assert.equal(snippet.apply, "SELECT *\nFROM ${table}\nLIMIT 100;");
|
||||
});
|
||||
|
||||
test("applies keyword case to built-in SQL snippets", () => {
|
||||
|
|
@ -800,7 +801,8 @@ test("applies keyword case to built-in SQL snippets", () => {
|
|||
|
||||
const snippet = items.find((item) => item.type === "snippet" && item.label === "select *");
|
||||
assert.ok(snippet);
|
||||
assert.equal(snippet.apply, "select *\nfrom table\nlimit 100;");
|
||||
assert.equal(snippet.detail, "select *\nfrom table\nlimit 100;");
|
||||
assert.equal(snippet.apply, "select *\nfrom ${table}\nlimit 100;");
|
||||
});
|
||||
|
||||
test("suggests DATE_FORMAT as parameter snippet", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue