mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-20 00:17:44 +00:00
fix(sqlserver): complete routine parameters from the typed header
This commit is contained in:
@@ -143,6 +143,36 @@ BEGIN
|
||||
expect(completionItems(sql)).toEqual(expect.arrayContaining([expect.objectContaining({ label: "@customer_id", type: "variable" }), expect.objectContaining({ label: "@customer_name", type: "variable" })]));
|
||||
});
|
||||
|
||||
it("suggests procedure parameters in the current routine body", () => {
|
||||
const sql = `CREATE OR ALTER PROCEDURE dbo.find_customer
|
||||
@customer_id AS INT,
|
||||
@customer_name NVARCHAR(100) = NULL OUTPUT
|
||||
AS
|
||||
BEGIN
|
||||
SELECT @customer_`;
|
||||
|
||||
expect(completionItems(sql)).toEqual(expect.arrayContaining([expect.objectContaining({ label: "@customer_id", type: "variable" }), expect.objectContaining({ label: "@customer_name", type: "variable" })]));
|
||||
});
|
||||
|
||||
it("suggests parenthesized function parameters without collecting body references", () => {
|
||||
const sql = `ALTER FUNCTION dbo.customer_label(
|
||||
@customer_id INT,
|
||||
@separator NVARCHAR(10) = N'-'
|
||||
)
|
||||
RETURNS NVARCHAR(200)
|
||||
AS
|
||||
BEGIN
|
||||
DECLARE @label NVARCHAR(200);
|
||||
SET @phantom = 1;
|
||||
SELECT @`;
|
||||
const variables = completionItems(sql)
|
||||
.filter((item) => item.type === "variable")
|
||||
.map((item) => item.label);
|
||||
|
||||
expect(variables).toEqual(expect.arrayContaining(["@customer_id", "@separator", "@label"]));
|
||||
expect(variables).not.toContain("@phantom");
|
||||
});
|
||||
|
||||
it("ignores later declarations and declarations inside comments or strings", () => {
|
||||
const sql = `DECLARE @visible INT;
|
||||
-- DECLARE @commented INT;
|
||||
|
||||
@@ -2651,6 +2651,15 @@ function collectSqlServerLocalVariables(sql: string, cursor: number, options: Sq
|
||||
const tokens = tokenizeSqlSemantic(batchSql, "sqlserver").filter((token) => token.kind !== "comment" && token.kind !== "string");
|
||||
const variables: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
const addVariable = (variable: string) => {
|
||||
if (!/^@[A-Za-z_@$#][\w@$#]*$/u.test(variable) || variable.startsWith("@@")) return;
|
||||
const key = variable.toLowerCase();
|
||||
if (seen.has(key)) return;
|
||||
seen.add(key);
|
||||
variables.push(variable);
|
||||
};
|
||||
|
||||
collectSqlServerRoutineParameters(tokens, addVariable);
|
||||
|
||||
for (let index = 0; index < tokens.length; index += 1) {
|
||||
const declare = tokens[index];
|
||||
@@ -2668,11 +2677,7 @@ function collectSqlServerLocalVariables(sql: string, cursor: number, options: Sq
|
||||
if (token.kind === "word" && /^@[A-Za-z_@$#][\w@$#]*$/u.test(token.text) && !token.text.startsWith("@@")) {
|
||||
const next = tokens[declarationIndex + 1];
|
||||
if (next && next.text !== "," && next.text !== ";") {
|
||||
const key = token.text.toLowerCase();
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
variables.push(token.text);
|
||||
}
|
||||
addVariable(token.text);
|
||||
}
|
||||
expectVariable = false;
|
||||
}
|
||||
@@ -2685,6 +2690,44 @@ function collectSqlServerLocalVariables(sql: string, cursor: number, options: Sq
|
||||
return variables;
|
||||
}
|
||||
|
||||
function collectSqlServerRoutineParameters(tokens: ReturnType<typeof tokenizeSqlSemantic>, addVariable: (variable: string) => void) {
|
||||
for (let index = 0; index < tokens.length; index += 1) {
|
||||
const start = tokens[index];
|
||||
if (start?.kind !== "word" || (start.normalized !== "create" && start.normalized !== "alter")) continue;
|
||||
|
||||
let routineIndex = index + 1;
|
||||
if (start.normalized === "create" && tokens[routineIndex]?.kind === "word" && tokens[routineIndex]?.normalized === "or" && tokens[routineIndex + 1]?.kind === "word" && tokens[routineIndex + 1]?.normalized === "alter") {
|
||||
routineIndex += 2;
|
||||
}
|
||||
const routine = tokens[routineIndex];
|
||||
if (routine?.kind !== "word" || !["proc", "procedure", "function"].includes(routine.normalized)) continue;
|
||||
|
||||
if (routine.normalized === "function") {
|
||||
const openingIndex = tokens.findIndex((token, tokenIndex) => tokenIndex > routineIndex && token.kind === "punctuation" && token.text === "(" && token.depth === routine.depth);
|
||||
if (openingIndex < 0) continue;
|
||||
const openingDepth = tokens[openingIndex]!.depth;
|
||||
for (let parameterIndex = openingIndex + 1; parameterIndex < tokens.length; parameterIndex += 1) {
|
||||
const token = tokens[parameterIndex]!;
|
||||
if (token.kind === "punctuation" && token.text === ")" && token.depth === openingDepth) break;
|
||||
if (token.kind === "word" && token.depth === openingDepth + 1) addVariable(token.text);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let parameterIndex = routineIndex + 1; parameterIndex < tokens.length; parameterIndex += 1) {
|
||||
const token = tokens[parameterIndex]!;
|
||||
if (token.depth < routine.depth) break;
|
||||
if (token.depth !== routine.depth || token.kind !== "word") continue;
|
||||
if (token.normalized === "as") {
|
||||
const previous = tokens[parameterIndex - 1];
|
||||
if (previous?.kind === "word" && previous.text.startsWith("@") && !previous.text.startsWith("@@")) continue;
|
||||
break;
|
||||
}
|
||||
addVariable(token.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sqlTokenStartsLine(sql: string, position: number): boolean {
|
||||
const lineStart = sql.lastIndexOf("\n", Math.max(0, position - 1)) + 1;
|
||||
return sql.slice(lineStart, position).trim().length === 0;
|
||||
|
||||
Reference in New Issue
Block a user