mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Create a test case for module resolution when symlinked folder contents change and resolve modules
Test case for #22349
This commit is contained in:
@@ -7706,6 +7706,186 @@ namespace ts.projectSystem {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe("module resolution when symlinked folder contents change and resolve modules", () => {
|
||||
const projectRootPath = "/users/username/projects/myproject";
|
||||
const packages = `${projectRootPath}/javascript/packages`;
|
||||
const recognizersDateTime = `${packages}/recognizers-date-time`;
|
||||
const recognizersText = `${packages}/recognizers-text`;
|
||||
const recognizersTextDist = `${recognizersText}/dist`;
|
||||
const moduleName = "@microsoft/recognizers-text";
|
||||
const moduleNameInFile = `"${moduleName}"`;
|
||||
const recognizersDateTimeSrcFile: File = {
|
||||
path: `${recognizersDateTime}/src/datetime/baseDate.ts`,
|
||||
content: `import {C} from ${moduleNameInFile};
|
||||
new C();`
|
||||
};
|
||||
const recognizerDateTimeTsconfigPath = `${recognizersDateTime}/tsconfig.json`;
|
||||
const recognizerDateTimeTsconfigWithoutPathMapping: File = {
|
||||
path: recognizerDateTimeTsconfigPath,
|
||||
content: JSON.stringify({
|
||||
include: ["src"]
|
||||
})
|
||||
};
|
||||
const recognizerDateTimeTsconfigWithPathMapping: File = {
|
||||
path: recognizerDateTimeTsconfigPath,
|
||||
content: JSON.stringify({
|
||||
compilerOptions: {
|
||||
rootDir: "src",
|
||||
baseUrl: "./",
|
||||
paths: {
|
||||
"@microsoft/*": ["../*"]
|
||||
}
|
||||
},
|
||||
include: ["src"]
|
||||
})
|
||||
};
|
||||
const nodeModulesRecorgnizersText: SymLink = {
|
||||
path: `${recognizersDateTime}/node_modules/@microsoft/recognizers-text`,
|
||||
symLink: recognizersText
|
||||
};
|
||||
const recognizerTextSrcFile: File = {
|
||||
path: `${recognizersText}/src/recognizers-text.ts`,
|
||||
content: `export class C { method () { return 10; } }`
|
||||
};
|
||||
const recongnizerTextDistTypingFile: File = {
|
||||
path: `${recognizersTextDist}/types/recognizers-text.d.ts`,
|
||||
content: `export class C { method(): number; }`
|
||||
};
|
||||
const recongnizerTextPackageJson: File = {
|
||||
path: `${recognizersText}/package.json`,
|
||||
content: JSON.stringify({
|
||||
typings: "dist/types/recognizers-text.d.ts"
|
||||
})
|
||||
};
|
||||
const filesInProjectWithUnresolvedModule = [recognizerDateTimeTsconfigPath, libFile.path, recognizersDateTimeSrcFile.path];
|
||||
const filesInProjectWithResolvedModule = [...filesInProjectWithUnresolvedModule, recongnizerTextDistTypingFile.path];
|
||||
|
||||
function verifyErrors(session: TestSession, semanticErrors: protocol.Diagnostic[]) {
|
||||
session.clearMessages();
|
||||
const expectedSequenceId = session.getNextSeq();
|
||||
session.executeCommandSeq<protocol.GeterrRequest>({
|
||||
command: server.CommandNames.Geterr,
|
||||
arguments: {
|
||||
delay: 0,
|
||||
files: [recognizersDateTimeSrcFile.path],
|
||||
}
|
||||
});
|
||||
|
||||
const host = session.host;
|
||||
host.checkTimeoutQueueLengthAndRun(1);
|
||||
|
||||
checkErrorMessage(session, "syntaxDiag", { file: recognizersDateTimeSrcFile.path, diagnostics: [] });
|
||||
session.clearMessages();
|
||||
|
||||
host.runQueuedImmediateCallbacks(1);
|
||||
|
||||
checkErrorMessage(session, "semanticDiag", { file: recognizersDateTimeSrcFile.path, diagnostics: semanticErrors });
|
||||
session.clearMessages();
|
||||
|
||||
host.runQueuedImmediateCallbacks(1);
|
||||
|
||||
checkErrorMessage(session, "suggestionDiag", {
|
||||
file: recognizersDateTimeSrcFile.path,
|
||||
diagnostics: [],
|
||||
});
|
||||
checkCompleteEvent(session, 2, expectedSequenceId);
|
||||
}
|
||||
|
||||
function createSingleWatchMap(paths: string[]) {
|
||||
return arrayToMap(paths, p => p, () => 1);
|
||||
}
|
||||
|
||||
function verifyWatchedFilesAndDirectories(host: TestServerHost, files: string[], directories: string[]) {
|
||||
checkWatchedFilesDetailed(host, createSingleWatchMap(files.filter(f => f !== recognizersDateTimeSrcFile.path)));
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
||||
checkWatchedDirectoriesDetailed(host, createSingleWatchMap(directories), /*recursive*/ true);
|
||||
}
|
||||
|
||||
function createSessionAndOpenFile(host: TestServerHost) {
|
||||
const session = createSession(host, { canUseEvents: true });
|
||||
session.executeCommandSeq<protocol.OpenRequest>({
|
||||
command: protocol.CommandTypes.Open,
|
||||
arguments: {
|
||||
file: recognizersDateTimeSrcFile.path,
|
||||
projectRootPath
|
||||
}
|
||||
});
|
||||
return session;
|
||||
}
|
||||
|
||||
function verifyModuleResolution(withPathMapping: boolean) {
|
||||
describe(withPathMapping ? "when tsconfig file contains path mapping" : "when tsconfig does not contain path mapping", () => {
|
||||
const filesWithSources = [libFile, recognizersDateTimeSrcFile, withPathMapping ? recognizerDateTimeTsconfigWithPathMapping : recognizerDateTimeTsconfigWithoutPathMapping, recognizerTextSrcFile, recongnizerTextPackageJson];
|
||||
const filesWithNodeModulesSetup = [...filesWithSources, nodeModulesRecorgnizersText];
|
||||
const filesAfterCompilation = [...filesWithNodeModulesSetup, recongnizerTextDistTypingFile];
|
||||
|
||||
const watchedDirectoriesWithResolvedModule = [`${recognizersDateTime}/src`, withPathMapping ? packages : recognizersDateTime, ...getTypeRootsFromLocation(recognizersDateTime)];
|
||||
const watchedDirectoriesWithUnresolvedModule = [recognizersDateTime, ...watchedDirectoriesWithResolvedModule, ...getNodeModuleDirectories(packages)];
|
||||
|
||||
function verifyProjectWithResolvedModule(session: TestSession) {
|
||||
const projectService = session.getProjectService();
|
||||
const project = projectService.configuredProjects.get(recognizerDateTimeTsconfigPath);
|
||||
checkProjectActualFiles(project, filesInProjectWithResolvedModule);
|
||||
verifyWatchedFilesAndDirectories(session.host, filesInProjectWithResolvedModule, watchedDirectoriesWithResolvedModule);
|
||||
verifyErrors(session, []);
|
||||
}
|
||||
|
||||
function verifyProjectWithUnresolvedModule(session: TestSession) {
|
||||
const projectService = session.getProjectService();
|
||||
const project = projectService.configuredProjects.get(recognizerDateTimeTsconfigPath);
|
||||
checkProjectActualFiles(project, filesInProjectWithUnresolvedModule);
|
||||
verifyWatchedFilesAndDirectories(session.host, filesInProjectWithUnresolvedModule, watchedDirectoriesWithUnresolvedModule);
|
||||
const startOffset = recognizersDateTimeSrcFile.content.indexOf('"') + 1;
|
||||
verifyErrors(session, [
|
||||
createDiagnostic({ line: 1, offset: startOffset }, { line: 1, offset: startOffset + moduleNameInFile.length }, Diagnostics.Cannot_find_module_0, [moduleName])
|
||||
]);
|
||||
}
|
||||
|
||||
it("when project compiles from sources", () => {
|
||||
const host = createServerHost(filesWithSources);
|
||||
const session = createSessionAndOpenFile(host);
|
||||
verifyProjectWithUnresolvedModule(session);
|
||||
|
||||
host.reloadFS(filesAfterCompilation);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
verifyProjectWithResolvedModule(session);
|
||||
});
|
||||
|
||||
it("when project has node_modules setup but doesnt have modules in typings folder and then recompiles", () => {
|
||||
const host = createServerHost(filesWithNodeModulesSetup);
|
||||
const session = createSessionAndOpenFile(host);
|
||||
verifyProjectWithUnresolvedModule(session);
|
||||
|
||||
host.reloadFS(filesAfterCompilation);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
verifyProjectWithResolvedModule(session);
|
||||
});
|
||||
|
||||
it("when project recompiles after deleting generated folders", () => {
|
||||
const host = createServerHost(filesAfterCompilation);
|
||||
const session = createSessionAndOpenFile(host);
|
||||
|
||||
verifyProjectWithResolvedModule(session);
|
||||
|
||||
host.removeFolder(recognizersTextDist, /*recursive*/ true);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
verifyProjectWithUnresolvedModule(session);
|
||||
|
||||
host.ensureFileOrFolder(recongnizerTextDistTypingFile);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
|
||||
verifyProjectWithResolvedModule(session);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
verifyModuleResolution(/*withPathMapping*/ false);
|
||||
verifyModuleResolution(/*withPathMapping*/ true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("tsserverProjectSystem forceConsistentCasingInFileNames", () => {
|
||||
|
||||
@@ -93,25 +93,27 @@ interface Array<T> {}`
|
||||
return isString((<SymLink>fileOrFolderOrSymLink).symLink);
|
||||
}
|
||||
|
||||
interface FSEntry {
|
||||
interface FSEntryBase {
|
||||
path: Path;
|
||||
fullPath: string;
|
||||
modifiedTime: Date;
|
||||
}
|
||||
|
||||
interface FsFile extends FSEntry {
|
||||
interface FsFile extends FSEntryBase {
|
||||
content: string;
|
||||
fileSize?: number;
|
||||
}
|
||||
|
||||
interface FsFolder extends FSEntry {
|
||||
interface FsFolder extends FSEntryBase {
|
||||
entries: SortedArray<FSEntry>;
|
||||
}
|
||||
|
||||
interface FsSymLink extends FSEntry {
|
||||
interface FsSymLink extends FSEntryBase {
|
||||
symLink: string;
|
||||
}
|
||||
|
||||
type FSEntry = FsFile | FsFolder | FsSymLink;
|
||||
|
||||
function isFsFolder(s: FSEntry): s is FsFolder {
|
||||
return s && isArray((<FsFolder>s).entries);
|
||||
}
|
||||
@@ -599,6 +601,24 @@ interface Array<T> {}`
|
||||
}
|
||||
}
|
||||
|
||||
removeFolder(folderPath: string, recursive?: boolean) {
|
||||
const path = this.toFullPath(folderPath);
|
||||
const currentEntry = this.fs.get(path) as FsFolder;
|
||||
Debug.assert(isFsFolder(currentEntry));
|
||||
if (recursive && currentEntry.entries.length) {
|
||||
const subEntries = currentEntry.entries.slice();
|
||||
subEntries.forEach(fsEntry => {
|
||||
if (isFsFolder(fsEntry)) {
|
||||
this.removeFolder(fsEntry.fullPath, recursive);
|
||||
}
|
||||
else {
|
||||
this.removeFileOrFolder(fsEntry, returnFalse);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.removeFileOrFolder(currentEntry, returnFalse);
|
||||
}
|
||||
|
||||
// For overriding the methods
|
||||
invokeWatchedDirectoriesCallback(folderFullPath: string, relativePath: string) {
|
||||
invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath)), cb => this.directoryCallback(cb, relativePath));
|
||||
@@ -643,7 +663,7 @@ interface Array<T> {}`
|
||||
}
|
||||
}
|
||||
|
||||
private toFsEntry(path: string): FSEntry {
|
||||
private toFsEntry(path: string): FSEntryBase {
|
||||
const fullPath = getNormalizedAbsolutePath(path, this.currentDirectory);
|
||||
return {
|
||||
path: this.toPath(fullPath),
|
||||
|
||||
Reference in New Issue
Block a user