fix(sqlserver): accept cursor declarations in SQL analysis

Closes #5860
This commit is contained in:
t8y2 committed 2026-08-11 13:24:51 +00:00
1 parent 2db2041680
commit 7f405eacfb
4 files changed
+375 -4

No files matched your search

@@ -25,7 +25,8 @@ export interface SqlSemanticDiagnosticVisibleRange {
}
export function sqlSemanticDiagnosticRangesForViewport(sql: string, visibleRanges: readonly SqlSemanticDiagnosticVisibleRange[], databaseType?: DatabaseType): SqlTextRange[] {
const statements = executableStatementRanges(sql, databaseType);
const executableRanges = executableStatementRanges(sql, databaseType);
const statements = databaseType === "sqlserver" ? mergeSqlServerDiagnosticBatchRanges(sql, executableRanges) : executableRanges;
if (statements.length === 0 || visibleRanges.length === 0) return [];
const selected: SqlTextRange[] = [];
@@ -41,6 +42,68 @@ export function sqlSemanticDiagnosticRangesForViewport(sql: string, visibleRange
return selected;
}
function mergeSqlServerDiagnosticBatchRanges(sql: string, statements: readonly SqlTextRange[]): SqlTextRange[] {
if (statements.length < 2) return [...statements];
const ranges: SqlTextRange[] = [];
let batch: SqlTextRange[] = [statements[0]];
for (const statement of statements.slice(1)) {
if (sqlServerGapContainsGoSeparator(sql.slice(batch[batch.length - 1].to, statement.from))) {
pushSqlServerDiagnosticBatchRanges(ranges, sql, batch);
batch = [];
}
batch.push(statement);
}
pushSqlServerDiagnosticBatchRanges(ranges, sql, batch);
return ranges;
}
function pushSqlServerDiagnosticBatchRanges(ranges: SqlTextRange[], sql: string, batch: readonly SqlTextRange[]) {
if (batch.length > 1 && batch.some((statement) => sqlServerStatementNeedsBatchContext(statement.sql))) {
const from = batch[0].from;
const to = batch[batch.length - 1].to;
ranges.push({ from, to, sql: sql.slice(from, to) });
} else {
ranges.push(...batch);
}
}
function sqlServerStatementNeedsBatchContext(sql: string): boolean {
return /^\s*(?:WHILE|BEGIN|END|IF|ELSE|TRY|CATCH)\b/i.test(sql) || /^\s*DECLARE\s+(?:\[[^\]]+\]|"[^"]+"|[A-Z_@#][\w@$#]*)\s+CURSOR\b/i.test(sql);
}
function sqlServerGapContainsGoSeparator(gap: string): boolean {
let inBlockComment = false;
let lineStart = 0;
while (lineStart <= gap.length) {
const newline = gap.indexOf("\n", lineStart);
const lineEnd = newline < 0 ? gap.length : newline;
const line = gap.slice(lineStart, lineEnd).replace(/\r$/, "");
if (!inBlockComment && /^\s*go(?:\s+\d+)?\s*$/i.test(line)) return true;
let offset = 0;
while (offset < line.length) {
if (inBlockComment) {
const close = line.indexOf("*/", offset);
if (close < 0) break;
inBlockComment = false;
offset = close + 2;
continue;
}
const open = line.indexOf("/*", offset);
if (open < 0) break;
const lineComment = line.indexOf("--", offset);
if (lineComment >= 0 && lineComment < open) break;
inBlockComment = true;
offset = open + 2;
}
if (newline < 0) break;
lineStart = newline + 1;
}
return false;
}
export function buildSqlSemanticDiagnostics(analysis: SqlReferenceAnalysis, schema: SqlSemanticDiagnosticSchema): SqlSemanticDiagnostic[] {
const diagnostics: SqlSemanticDiagnostic[] = [];
const tables = analysis.tables.filter((table) => table.name.trim());