Add test case for symLink and rename

This commit is contained in:
Sheetal Nandi
2018-01-11 15:45:05 -08:00
parent 269867f5e8
commit ef7f131398
2 changed files with 224 additions and 22 deletions

View File

@@ -6560,4 +6560,111 @@ namespace ts.projectSystem {
checkProjectActualFiles(project, [file.path]);
});
});
describe("tsserverProjectSystem with symLinks", () => {
it("rename in common file renames all project", () => {
const projects = "/users/username/projects";
const folderA = `${projects}/a`;
const aFile: FileOrFolder = {
path: `${folderA}/a.ts`,
content: `import {C} from "./c/fc"; console.log(C)`
};
const aTsconfig: FileOrFolder = {
path: `${folderA}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { module: "commonjs" } })
};
const aC: FileOrFolder = {
path: `${folderA}/c`,
symLink: "../c"
};
const aFc = `${folderA}/c/fc.ts`;
const folderB = `${projects}/b`;
const bFile: FileOrFolder = {
path: `${folderB}/b.ts`,
content: `import {C} from "./c/fc"; console.log(C)`
};
const bTsconfig: FileOrFolder = {
path: `${folderB}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { module: "commonjs" } })
};
const bC: FileOrFolder = {
path: `${folderB}/c`,
symLink: "../c"
};
const bFc = `${folderB}/c/fc.ts`;
const folderC = `${projects}/c`;
const cFile: FileOrFolder = {
path: `${folderC}/fc.ts`,
content: `export const C = 8`
};
const files = [cFile, libFile, aFile, aTsconfig, aC, bFile, bTsconfig, bC];
const host = createServerHost(files);
const session = createSession(host);
const projectService = session.getProjectService();
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
arguments: {
file: aFile.path,
projectRootPath: folderA
}
});
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
arguments: {
file: bFile.path,
projectRootPath: folderB
}
});
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
arguments: {
file: aFc,
projectRootPath: folderA
}
});
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
arguments: {
file: bFc,
projectRootPath: folderB
}
});
checkNumberOfProjects(projectService, { configuredProjects: 2 });
assert.isDefined(projectService.configuredProjects.get(aTsconfig.path));
assert.isDefined(projectService.configuredProjects.get(bTsconfig.path));
verifyRenameResponse(session.executeCommandSeq<protocol.RenameRequest>({
command: protocol.CommandTypes.Rename,
arguments: {
file: aFc,
line: 1,
offset: 14,
findInStrings: false,
findInComments: false
}
}).response as protocol.RenameResponseBody);
function verifyRenameResponse({ info, locs }: protocol.RenameResponseBody) {
assert.isTrue(info.canRename);
assert.equal(locs.length, 2); // Currently 2 but needs to be 4
assert.deepEqual(locs[0], {
file: aFile.path,
locs: [
{ start: { line: 1, offset: 39 }, end: { line: 1, offset: 40 } },
{ start: { line: 1, offset: 9 }, end: { line: 1, offset: 10 } }
]
});
assert.deepEqual(locs[1], {
file: aFc,
locs: [
{ start: { line: 1, offset: 14 }, end: { line: 1, offset: 15 } }
]
});
}
});
});
}

View File

