From 53322778c8d0c858763165f44922202a37e1201d Mon Sep 17 00:00:00 2001 From: "Sai Avishkar Sreerama (from Dev Box)" Date: Tue, 11 Aug 2026 14:48:10 -0500 Subject: [PATCH 1/5] Add move to schema database project menu command --- .../sqlMoveToSchemaProvider.ts | 110 +++++++++++++++++- .../test/unit/sqlSymbolRenameProvider.test.ts | 25 ++++ extensions/sql-database-projects/package.json | 14 +++ .../sql-database-projects/package.nls.json | 1 + .../src/common/constants.ts | 1 + .../src/controllers/mainController.ts | 8 ++ .../src/controllers/projectController.ts | 36 ++++++ localization/xliff/sql-database-projects.xlf | 3 + 8 files changed, 196 insertions(+), 2 deletions(-) diff --git a/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts b/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts index 7739c7a4a6..ae2f498e8a 100644 --- a/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts +++ b/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts @@ -67,6 +67,11 @@ interface SchemaFolderMovePlan { newAbsUri: vscode.Uri; } +interface MoveToSchemaSqlToken { + text: string; + start: number; +} + /** * Surfaces a "Move to Schema..." action under the editor's **Refactor...** menu for SQL files in a * SQL project. @@ -96,8 +101,12 @@ export class SqlMoveToSchemaProvider implements vscode.CodeActionProvider { }), vscode.commands.registerCommand( cmdMoveToSchema, - (document: vscode.TextDocument, position: vscode.Position) => - provider.runMoveToSchema(document, position), + (documentOrPath: vscode.TextDocument | string, position?: vscode.Position) => + typeof documentOrPath === "string" + ? provider.runMoveToSchemaFromFilePath(documentOrPath) + : position + ? provider.runMoveToSchema(documentOrPath, position) + : undefined, ), ]; } @@ -189,6 +198,103 @@ export class SqlMoveToSchemaProvider implements vscode.CodeActionProvider { await this.applyMove(document, position, selected.label, schemas); } + //#region Tree Entry Local Parsing (replaceable) + // NOTE: This region exists only to bridge the tree-entry (file path only) flow into the + // existing Move-to-Schema document+position flow. If STS exposes an API that resolves the + // movable object position from a file path, replace this entire region with that STS call. + + /** + * Starts Move to Schema from a Database Projects tree file path. + */ + public async runMoveToSchemaFromFilePath(filePath: string): Promise { + const document = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath)); + const position = this.findMoveToSchemaSymbolPosition(document); + if (!position) { + void vscode.window.showInformationMessage(loc.noMovableSymbolAtCursor); + return; + } + + await this.runMoveToSchema(document, position); + } + + /** + * Finds the position of the object identifier used as the Move-to-Schema cursor target. + * Supports CREATE/ALTER forms, including CREATE OR ALTER, and schema-qualified names. + */ + private findMoveToSchemaSymbolPosition( + document: vscode.TextDocument, + ): vscode.Position | undefined { + const tokens = this.tokenizeMoveToSchemaSql(document.getText()); + const objectTypes = new Set([ + "table", + "view", + "proc", + "procedure", + "function", + "trigger", + "sequence", + ]); + + for (let i = 0; i < tokens.length; i++) { + const statement = tokens[i].text.toLowerCase(); + if (statement !== "create" && statement !== "alter") { + continue; + } + + let objectTypeIndex = i + 1; + if ( + tokens[objectTypeIndex]?.text.toLowerCase() === "or" && + tokens[objectTypeIndex + 1]?.text.toLowerCase() === "alter" + ) { + objectTypeIndex += 2; + } + + const objectType = tokens[objectTypeIndex]?.text.toLowerCase(); + if (!objectType || !objectTypes.has(objectType)) { + continue; + } + + const firstNameToken = tokens[objectTypeIndex + 1]; + if (!firstNameToken) { + return undefined; + } + + // schema.object -> target object token; object -> target first name token + if (tokens[objectTypeIndex + 2]?.text === "." && tokens[objectTypeIndex + 3]) { + return document.positionAt(tokens[objectTypeIndex + 3].start); + } + return document.positionAt(firstNameToken.start); + } + + return undefined; + } + + /** + * Tokenizes SQL text into identifiers and separators while skipping whitespace, comments, + * and string literals so statement detection ignores non-executable text. + */ + private tokenizeMoveToSchemaSql(text: string): MoveToSchemaSqlToken[] { + const tokenPattern = + /\s+|--[^\r\n]*|\/\*[\s\S]*?\*\/|'(?:''|[^'])*'|\[(?:[^\]]|\]\])+\]|"(?:""|[^"])*"|`(?:``|[^`])*`|[A-Za-z0-9_$#@]+|\./gy; + const tokens: MoveToSchemaSqlToken[] = []; + + let match: RegExpExecArray | null; + while ((match = tokenPattern.exec(text)) !== null) { + const value = match[0]; + if (!value || /^\s+$/.test(value)) { + continue; + } + if (value.startsWith("--") || value.startsWith("/*") || value.startsWith("'")) { + continue; + } + tokens.push({ text: value, start: match.index }); + } + + return tokens; + } + + //#endregion + /** * Runs the move end to end: asks STS for the script edits, applies them through VS Code's * refactor preview, then relocates the definition file and updates the `.sqlproj`. diff --git a/extensions/mssql/test/unit/sqlSymbolRenameProvider.test.ts b/extensions/mssql/test/unit/sqlSymbolRenameProvider.test.ts index 62c0bcd700..c9eee4e36d 100644 --- a/extensions/mssql/test/unit/sqlSymbolRenameProvider.test.ts +++ b/extensions/mssql/test/unit/sqlSymbolRenameProvider.test.ts @@ -63,6 +63,10 @@ function makeMoveDocument( if (!range) return lineText; return lineText.slice(range.start.character, range.end.character); }), + positionAt: sandbox.stub().callsFake((offset: number) => { + const lines = lineText.slice(0, offset).split("\n"); + return new vscode.Position(lines.length - 1, lines[lines.length - 1].length); + }), } as unknown as vscode.TextDocument; } @@ -661,6 +665,27 @@ suite("SqlMoveToSchemaProvider Tests", () => { // ------------------------------------------------------------------------- suite("runMoveToSchema", () => { + test("resolves the object position when started from a file path", async () => { + findFilesStub.resolves([vscode.Uri.file(defaultProjFile)]); + showQuickPickStub.resolves({ label: "hr" }); + sendRequestStub.withArgs(ListProjectSchemasRequest.type).resolves({ schemas: ["hr"] }); + sendRequestStub.withArgs(SqlMoveToSchemaRequest.type).resolves({ changes: {} }); + + const document = makeMoveDocument(sandbox, { + lineText: "Alter Table dbo.Table1\nAdd NewColumn1 INT NULL;", + }); + sandbox.stub(vscode.workspace, "openTextDocument").resolves(document); + + await provider.runMoveToSchemaFromFilePath(defaultSqlFile); + + expect(sendRequestStub).to.have.been.calledWith( + SqlMoveToSchemaRequest.type, + sinon.match({ + position: { line: 0, character: 16 }, + }), + ); + }); + test("shows message when file is not in a SQL project", async () => { const doc = makeMoveDocument(sandbox); await provider.runMoveToSchema(doc, new vscode.Position(0, 0)); diff --git a/extensions/sql-database-projects/package.json b/extensions/sql-database-projects/package.json index af5b83f39d..72bc471c66 100644 --- a/extensions/sql-database-projects/package.json +++ b/extensions/sql-database-projects/package.json @@ -188,6 +188,11 @@ "title": "%sqlDatabaseProjects.configureCodeAnalysisSettings%", "category": "%sqlDatabaseProjects.displayName%" }, + { + "command": "sqlDatabaseProjects.moveToSchema", + "title": "%sqlDatabaseProjects.moveToSchema%", + "category": "%sqlDatabaseProjects.displayName%" + }, { "command": "sqlDatabaseProjects.addDatabaseReference", "title": "%sqlDatabaseProjects.addDatabaseReference%", @@ -344,6 +349,10 @@ "command": "sqlDatabaseProjects.configureCodeAnalysisSettings", "when": "false" }, + { + "command": "sqlDatabaseProjects.moveToSchema", + "when": "false" + }, { "command": "sqlDatabaseProjects.addDatabaseReference", "when": "false" @@ -537,6 +546,11 @@ "when": "view == dataworkspace.views.main && viewItem =~ /^(databaseProject.itemType.project|databaseProject.itemType.legacyProject)$/", "group": "9_dbProjectsLast@9" }, + { + "command": "sqlDatabaseProjects.moveToSchema", + "when": "view == dataworkspace.views.main && viewItem =~ /^databaseProject.itemType.file/", + "group": "9_dbProjectsLast@4" + }, { "submenu": "sqlDatabaseProjects.objectExplorerSubmenu", "when": "view == objectExplorer && viewItem =~ /\\btype=(disconnectedServer|Server|Database)\\b/", diff --git a/extensions/sql-database-projects/package.nls.json b/extensions/sql-database-projects/package.nls.json index 1108f6034b..973885b222 100644 --- a/extensions/sql-database-projects/package.nls.json +++ b/extensions/sql-database-projects/package.nls.json @@ -13,6 +13,7 @@ "sqlDatabaseProjects.createProjectFromDatabase": "Create Project from Database", "sqlDatabaseProjects.updateProjectFromDatabase": "Update Project from Database", "sqlDatabaseProjects.configureCodeAnalysisSettings": "Code Analysis Settings", + "sqlDatabaseProjects.moveToSchema": "Move to Schema...", "sqlDatabaseProjects.properties": "Properties", "sqlDatabaseProjects.schemaCompare": "Schema Compare", "sqlDatabaseProjects.delete": "Delete", diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index c332075cab..8a1bbaf742 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -98,6 +98,7 @@ export const schemaCompareRunComparisonCommand = "schemaCompare.runComparison"; export const mssqlSchemaCompareCommand = "mssql.schemaCompare"; export const mssqlPublishProjectCommand = "mssql.publishDatabaseProject"; export const mssqlConfigureCodeAnalysisSettingsCommand = "mssql.configureCodeAnalysisSettings"; +export const mssqlMoveToSchemaCommand = "mssql.moveToSchema"; export const vscodeOpenCommand = "vscode.open"; export const refreshDataWorkspaceCommand = "dataworkspace.refresh"; diff --git a/extensions/sql-database-projects/src/controllers/mainController.ts b/extensions/sql-database-projects/src/controllers/mainController.ts index e707c021ef..0d037a9c76 100644 --- a/extensions/sql-database-projects/src/controllers/mainController.ts +++ b/extensions/sql-database-projects/src/controllers/mainController.ts @@ -155,6 +155,14 @@ export default class MainController implements vscode.Disposable { }, ), ); + this.context.subscriptions.push( + vscode.commands.registerCommand( + "sqlDatabaseProjects.moveToSchema", + async (node: WorkspaceTreeItem) => { + await this.projectsController.moveToSchema(node); + }, + ), + ); this.context.subscriptions.push( vscode.commands.registerCommand( "sqlDatabaseProjects.createProjectFromDatabase", diff --git a/extensions/sql-database-projects/src/controllers/projectController.ts b/extensions/sql-database-projects/src/controllers/projectController.ts index f176158cab..9aa3fa60ef 100644 --- a/extensions/sql-database-projects/src/controllers/projectController.ts +++ b/extensions/sql-database-projects/src/controllers/projectController.ts @@ -2341,6 +2341,42 @@ export class ProjectsController { } } + /** + * Launches Move to Schema from a SQL object file node in the Database Projects tree. + * This reuses the mssql extension's existing quickpick + refactor preview flow. + */ + public async moveToSchema(treeNode: dataworkspace.WorkspaceTreeItem): Promise { + try { + const element = treeNode.element; + if (!(element instanceof FileNode)) { + return; + } + + if (element.type !== constants.DatabaseProjectItemType.sqlObjectScript) { + return; + } + + if ( + path.extname(element.fileSystemUri.fsPath).toLowerCase() !== + constants.sqlFileExtension + ) { + return; + } + + await vscode.commands.executeCommand( + constants.mssqlMoveToSchemaCommand, + element.fileSystemUri.fsPath, + ); + } catch (err) { + void vscode.window.showErrorMessage(utils.getErrorMessage(err)); + TelemetryReporter.sendErrorEvent( + TelemetryViews.ProjectController, + TelemetryActions.move, + err, + ); + } + } + //#endregion /** diff --git a/localization/xliff/sql-database-projects.xlf b/localization/xliff/sql-database-projects.xlf index 8f57e97464..77d711e391 100644 --- a/localization/xliff/sql-database-projects.xlf +++ b/localization/xliff/sql-database-projects.xlf @@ -1008,6 +1008,9 @@ Full path to .NET SDK on the machine. For example, if dotnet.exe is located at C:\folder1\dotnet\dotnet.exe, set the path for this setting to C:\folder1\dotnet + + Move to Schema... + New Database Project From 7eaaf5562eaa62f51217d5cc52970f39a6e4d321 Mon Sep 17 00:00:00 2001 From: "Sai Avishkar Sreerama (from Dev Box)" Date: Tue, 11 Aug 2026 15:20:49 -0500 Subject: [PATCH 2/5] addressing copilot comments which are good to have --- .../sqlMoveToSchemaProvider.ts | 33 +++++++++++++------ .../test/unit/sqlSymbolRenameProvider.test.ts | 32 ++++++++++++++---- extensions/sql-database-projects/package.json | 2 +- 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts b/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts index ae2f498e8a..a387dc2ea9 100644 --- a/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts +++ b/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts @@ -207,14 +207,20 @@ export class SqlMoveToSchemaProvider implements vscode.CodeActionProvider { * Starts Move to Schema from a Database Projects tree file path. */ public async runMoveToSchemaFromFilePath(filePath: string): Promise { - const document = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath)); - const position = this.findMoveToSchemaSymbolPosition(document); - if (!position) { - void vscode.window.showInformationMessage(loc.noMovableSymbolAtCursor); - return; - } + try { + const document = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath)); + const position = this.findMoveToSchemaSymbolPosition(document); + if (!position) { + void vscode.window.showInformationMessage(loc.noMovableSymbolAtCursor); + return; + } - await this.runMoveToSchema(document, position); + await this.runMoveToSchema(document, position); + } catch (err) { + void vscode.window.showErrorMessage( + loc.moveToSchemaRequestFailed(err instanceof Error ? err.message : String(err)), + ); + } } /** @@ -275,12 +281,19 @@ export class SqlMoveToSchemaProvider implements vscode.CodeActionProvider { */ private tokenizeMoveToSchemaSql(text: string): MoveToSchemaSqlToken[] { const tokenPattern = - /\s+|--[^\r\n]*|\/\*[\s\S]*?\*\/|'(?:''|[^'])*'|\[(?:[^\]]|\]\])+\]|"(?:""|[^"])*"|`(?:``|[^`])*`|[A-Za-z0-9_$#@]+|\./gy; + /\s+|--[^\r\n]*|\/\*[\s\S]*?\*\/|'(?:''|[^'])*'|\[(?:[^\]]|\]\])+\]|"(?:""|[^"])*"|`(?:``|[^`])*`|[A-Za-z0-9_$#@]+|\./y; const tokens: MoveToSchemaSqlToken[] = []; - let match: RegExpExecArray | null; - while ((match = tokenPattern.exec(text)) !== null) { + for (let index = 0; index < text.length; ) { + tokenPattern.lastIndex = index; + const match = tokenPattern.exec(text); + if (!match) { + index++; + continue; + } + const value = match[0]; + index = tokenPattern.lastIndex; if (!value || /^\s+$/.test(value)) { continue; } diff --git a/extensions/mssql/test/unit/sqlSymbolRenameProvider.test.ts b/extensions/mssql/test/unit/sqlSymbolRenameProvider.test.ts index c9eee4e36d..679299c10d 100644 --- a/extensions/mssql/test/unit/sqlSymbolRenameProvider.test.ts +++ b/extensions/mssql/test/unit/sqlSymbolRenameProvider.test.ts @@ -44,6 +44,7 @@ function makeMoveDocument( ): vscode.TextDocument { const fsPath = opts.fsPath ?? defaultSqlFile; const lineText = opts.lineText ?? "CREATE TABLE [dbo].[MyTable]"; + const lines = lineText.split("\n"); const uri = vscode.Uri.file(fsPath); const uriString = uri.toString(); return { @@ -51,17 +52,24 @@ function makeMoveDocument( lineAt: sandbox.stub().callsFake((lineOrPos: number | vscode.Position) => { const lineNum = typeof lineOrPos === "number" ? lineOrPos : (lineOrPos as vscode.Position).line; + const text = lines[lineNum] ?? ""; return { - text: lineNum === 0 ? lineText : "", + text, range: new vscode.Range( new vscode.Position(lineNum, 0), - new vscode.Position(lineNum, (lineNum === 0 ? lineText : "").length), + new vscode.Position(lineNum, text.length), ), }; }), getText: sandbox.stub().callsFake((range?: vscode.Range) => { if (!range) return lineText; - return lineText.slice(range.start.character, range.end.character); + if (range.start.line === range.end.line) { + return (lines[range.start.line] ?? "").slice( + range.start.character, + range.end.character, + ); + } + return lineText.slice(0, range.end.character); }), positionAt: sandbox.stub().callsFake((offset: number) => { const lines = lineText.slice(0, offset).split("\n"); @@ -665,14 +673,15 @@ suite("SqlMoveToSchemaProvider Tests", () => { // ------------------------------------------------------------------------- suite("runMoveToSchema", () => { - test("resolves the object position when started from a file path", async () => { + test("resolves the object position after a preamble when started from a file path", async () => { findFilesStub.resolves([vscode.Uri.file(defaultProjFile)]); showQuickPickStub.resolves({ label: "hr" }); sendRequestStub.withArgs(ListProjectSchemasRequest.type).resolves({ schemas: ["hr"] }); sendRequestStub.withArgs(SqlMoveToSchemaRequest.type).resolves({ changes: {} }); const document = makeMoveDocument(sandbox, { - lineText: "Alter Table dbo.Table1\nAdd NewColumn1 INT NULL;", + lineText: + "SET ANSI_NULLS ON;\nGO\nAlter Table dbo.Table1\nAdd NewColumn1 INT NULL;", }); sandbox.stub(vscode.workspace, "openTextDocument").resolves(document); @@ -681,11 +690,22 @@ suite("SqlMoveToSchemaProvider Tests", () => { expect(sendRequestStub).to.have.been.calledWith( SqlMoveToSchemaRequest.type, sinon.match({ - position: { line: 0, character: 16 }, + position: { line: 2, character: 16 }, }), ); }); + test("shows error when opening a file path fails", async () => { + const error = new Error("File not found"); + sandbox.stub(vscode.workspace, "openTextDocument").rejects(error); + + await provider.runMoveToSchemaFromFilePath(defaultSqlFile); + + expect(messageBoxes.showErrorMessage).to.have.been.calledWith( + moveLoc.moveToSchemaRequestFailed(error.message), + ); + }); + test("shows message when file is not in a SQL project", async () => { const doc = makeMoveDocument(sandbox); await provider.runMoveToSchema(doc, new vscode.Position(0, 0)); diff --git a/extensions/sql-database-projects/package.json b/extensions/sql-database-projects/package.json index 72bc471c66..09f35afb59 100644 --- a/extensions/sql-database-projects/package.json +++ b/extensions/sql-database-projects/package.json @@ -548,7 +548,7 @@ }, { "command": "sqlDatabaseProjects.moveToSchema", - "when": "view == dataworkspace.views.main && viewItem =~ /^databaseProject.itemType.file/", + "when": "view == dataworkspace.views.main && viewItem == databaseProject.itemType.file.sqlObjectScript", "group": "9_dbProjectsLast@4" }, { From fd016614523c751d3d1ee79589a9d977f5c78592 Mon Sep 17 00:00:00 2001 From: "Sai Avishkar Sreerama (from Dev Box)" Date: Tue, 11 Aug 2026 15:34:19 -0500 Subject: [PATCH 3/5] Document move to schema tokenizer regex --- .../mssql/src/languageservice/sqlMoveToSchemaProvider.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts b/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts index a387dc2ea9..b53039adf2 100644 --- a/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts +++ b/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts @@ -280,6 +280,10 @@ export class SqlMoveToSchemaProvider implements vscode.CodeActionProvider { * and string literals so statement detection ignores non-executable text. */ private tokenizeMoveToSchemaSql(text: string): MoveToSchemaSqlToken[] { + // Match only syntax that affects CREATE/ALTER object detection: whitespace and + // comments/strings to skip, quoted or bare identifiers to keep, and dots for + // schema-qualified names. The sticky flag keeps token positions exact while the + // loop below advances over punctuation this parser does not care about. const tokenPattern = /\s+|--[^\r\n]*|\/\*[\s\S]*?\*\/|'(?:''|[^'])*'|\[(?:[^\]]|\]\])+\]|"(?:""|[^"])*"|`(?:``|[^`])*`|[A-Za-z0-9_$#@]+|\./y; const tokens: MoveToSchemaSqlToken[] = []; From 4469b13e6fbd617f0f15f4062b20d376d9f22994 Mon Sep 17 00:00:00 2001 From: "Sai Avishkar Sreerama (from Dev Box)" Date: Tue, 11 Aug 2026 15:40:14 -0500 Subject: [PATCH 4/5] Document move to schema context menu option --- extensions/sql-database-projects/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/sql-database-projects/CHANGELOG.md b/extensions/sql-database-projects/CHANGELOG.md index 1673c9275b..045dbc0d89 100644 --- a/extensions/sql-database-projects/CHANGELOG.md +++ b/extensions/sql-database-projects/CHANGELOG.md @@ -11,6 +11,7 @@ _The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Added support for using **Move to Schema** on sequences and DML triggers - Added a **Restore Packages** command to the SQL project context menu for restoring the project's NuGet packages. - Added support for **Move to Schema** now automatically moves the `.sql` file to the target schema folder. +- Added a **Move to Schema** option to the context menu for SQL object files in the Database Projects tree. - Added support for creating SQL objects with the schema corresponding to the selected folder instead of defaulting to `dbo`. - Added support for custom code analysis rules. Rules contributed by referenced NuGet analyzer packages now appear in the **Code Analysis Settings** dialog alongside the built-in rules. - Fixed an issue where the **Rename Symbol** feature was incorrectly enabled on SQL alias identifiers (column aliases, table aliases, subquery aliases, and CTE names), which could generate an invalid `.refactorlog` entry. From 59b0fb1a7b6f359808c1082f6844b9eb8edcb22d Mon Sep 17 00:00:00 2001 From: "Sai Avishkar Sreerama (from Dev Box)" Date: Tue, 11 Aug 2026 16:00:38 -0500 Subject: [PATCH 5/5] Add move to schema tokenizer tests --- .../sqlMoveToSchemaProvider.ts | 2 +- .../test/unit/sqlSymbolRenameProvider.test.ts | 64 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts b/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts index b53039adf2..df66a89289 100644 --- a/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts +++ b/extensions/mssql/src/languageservice/sqlMoveToSchemaProvider.ts @@ -202,7 +202,7 @@ export class SqlMoveToSchemaProvider implements vscode.CodeActionProvider { // NOTE: This region exists only to bridge the tree-entry (file path only) flow into the // existing Move-to-Schema document+position flow. If STS exposes an API that resolves the // movable object position from a file path, replace this entire region with that STS call. - + // TODO Task: https://github.com/microsoft/vscode-mssql/issues/22709 /** * Starts Move to Schema from a Database Projects tree file path. */ diff --git a/extensions/mssql/test/unit/sqlSymbolRenameProvider.test.ts b/extensions/mssql/test/unit/sqlSymbolRenameProvider.test.ts index 679299c10d..369b87def8 100644 --- a/extensions/mssql/test/unit/sqlSymbolRenameProvider.test.ts +++ b/extensions/mssql/test/unit/sqlSymbolRenameProvider.test.ts @@ -695,6 +695,70 @@ suite("SqlMoveToSchemaProvider Tests", () => { ); }); + test("ignores CREATE text in comments and string literals", async () => { + findFilesStub.resolves([vscode.Uri.file(defaultProjFile)]); + showQuickPickStub.resolves({ label: "hr" }); + sendRequestStub.withArgs(ListProjectSchemasRequest.type).resolves({ schemas: ["hr"] }); + sendRequestStub.withArgs(SqlMoveToSchemaRequest.type).resolves({ changes: {} }); + + const document = makeMoveDocument(sandbox, { + lineText: + "-- CREATE TABLE dbo.CommentedOut\n/* CREATE VIEW dbo.BlockCommentedOut */\nSELECT 'CREATE VIEW dbo.InString';\nCREATE TABLE dbo.RealTable;", + }); + sandbox.stub(vscode.workspace, "openTextDocument").resolves(document); + + await provider.runMoveToSchemaFromFilePath(defaultSqlFile); + + expect(sendRequestStub).to.have.been.calledWith( + SqlMoveToSchemaRequest.type, + sinon.match({ + position: { line: 3, character: 17 }, + }), + ); + }); + + test("resolves the object token for CREATE OR ALTER with quoted names", async () => { + findFilesStub.resolves([vscode.Uri.file(defaultProjFile)]); + showQuickPickStub.resolves({ label: "hr" }); + sendRequestStub.withArgs(ListProjectSchemasRequest.type).resolves({ schemas: ["hr"] }); + sendRequestStub.withArgs(SqlMoveToSchemaRequest.type).resolves({ changes: {} }); + + const document = makeMoveDocument(sandbox, { + lineText: "CREATE OR ALTER VIEW [sales].[Order Details] AS SELECT 1;", + }); + sandbox.stub(vscode.workspace, "openTextDocument").resolves(document); + + await provider.runMoveToSchemaFromFilePath(defaultSqlFile); + + expect(sendRequestStub).to.have.been.calledWith( + SqlMoveToSchemaRequest.type, + sinon.match({ + position: { line: 0, character: 29 }, + }), + ); + }); + + test("skips unsupported object types and finds a later supported definition", async () => { + findFilesStub.resolves([vscode.Uri.file(defaultProjFile)]); + showQuickPickStub.resolves({ label: "hr" }); + sendRequestStub.withArgs(ListProjectSchemasRequest.type).resolves({ schemas: ["hr"] }); + sendRequestStub.withArgs(SqlMoveToSchemaRequest.type).resolves({ changes: {} }); + + const document = makeMoveDocument(sandbox, { + lineText: "CREATE DATABASE MyDatabase;\nCREATE SEQUENCE dbo.OrderSequence;", + }); + sandbox.stub(vscode.workspace, "openTextDocument").resolves(document); + + await provider.runMoveToSchemaFromFilePath(defaultSqlFile); + + expect(sendRequestStub).to.have.been.calledWith( + SqlMoveToSchemaRequest.type, + sinon.match({ + position: { line: 1, character: 20 }, + }), + ); + }); + test("shows error when opening a file path fails", async () => { const error = new Error("File not found"); sandbox.stub(vscode.workspace, "openTextDocument").rejects(error);