diff --git a/extensions/sql-database-projects/src/models/tree/baseTreeItem.ts b/extensions/sql-database-projects/src/models/tree/baseTreeItem.ts index 98d59d3066..c40d6d0735 100644 --- a/extensions/sql-database-projects/src/models/tree/baseTreeItem.ts +++ b/extensions/sql-database-projects/src/models/tree/baseTreeItem.ts @@ -13,9 +13,10 @@ export abstract class BaseProjectTreeItem { /** * Constructor * @param relativeProjectUri Project-relative URI that's compatible with the project tree + * @param sqlprojUri Full URI to the .sqlproj of this project * @param parent parent tree item */ - constructor(public relativeProjectUri: vscode.Uri, public parent?: BaseProjectTreeItem) { } + constructor(public relativeProjectUri: vscode.Uri, public sqlprojUri: vscode.Uri, public parent?: BaseProjectTreeItem) { } abstract get children(): BaseProjectTreeItem[]; diff --git a/extensions/sql-database-projects/src/models/tree/databaseReferencesTreeItem.ts b/extensions/sql-database-projects/src/models/tree/databaseReferencesTreeItem.ts index 368b6bede5..1cf9259004 100644 --- a/extensions/sql-database-projects/src/models/tree/databaseReferencesTreeItem.ts +++ b/extensions/sql-database-projects/src/models/tree/databaseReferencesTreeItem.ts @@ -18,15 +18,26 @@ import { IDatabaseReferenceProjectEntry } from 'sqldbproj'; export class DatabaseReferencesTreeItem extends BaseProjectTreeItem { private references: DatabaseReferenceTreeItem[] = []; - constructor(project: ProjectRootTreeItem) { - super(vscode.Uri.file(path.join(project.relativeProjectUri.fsPath, constants.databaseReferencesNodeName)), project); + /** + * Constructor + * @param projectNodeName Name of the project node. Used for creating the relative path of the Database References node to the project + * @param sqlprojUri Full URI to the .sqlproj + * @param databaseReferences Array of database references in the project + * @param project + */ + constructor(projectNodeName: string, sqlprojUri: vscode.Uri, databaseReferences: IDatabaseReferenceProjectEntry[], project: ProjectRootTreeItem) { + super(vscode.Uri.file(path.join(projectNodeName, constants.databaseReferencesNodeName)), sqlprojUri, project); - this.construct(); + this.construct(databaseReferences); } - private construct() { - for (const reference of (this.parent as ProjectRootTreeItem).project.databaseReferences) { - this.references.push(new DatabaseReferenceTreeItem(reference, this)); + private construct(databaseReferences: IDatabaseReferenceProjectEntry[]) { + if (!databaseReferences) { + return; + } + + for (const reference of databaseReferences) { + this.references.push(new DatabaseReferenceTreeItem(reference, this.relativeProjectUri, this.sqlprojUri, this)); } } @@ -44,8 +55,8 @@ export class DatabaseReferencesTreeItem extends BaseProjectTreeItem { } export class DatabaseReferenceTreeItem extends BaseProjectTreeItem { - constructor(private reference: IDatabaseReferenceProjectEntry, referencesTreeItem: DatabaseReferencesTreeItem) { - super(vscode.Uri.file(path.join(referencesTreeItem.relativeProjectUri.fsPath, reference.databaseName)), referencesTreeItem); + constructor(private reference: IDatabaseReferenceProjectEntry, referencesNodeRelativeProjectUri: vscode.Uri, sqlprojUri: vscode.Uri, referencesTreeItem: DatabaseReferencesTreeItem) { + super(vscode.Uri.file(path.join(referencesNodeRelativeProjectUri.fsPath, reference.databaseName)), sqlprojUri, referencesTreeItem); } public get children(): BaseProjectTreeItem[] { diff --git a/extensions/sql-database-projects/src/models/tree/fileFolderTreeItem.ts b/extensions/sql-database-projects/src/models/tree/fileFolderTreeItem.ts index 4d9e363115..a15a56e895 100644 --- a/extensions/sql-database-projects/src/models/tree/fileFolderTreeItem.ts +++ b/extensions/sql-database-projects/src/models/tree/fileFolderTreeItem.ts @@ -8,8 +8,7 @@ import * as path from 'path'; import * as utils from '../../common/utils'; import { BaseProjectTreeItem } from './baseTreeItem'; import { ProjectRootTreeItem } from './projectTreeItem'; -import { Project } from '../project'; -import { DatabaseProjectItemType } from '../../common/constants'; +import { DatabaseProjectItemType, sqlprojExtension } from '../../common/constants'; import { IconPathHelper } from '../../common/iconHelper'; /** @@ -19,8 +18,8 @@ export class FolderNode extends BaseProjectTreeItem { public fileChildren: { [childName: string]: (FolderNode | FileNode) } = {}; public fileSystemUri: vscode.Uri; - constructor(folderPath: vscode.Uri, parent: FolderNode | ProjectRootTreeItem) { - super(fsPathToProjectUri(folderPath, parent.root as ProjectRootTreeItem), parent); + constructor(folderPath: vscode.Uri, sqlprojUri: vscode.Uri, parent: FolderNode | ProjectRootTreeItem) { + super(fsPathToProjectUri(folderPath, sqlprojUri), sqlprojUri, parent); this.fileSystemUri = folderPath; } @@ -35,10 +34,6 @@ export class FolderNode extends BaseProjectTreeItem { return folderItem; } - - public get project(): Project { - return (this.parent).project; - } } /** @@ -47,8 +42,8 @@ export class FolderNode extends BaseProjectTreeItem { export class FileNode extends BaseProjectTreeItem { public fileSystemUri: vscode.Uri; - constructor(filePath: vscode.Uri, parent: FolderNode | ProjectRootTreeItem) { - super(fsPathToProjectUri(filePath, parent.root as ProjectRootTreeItem, true), parent); + constructor(filePath: vscode.Uri, sqlprojUri: vscode.Uri, parent: FolderNode | ProjectRootTreeItem) { + super(fsPathToProjectUri(filePath, sqlprojUri, true), sqlprojUri, parent); this.fileSystemUri = filePath; } @@ -107,8 +102,9 @@ export function sortFileFolderNodes(a: (FolderNode | FileNode), b: (FolderNode | /** * Converts a full filesystem URI to a project-relative URI that's compatible with the project tree */ -function fsPathToProjectUri(fileSystemUri: vscode.Uri, projectNode: ProjectRootTreeItem, isFile?: boolean): vscode.Uri { - const projBaseDir = projectNode.project.projectFolderPath; +function fsPathToProjectUri(fileSystemUri: vscode.Uri, sqlprojUri: vscode.Uri, isFile?: boolean): vscode.Uri { + const projBaseDir = path.dirname(sqlprojUri.fsPath); + const projectFolderName = path.basename(sqlprojUri.fsPath, sqlprojExtension); let localUri = ''; if (fileSystemUri.fsPath.startsWith(projBaseDir)) { @@ -120,5 +116,5 @@ function fsPathToProjectUri(fileSystemUri: vscode.Uri, projectNode: ProjectRootT localUri = parts[parts.length - 1]; } - return vscode.Uri.file(path.join(projectNode.relativeProjectUri.fsPath, localUri)); + return vscode.Uri.file(path.join(projectFolderName, localUri)); } diff --git a/extensions/sql-database-projects/src/models/tree/projectTreeItem.ts b/extensions/sql-database-projects/src/models/tree/projectTreeItem.ts index 6bfec3fff4..dd283a1c17 100644 --- a/extensions/sql-database-projects/src/models/tree/projectTreeItem.ts +++ b/extensions/sql-database-projects/src/models/tree/projectTreeItem.ts @@ -26,14 +26,17 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem { fileChildren: { [childName: string]: (fileTree.FolderNode | fileTree.FileNode) } = {}; project: Project; fileSystemUri: vscode.Uri; + projectNodeName: string; constructor(project: Project) { - super(vscode.Uri.parse(path.basename(project.projectFilePath, sqlprojExtension)), undefined); + super(vscode.Uri.parse(path.basename(project.projectFilePath, sqlprojExtension)), vscode.Uri.file(project.projectFilePath), undefined); this.project = project; this.fileSystemUri = vscode.Uri.file(project.projectFilePath); - this.databaseReferencesNode = new DatabaseReferencesTreeItem(this); - this.sqlCmdVariablesNode = new SqlCmdVariablesTreeItem(this); + this.projectNodeName = path.basename(project.projectFilePath, sqlprojExtension); + + this.databaseReferencesNode = new DatabaseReferencesTreeItem(this.projectNodeName, this.sqlprojUri, project.databaseReferences, this); + this.sqlCmdVariablesNode = new SqlCmdVariablesTreeItem(this.projectNodeName, this.sqlprojUri, project.sqlCmdVariables, this); this.construct(); } @@ -50,7 +53,7 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem { const projectItem = new vscode.TreeItem(this.fileSystemUri, collapsibleState); projectItem.contextValue = this.project.isSdkStyleProject ? DatabaseProjectItemType.project : DatabaseProjectItemType.legacyProject; projectItem.iconPath = IconPathHelper.databaseProject; - projectItem.label = path.basename(this.relativeProjectUri.fsPath, sqlprojExtension); + projectItem.label = this.projectNodeName; return projectItem; } @@ -80,17 +83,17 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem { switch (entry.type) { case EntryType.File: if (entry.sqlObjectType === ExternalStreamingJob) { - newNode = new fileTree.ExternalStreamingJobFileNode(entry.fsUri, parentNode); + newNode = new fileTree.ExternalStreamingJobFileNode(entry.fsUri, this.sqlprojUri, parentNode); } else if (entry.containsCreateTableStatement) { - newNode = new fileTree.TableFileNode(entry.fsUri, parentNode); + newNode = new fileTree.TableFileNode(entry.fsUri, this.sqlprojUri, parentNode); } else { - newNode = new fileTree.FileNode(entry.fsUri, parentNode); + newNode = new fileTree.FileNode(entry.fsUri, this.sqlprojUri, parentNode); } break; case EntryType.Folder: - newNode = new fileTree.FolderNode(entry.fsUri, parentNode); + newNode = new fileTree.FolderNode(entry.fsUri, this.sqlprojUri, parentNode); break; default: throw new Error(`Unknown EntryType: '${entry.type}'`); @@ -104,7 +107,7 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem { * Gets the immediate parent tree node for an entry in a project file */ private getEntryParentNode(entry: FileProjectEntry): fileTree.FolderNode | ProjectRootTreeItem { - const relativePathParts = utils.trimChars(utils.trimUri(vscode.Uri.file(this.project.projectFilePath), entry.fsUri), '/').split('/').slice(0, -1); // remove the last part because we only care about the parent + const relativePathParts = utils.trimChars(utils.trimUri(this.sqlprojUri, entry.fsUri), '/').split('/').slice(0, -1); // remove the last part because we only care about the parent if (relativePathParts.length === 0) { return this; // if nothing left after trimming the entry itself, must been root @@ -119,7 +122,7 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem { for (const part of relativePathParts) { if (current.fileChildren[part] === undefined) { const parentPath = current instanceof ProjectRootTreeItem ? path.dirname(current.fileSystemUri.fsPath) : current.fileSystemUri.fsPath; - current.fileChildren[part] = new fileTree.FolderNode(vscode.Uri.file(path.join(parentPath, part)), current); + current.fileChildren[part] = new fileTree.FolderNode(vscode.Uri.file(path.join(parentPath, part)), this.sqlprojUri, current); } if (current.fileChildren[part] instanceof fileTree.FileNode) { diff --git a/extensions/sql-database-projects/src/models/tree/sqlcmdVariableTreeItem.ts b/extensions/sql-database-projects/src/models/tree/sqlcmdVariableTreeItem.ts index cc368478fe..e918c1baf0 100644 --- a/extensions/sql-database-projects/src/models/tree/sqlcmdVariableTreeItem.ts +++ b/extensions/sql-database-projects/src/models/tree/sqlcmdVariableTreeItem.ts @@ -15,30 +15,35 @@ import { IconPathHelper } from '../../common/iconHelper'; * Folder for containing SQLCMD variable nodes in the tree */ export class SqlCmdVariablesTreeItem extends BaseProjectTreeItem { - private sqlcmdVariables: SqlCmdVariableTreeItem[] = []; + private sqlcmdVariableTreeItems: SqlCmdVariableTreeItem[] = []; - constructor(project: ProjectRootTreeItem) { - super(vscode.Uri.file(path.join(project.relativeProjectUri.fsPath, constants.sqlcmdVariablesNodeName)), project); + /** + * Constructor + * @param projectNodeName Name of the project node. Used for creating the relative path of the SQLCMD Variables node to the project + * @param sqlprojUri Full URI to the .sqlproj + * @param sqlCmdVariables Collection of SQLCMD variables in the project + * @param project + */ + constructor(projectNodeName: string, sqlprojUri: vscode.Uri, sqlCmdVariables: Record, project: ProjectRootTreeItem) { + super(vscode.Uri.file(path.join(projectNodeName, constants.sqlcmdVariablesNodeName)), sqlprojUri, project); - this.construct(); + this.construct(sqlCmdVariables); } - private construct() { - const sqlCmdVariables = (this.parent as ProjectRootTreeItem).project.sqlCmdVariables; - + private construct(sqlCmdVariables: Record) { if (!sqlCmdVariables) { return; } for (const sqlCmdVariable of Object.keys(sqlCmdVariables)) { if (sqlCmdVariable) { - this.sqlcmdVariables.push(new SqlCmdVariableTreeItem(sqlCmdVariable, this)); + this.sqlcmdVariableTreeItems.push(new SqlCmdVariableTreeItem(sqlCmdVariable, this.relativeProjectUri, this.sqlprojUri, this)); } } } public get children(): SqlCmdVariableTreeItem[] { - return this.sqlcmdVariables; + return this.sqlcmdVariableTreeItems; } public get treeItem(): vscode.TreeItem { @@ -54,8 +59,8 @@ export class SqlCmdVariablesTreeItem extends BaseProjectTreeItem { * Represents a SQLCMD variable in a .sqlproj */ export class SqlCmdVariableTreeItem extends BaseProjectTreeItem { - constructor(private sqlcmdVar: string, sqlcmdVarsTreeItem: SqlCmdVariablesTreeItem) { - super(vscode.Uri.file(path.join(sqlcmdVarsTreeItem.relativeProjectUri.fsPath, sqlcmdVar)), sqlcmdVarsTreeItem); + constructor(private sqlcmdVar: string, sqlprojUri: vscode.Uri, sqlCmdNodeRelativeProjectUri: vscode.Uri, sqlcmdVarsTreeItem: SqlCmdVariablesTreeItem) { + super(vscode.Uri.file(path.join(sqlCmdNodeRelativeProjectUri.fsPath, sqlcmdVar)), sqlprojUri, sqlcmdVarsTreeItem); } public get children(): BaseProjectTreeItem[] { diff --git a/extensions/sql-database-projects/src/test/projectTree.test.ts b/extensions/sql-database-projects/src/test/projectTree.test.ts index d56aa643bc..7d9b79dd27 100644 --- a/extensions/sql-database-projects/src/test/projectTree.test.ts +++ b/extensions/sql-database-projects/src/test/projectTree.test.ts @@ -18,30 +18,31 @@ describe('Project Tree tests', function (): void { it('Should correctly order tree nodes by type, then by name', function (): void { const root = os.platform() === 'win32' ? 'Z:\\' : '/'; - const parent = new ProjectRootTreeItem(new Project(vscode.Uri.file(`${root}Fake.sqlproj`).fsPath)); + const sqlprojUri = vscode.Uri.file(`${root}Fake.sqlproj`); + const parent = new ProjectRootTreeItem(new Project(sqlprojUri.fsPath)); let inputNodes: (FileNode | FolderNode)[] = [ - new FileNode(vscode.Uri.file(`${root}C`), parent), - new FileNode(vscode.Uri.file(`${root}D`), parent), - new FolderNode(vscode.Uri.file(`${root}Z`), parent), - new FolderNode(vscode.Uri.file(`${root}X`), parent), - new FileNode(vscode.Uri.file(`${root}B`), parent), - new FileNode(vscode.Uri.file(`${root}A`), parent), - new FolderNode(vscode.Uri.file(`${root}W`), parent), - new FolderNode(vscode.Uri.file(`${root}Y`), parent) + new FileNode(vscode.Uri.file(`${root}C`), sqlprojUri, parent), + new FileNode(vscode.Uri.file(`${root}D`), sqlprojUri, parent), + new FolderNode(vscode.Uri.file(`${root}Z`), sqlprojUri, parent), + new FolderNode(vscode.Uri.file(`${root}X`), sqlprojUri, parent), + new FileNode(vscode.Uri.file(`${root}B`), sqlprojUri, parent), + new FileNode(vscode.Uri.file(`${root}A`), sqlprojUri, parent), + new FolderNode(vscode.Uri.file(`${root}W`), sqlprojUri, parent), + new FolderNode(vscode.Uri.file(`${root}Y`), sqlprojUri, parent) ]; inputNodes = inputNodes.sort(sortFileFolderNodes); const expectedNodes: (FileNode | FolderNode)[] = [ - new FolderNode(vscode.Uri.file(`${root}W`), parent), - new FolderNode(vscode.Uri.file(`${root}X`), parent), - new FolderNode(vscode.Uri.file(`${root}Y`), parent), - new FolderNode(vscode.Uri.file(`${root}Z`), parent), - new FileNode(vscode.Uri.file(`${root}A`), parent), - new FileNode(vscode.Uri.file(`${root}B`), parent), - new FileNode(vscode.Uri.file(`${root}C`), parent), - new FileNode(vscode.Uri.file(`${root}D`), parent) + new FolderNode(vscode.Uri.file(`${root}W`), sqlprojUri, parent), + new FolderNode(vscode.Uri.file(`${root}X`), sqlprojUri, parent), + new FolderNode(vscode.Uri.file(`${root}Y`), sqlprojUri, parent), + new FolderNode(vscode.Uri.file(`${root}Z`), sqlprojUri, parent), + new FileNode(vscode.Uri.file(`${root}A`), sqlprojUri, parent), + new FileNode(vscode.Uri.file(`${root}B`), sqlprojUri, parent), + new FileNode(vscode.Uri.file(`${root}C`), sqlprojUri, parent), + new FileNode(vscode.Uri.file(`${root}D`), sqlprojUri, parent) ]; should(inputNodes.map(n => n.relativeProjectUri.path)).deepEqual(expectedNodes.map(n => n.relativeProjectUri.path));