mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-07-04 14:56:16 -05:00
Add test case for symLink and rename
This commit is contained in:
@@ -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 } }
|
||||
]
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user