diff --git a/extensions/sql-database-projects/src/common/utils.ts b/extensions/sql-database-projects/src/common/utils.ts index d3f73e44b7..06da7fe245 100644 --- a/extensions/sql-database-projects/src/common/utils.ts +++ b/extensions/sql-database-projects/src/common/utils.ts @@ -97,6 +97,16 @@ export function getSafeNonWindowsPath(filePath: string): string { return '"' + filePath + '"'; } +/** + * Get safe relative path for Windows and non-Windows Platform + * This is needed to read sqlproj entried created on SSDT and opened in MAC + * '/' in tree is recognized all platforms but "\\" only by windows + */ +export function getPlatformSafeFileEntryPath(filePath: string): string { + const parts = filePath.split('\\'); + return parts.join('/'); +} + /** * Read SQLCMD variables from xmlDoc and return them * @param xmlDoc xml doc to read SQLCMD variables from. Format must be the same that sqlproj and publish profiles use diff --git a/extensions/sql-database-projects/src/controllers/projectController.ts b/extensions/sql-database-projects/src/controllers/projectController.ts index e5bd72297c..2359dbc772 100644 --- a/extensions/sql-database-projects/src/controllers/projectController.ts +++ b/extensions/sql-database-projects/src/controllers/projectController.ts @@ -85,11 +85,19 @@ export class ProjectsController { // TODO: prompt to create new datasources.json; for now, swallow } else { + this.projects = this.projects.filter((e) => { return e !== newProject; }); throw err; } } - this.refreshProjectsTree(); + try { + this.refreshProjectsTree(); + } + catch (err) { + // if the project didnt load - remove it from the list of open projects + this.projects = this.projects.filter((e) => { return e !== newProject; }); + throw err; + } return newProject; } diff --git a/extensions/sql-database-projects/src/models/project.ts b/extensions/sql-database-projects/src/models/project.ts index 3823d2f5c8..709604157a 100644 --- a/extensions/sql-database-projects/src/models/project.ts +++ b/extensions/sql-database-projects/src/models/project.ts @@ -74,7 +74,8 @@ export class Project { throw new Error(constants.invalidDatabaseReference); } - this.databaseReferences.push(path.parse(filepath).name); + const platformSafeFilePath = utils.getPlatformSafeFileEntryPath(filepath); + this.databaseReferences.push(path.parse(platformSafeFilePath).name); } } } @@ -250,7 +251,8 @@ export class Project { } public createProjectEntry(relativePath: string, entryType: EntryType): ProjectEntry { - return new ProjectEntry(Uri.file(path.join(this.projectFolderPath, relativePath)), relativePath, entryType); + let platformSafeRelativePath = utils.getPlatformSafeFileEntryPath(relativePath); + return new ProjectEntry(Uri.file(path.join(this.projectFolderPath, platformSafeRelativePath)), relativePath, entryType); } private findOrCreateItemGroup(containedTag?: string): any { diff --git a/extensions/sql-database-projects/src/test/projectController.test.ts b/extensions/sql-database-projects/src/test/projectController.test.ts index 12b2489910..3f0619fb74 100644 --- a/extensions/sql-database-projects/src/test/projectController.test.ts +++ b/extensions/sql-database-projects/src/test/projectController.test.ts @@ -79,6 +79,20 @@ describe.skip('ProjectsController: project controller operations', function (): should(project.dataSources.length).equal(2); // detailed datasources tests in their own test file }); + it('Should not keep failed to load project in project list.', async function (): Promise { + const folderPath = await testUtils.generateTestFolderPath(); + const sqlProjPath = await testUtils.createTestSqlProjFile('empty file with no valid xml', folderPath); + const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider()); + + try { + await projController.openProject(vscode.Uri.file(sqlProjPath)); + should.fail(null, null, 'The given project not expected to open'); + } + catch { + should(projController.projects.length).equal(0, 'The added project should be removed'); + } + }); + it('Should return silently when no SQL object name provided in prompts', async function (): Promise { for (const name of ['', ' ', undefined]) { testContext.apiWrapper.reset(); diff --git a/extensions/sql-database-projects/src/test/projectTree.test.ts b/extensions/sql-database-projects/src/test/projectTree.test.ts index 835d1320ec..ca4c64e32d 100644 --- a/extensions/sql-database-projects/src/test/projectTree.test.ts +++ b/extensions/sql-database-projects/src/test/projectTree.test.ts @@ -93,4 +93,27 @@ describe.skip('Project Tree tests', function (): void { DatabaseProjectItemType.file, DatabaseProjectItemType.file]); }); + + it('Should be able to parse windows relative path as platform safe path', async function (): Promise { + const root = os.platform() === 'win32' ? 'Z:\\' : '/'; + const proj = new Project(vscode.Uri.file(`${root}TestProj.sqlproj`).fsPath); + + // nested entries before explicit top-level folder entry + // also, ordering of files/folders at all levels + proj.files.push(proj.createProjectEntry('someFolder1\\MyNestedFolder1\\MyFile1.sql', EntryType.File)); + proj.files.push(proj.createProjectEntry('someFolder1\\MyNestedFolder2', EntryType.Folder)); + proj.files.push(proj.createProjectEntry('someFolder1\\MyFile2.sql', EntryType.File)); + + const tree = new ProjectRootTreeItem(proj); + should(tree.children.map(x => x.uri.path)).deepEqual([ + '/TestProj.sqlproj/Data Sources', + '/TestProj.sqlproj/Database References', + '/TestProj.sqlproj/someFolder1']); + + // Why are we only matching names - https://github.com/microsoft/azuredatastudio/issues/11026 + should(tree.children.find(x => x.uri.path === '/TestProj.sqlproj/someFolder1')?.children.map(y => path.basename(y.uri.path))).deepEqual([ + 'MyNestedFolder1', + 'MyNestedFolder2', + 'MyFile2.sql']); + }); });