fix(sqlserver): diagnose DECLARE without data type in routine bodies
This commit is contained in:
8 files changed
+538
-3
No files matched your search
@@ -136,6 +136,7 @@ import { createHoverSearch, type HoverSearchController } from "@/lib/editor/sqlH
|
||||
import { lineColumnToOffset, sqlErrorDecorationRange as resolveSqlErrorDecorationRange, sqlErrorSqlMatchesEditor } from "@/lib/sql/sqlDiagnostics";
|
||||
import { analyzeMysqlRoutineSyntax, supportsMysqlRoutineSyntaxDiagnostics } from "@/lib/sql/mysqlRoutineSyntaxDiagnostics";
|
||||
import { buildOracleSyntaxDiagnostics } from "@/lib/sql/oracleSyntaxDiagnostics";
|
||||
import { buildSqlServerRoutineSyntaxDiagnostics } from "@/lib/sql/sqlServerRoutineSyntaxDiagnostics";
|
||||
import {
|
||||
DBX_TABLE_REFERENCE_MIME,
|
||||
DBX_TABLE_REFERENCE_DROP_EVENT,
|
||||
@@ -217,6 +218,7 @@ import {
|
||||
isSqlVirtualTableReference,
|
||||
shouldRunSqlSemanticDiagnostics,
|
||||
sqlSemanticDiagnosticRangesForViewport,
|
||||
sqlServerRoutineDefinitionRangesForViewport,
|
||||
tableReferenceKey,
|
||||
type SqlSemanticDiagnostic,
|
||||
} from "@/lib/sql/semantic/diagnostics";
|
||||
@@ -3940,7 +3942,11 @@ async function refreshSemanticDiagnostics(options: { preserveOutsideRanges?: boo
|
||||
executableStatementRangeCache = executableStatementRangeCacheForDoc(executableStatementRangeCache, currentView.state.doc, props.databaseType, sqlStatementParameterOptions());
|
||||
}
|
||||
const diagnosticRanges = sqlSemanticDiagnosticRangesForViewport(sql, visibleRanges, props.databaseType, props.databaseType === "sqlserver" ? undefined : executableStatementRangeCache?.ranges, sqlStatementParameterOptions());
|
||||
if (diagnosticRanges.length === 0) {
|
||||
// SQL Server routine batches are excluded from `diagnosticRanges` (see
|
||||
// `sqlServerRoutineDefinitionRangesForViewport`), so they are recomputed here
|
||||
// and stay part of the replaced range set below.
|
||||
const sqlServerRoutineRanges = props.databaseType === "sqlserver" ? sqlServerRoutineDefinitionRangesForViewport(sql, visibleRanges) : [];
|
||||
if (diagnosticRanges.length === 0 && sqlServerRoutineRanges.length === 0) {
|
||||
if (!options.preserveOutsideRanges) setSemanticDiagnostics([]);
|
||||
return;
|
||||
}
|
||||
@@ -3953,6 +3959,12 @@ async function refreshSemanticDiagnostics(options: { preserveOutsideRanges?: boo
|
||||
return !!diagnosticRange && diagnosticRanges.some((range) => rangesOverlap(diagnosticRange, range));
|
||||
}),
|
||||
);
|
||||
// The analyzer never sees routine batches (the MsSql grammar cannot parse their
|
||||
// parameter list), so run the token-based routine syntax rules instead of leaving
|
||||
// a stored procedure without any check at all (dbx#9315).
|
||||
for (const range of sqlServerRoutineRanges) {
|
||||
nextDiagnostics.push(...offsetSqlSemanticDiagnostics(buildSqlServerRoutineSyntaxDiagnostics(range.sql, props.databaseType), range, sql));
|
||||
}
|
||||
const mysqlRoutineAnalysis = props.databaseType === "mysql" && supportsMysqlRoutineSyntaxDiagnostics(sqlDriverProfile.value) ? analyzeMysqlRoutineSyntax(sql) : null;
|
||||
if (mysqlRoutineAnalysis) {
|
||||
nextDiagnostics.push(
|
||||
@@ -4018,7 +4030,7 @@ async function refreshSemanticDiagnostics(options: { preserveOutsideRanges?: boo
|
||||
}
|
||||
}
|
||||
if (options.preserveOutsideRanges) {
|
||||
replaceSemanticDiagnosticsInRanges(nextDiagnostics, diagnosticRanges, sql);
|
||||
replaceSemanticDiagnosticsInRanges(nextDiagnostics, [...diagnosticRanges, ...sqlServerRoutineRanges], sql);
|
||||
} else {
|
||||
setSemanticDiagnostics(nextDiagnostics.sort(compareSqlSemanticDiagnostics));
|
||||
}
|
||||
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
/**
|
||||
* SQL Server 例程批次的语法诊断接线(#9315)写在 `<script setup>` 内部,
|
||||
* 无法直接 import,因此沿用仓库既有做法(QueryEditor.cteNavigationWiring.spec.ts)
|
||||
* 对源码做结构断言。规则本身由 sqlServerRoutineSyntaxDiagnostics.test.ts 覆盖,
|
||||
* 批次/视口裁剪由 sqlSemanticDiagnostics.test.ts 覆盖,这里只锁定接线契约。
|
||||
*/
|
||||
const source = readFileSync(new URL("../QueryEditor.vue", import.meta.url), "utf8");
|
||||
|
||||
function normalizeCode(text: string): string {
|
||||
return text.replace(/\s+/g, "").replace(/,(?=[)\]}])/g, "");
|
||||
}
|
||||
|
||||
const normalizedSource = normalizeCode(source);
|
||||
|
||||
function expectSourceToContain(fragment: string): void {
|
||||
expect(normalizedSource).toContain(normalizeCode(fragment));
|
||||
}
|
||||
|
||||
describe("QueryEditor 的 SQL Server 例程语法诊断接线", () => {
|
||||
it("例程批次被跳过整个语义范围后仍单独取回", () => {
|
||||
// sqlSemanticDiagnosticRangesForViewport 对例程批次返回空,所以必须再取一次。
|
||||
expectSourceToContain('const sqlServerRoutineRanges = props.databaseType === "sqlserver" ? sqlServerRoutineDefinitionRangesForViewport(sql, visibleRanges) : [];');
|
||||
// 例程批次不能因为普通范围为空就提前 return,否则过程体永远不检查。
|
||||
expectSourceToContain("if (diagnosticRanges.length === 0 && sqlServerRoutineRanges.length === 0) {");
|
||||
});
|
||||
|
||||
it("例程批次只跑 token 规则,不做引用分析", () => {
|
||||
expectSourceToContain("nextDiagnostics.push(...offsetSqlSemanticDiagnostics(buildSqlServerRoutineSyntaxDiagnostics(range.sql, props.databaseType), range, sql));");
|
||||
});
|
||||
|
||||
it("增量刷新时把例程批次一并纳入替换范围,避免旧诊断残留", () => {
|
||||
expectSourceToContain("replaceSemanticDiagnosticsInRanges(nextDiagnostics, [...diagnosticRanges, ...sqlServerRoutineRanges], sql);");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user