From 79d6ab64e954c61adc8054860b4bf3cc2f313a8c Mon Sep 17 00:00:00 2001 From: zipg Date: Mon, 21 Sep 2026 11:38:04 +0800 Subject: [PATCH] fix(sqlserver): diagnose DECLARE without data type in routine bodies --- .../src/components/editor/QueryEditor.vue | 16 +- ....sqlServerRoutineDiagnosticsWiring.spec.ts | 37 +++ .../src/lib/sql/semantic/diagnostics.ts | 22 ++ .../sql/sqlServerRoutineSyntaxDiagnostics.ts | 305 ++++++++++++++++++ .../issue-9315-routine-declare-after.png | Bin 0 -> 69521 bytes .../issue-9315-routine-declare-before.png | Bin 0 -> 68909 bytes .../app-tests/sqlSemanticDiagnostics.test.ts | 72 ++++- .../sqlServerRoutineSyntaxDiagnostics.test.ts | 89 +++++ 8 files changed, 538 insertions(+), 3 deletions(-) create mode 100644 apps/desktop/src/components/editor/__tests__/QueryEditor.sqlServerRoutineDiagnosticsWiring.spec.ts create mode 100644 apps/desktop/src/lib/sql/sqlServerRoutineSyntaxDiagnostics.ts create mode 100644 docs/screenshots/issue-9315-routine-declare-after.png create mode 100644 docs/screenshots/issue-9315-routine-declare-before.png create mode 100644 packages/app-tests/sqlServerRoutineSyntaxDiagnostics.test.ts diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index fa71599a1f..b61f13b22c 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -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)); } diff --git a/apps/desktop/src/components/editor/__tests__/QueryEditor.sqlServerRoutineDiagnosticsWiring.spec.ts b/apps/desktop/src/components/editor/__tests__/QueryEditor.sqlServerRoutineDiagnosticsWiring.spec.ts new file mode 100644 index 0000000000..1d784d8c9a --- /dev/null +++ b/apps/desktop/src/components/editor/__tests__/QueryEditor.sqlServerRoutineDiagnosticsWiring.spec.ts @@ -0,0 +1,37 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +/** + * SQL Server 例程批次的语法诊断接线(#9315)写在 `