@@ -70,6 +70,7 @@ interface Array<T> {}`
path: string;
content?: string;
fileSize?: number;
symLink?: string;
}
interface FSEntry {
@@ -86,6 +87,10 @@ interface Array<T> {}`
entries: FSEntry[];
}
interface SymLink extends FSEntry {
symLink: Path;
}
function isFolder(s: FSEntry): s is Folder {
return s && isArray((<Folder>s).entries);
}
@@ -94,6 +99,10 @@ interface Array<T> {}`
return s && isString((<File>s).content);
}
function isSymLink(s: FSEntry): s is SymLink {
return s && isString((<SymLink>s).symLink);
}
function invokeWatcherCallbacks<T>(callbacks: T[], invokeCallback: (cb: T) => void): void {
if (callbacks) {
// The array copy is made to ensure that even if one of the callback removes the callbacks,
@@ -316,9 +325,12 @@ interface Array<T> {}`
}
}
else {
// TODO: Changing from file => folder
// TODO: Changing from file => folder/Symlink
}
}
else if (isSymLink(currentEntry)) {
// TODO: update symlinks
}
else {
// Folder
if (isString(fileOrDirectory.content)) {
@@ -339,7 +351,7 @@ interface Array<T> {}`
// If this entry is not from the new file or folder
if (!mapNewLeaves.get(path)) {
// Leaf entries that arent in new list => remove these
if (isFile(fileOrDirectory) || isFolder(fileOrDirectory) && fileOrDirectory.entries.length === 0) {
if (isFile(fileOrDirectory) || isSymLink(fileOrDirectory) || isFolder(fileOrDirectory) && fileOrDirectory.entries.length === 0) {
this.removeFileOrFolder(fileOrDirectory, folder => !mapNewLeaves.get(folder.path));
}
}
@@ -387,6 +399,12 @@ interface Array<T> {}`
const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath));
this.addFileOrFolderInFolder(baseFolder, file, ignoreWatchInvokedWithTriggerAsFileCreate);
}
else if (isString(fileOrDirectory.symLink)) {
const symLink = this.toSymLink(fileOrDirectory);
Debug.assert(!this.fs.get(symLink.path));
const baseFolder = this.ensureFolder(getDirectoryPath(symLink.fullPath));
this.addFileOrFolderInFolder(baseFolder, symLink, ignoreWatchInvokedWithTriggerAsFileCreate);
}
else {
const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory);
this.ensureFolder(fullPath);
@@ -414,20 +432,20 @@ interface Array<T> {}`
return folder;
}
private addFileOrFolderInFolder(folder: Folder, fileOrDirectory: File | Folder, ignoreWatch?: boolean) {
private addFileOrFolderInFolder(folder: Folder, fileOrDirectory: File | Folder | SymLink, ignoreWatch?: boolean) {
folder.entries.push(fileOrDirectory);
this.fs.set(fileOrDirectory.path, fileOrDirectory);
if (ignoreWatch) {
return;
}
if (isFile(fileOrDirectory)) {
if (isFile(fileOrDirectory) || isSymLink(fileOrDirectory)) {
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Created);
}
this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath);
}
private removeFileOrFolder(fileOrDirectory: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean, isRenaming?: boolean) {
private removeFileOrFolder(fileOrDirectory: File | Folder | SymLink, isRemovableLeafFolder: (folder: Folder) => boolean, isRenaming?: boolean) {
const basePath = getDirectoryPath(fileOrDirectory.path);
const baseFolder = this.fs.get(basePath) as Folder;
if (basePath !== fileOrDirectory.path) {
@@ -436,7 +454,7 @@ interface Array<T> {}`
}
this.fs.delete(fileOrDirectory.path);
if (isFile(fileOrDirectory)) {
if (isFile(fileOrDirectory) || isSymLink(fileOrDirectory)) {
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
}
else {
@@ -503,6 +521,15 @@ interface Array<T> {}`
};
}
private toSymLink(fileOrDirectory: FileOrFolder): SymLink {
const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory);
return {
path: this.toPath(fullPath),
fullPath,
symLink: this.toPath(getNormalizedAbsolutePath(fileOrDirectory.symLink, getDirectoryPath(fullPath)))
};
}
private toFolder(path: string): Folder {
const fullPath = getNormalizedAbsolutePath(path, this.currentDirectory);
return {
@@ -512,14 +539,70 @@ interface Array<T> {}`
};
}
fileExists(s: string) {
const path = this.toFullPath(s);
return isFile(this.fs.get(path));
private isFile(fsEntry: FSEntry) {
return !!this.getRealFile(fsEntry.path, fsEntry);
}
readFile(s: string) {
const fsEntry = this.fs.get(this.toFullPath(s));
return isFile(fsEntry) ? fsEntry.content : undefined;
private getRealFile(path: Path, fsEntry = this.fs.get(path)): File | undefined {
if (isFile(fsEntry)) {
return fsEntry;
}
if (isSymLink(fsEntry)) {
return this.getRealFile(fsEntry.symLink);
}
if (fsEntry) {
// This fs entry is something else
return undefined;
}
const dir = getDirectoryPath(path);
const dirEntry = this.getRealFolder(dir);
if (dirEntry && dirEntry.path !== dir) {
return this.getRealFile(combinePaths(dirEntry.path, getBaseFileName(path)) as Path);
}
return undefined;
}
private isFolder(fsEntry: FSEntry) {
return !!this.getRealFolder(fsEntry.path, fsEntry);
}
private getRealFolder(path: Path, fsEntry = this.fs.get(path)): Folder | undefined {
if (isFolder(fsEntry)) {
return fsEntry;
}
if (isSymLink(fsEntry)) {
return this.getRealFolder(fsEntry.symLink);
}
if (fsEntry) {
// This fs entry is something else
return undefined;
}
const baseName = getBaseFileName(path);
const dir = getDirectoryPath(path);
if (dir !== baseName) {
const dirEntry = this.getRealFolder(dir);
if (dirEntry && dirEntry.path !== dir) {
return this.getRealFolder(combinePaths(dirEntry.path, baseName) as Path);
}
}
return undefined;
}
fileExists(s: string) {
const path = this.toFullPath(s);
return !!this.getRealFile(path);
}
readFile(s: string): string {
const fsEntry = this.getRealFile(this.toFullPath(s));
return fsEntry ? fsEntry.content : undefined;
}
getFileSize(s: string) {
@@ -533,14 +616,14 @@ interface Array<T> {}`
directoryExists(s: string) {
const path = this.toFullPath(s);
return isFolder(this.fs.get(path));
return !!this.getRealFolder(path);
}
getDirectories(s: string) {
getDirectories(s: string): string[] {
const path = this.toFullPath(s);
const folder = this.fs.get(path);
if (isFolder(folder)) {
return mapDefined(folder.entries, entry => isFolder(entry) ? getBaseFileName(entry.fullPath) : undefined);
const folder = this.getRealFolder(path);
if (folder) {
return mapDefined(folder.entries, entry => this.isFolder(entry) ? getBaseFileName(entry.fullPath) : undefined);
}
Debug.fail(folder ? "getDirectories called on file" : "getDirectories called on missing folder");
return [];
@@ -550,13 +633,13 @@ interface Array<T> {}`
return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => {
const directories: string[] = [];
const files: string[] = [];
const dirEntry = this.fs.get(this.toPath(dir));
if (isFolder(dirEntry)) {
dirEntry.entries.forEach((entry) => {
if (isFolder(entry)) {
const folder = this.getRealFolder(this.toPath(dir));
if (folder) {
folder.entries.forEach((entry) => {
if (this.isFolder(entry)) {
directories.push(getBaseFileName(entry.fullPath));
}
else if (isFile(entry)) {
else if (this.isFile(entry)) {
files.push(getBaseFileName(entry.fullPath));
}
else {
@@ -682,6 +765,18 @@ interface Array<T> {}`
clear(this.output);
}
realpath(s: string) {
while (true) {
const fsEntry = this.fs.get(this.toFullPath(s));
if (isSymLink(fsEntry)) {
s = fsEntry.symLink;
}
else {
return s;
}
}
}
readonly existMessage = "System Exit";
exitCode: number;
readonly resolvePath = (s: string) => s;