Skip to content

Commit

Permalink
Handle when output file would be in subFolder specified by outDir/dec…
Browse files Browse the repository at this point in the history
…larationDir

Fixes microsoft#35328
  • Loading branch information
sheetalkamat committed Nov 26, 2019
1 parent ea4e6b3 commit eeacbbf
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 45 deletions.
18 changes: 16 additions & 2 deletions src/server/project.ts
Expand Up @@ -1765,7 +1765,11 @@ namespace ts.server {
const dirPathWithTrailingDirectorySeparator = `${dirPath}${directorySeparator}`;
return forEachKey(
this.mapOfDeclarationDirectories!,
declDirPath => dirPath === declDirPath || startsWith(declDirPath, dirPathWithTrailingDirectorySeparator)
declDirPath => dirPath === declDirPath ||
// Any parent directory of declaration dir
startsWith(declDirPath, dirPathWithTrailingDirectorySeparator) ||
// Any directory inside declaration dir
startsWith(dirPath, `${declDirPath}/`)
);
}

Expand Down Expand Up @@ -1802,6 +1806,16 @@ namespace ts.server {
return this.fileOrDirectoryExistsUsingSource(path, /*isFile*/ false);
}

/**
* Call super.getDirectories only if directory actually present on the host
* This is needed to ensure that we arent getting directories that we fake about presence for
*/
getDirectories(path: string): string[] {
return !this.useSourceOfProjectReferenceRedirect() || !this.projectReferenceCallbacks || super.directoryExists(path) ?
super.getDirectories(path) :
[];
}

private realpathIfSymlinkedProjectReferenceDts(s: string): string | undefined {
return this.symlinkedFiles && this.symlinkedFiles.get(this.toPath(s));
}
Expand All @@ -1820,7 +1834,7 @@ namespace ts.server {
const directoryPath = ensureTrailingDirectorySeparator(this.toPath(directory));
if (this.symlinkedDirectories.has(directoryPath)) return;

const real = this.projectService.host.realpath!(directory);
const real = normalizePath(this.projectService.host.realpath!(directory));
let realPath: Path;
if (real === directory ||
(realPath = ensureTrailingDirectorySeparator(this.toPath(real))) === directoryPath) {
Expand Down
111 changes: 68 additions & 43 deletions src/testRunner/unittests/tsserver/projectReferences.ts
Expand Up @@ -1375,78 +1375,70 @@ function foo() {
});

describe("when references are monorepo like with symlinks", () => {
function verifySession(alreadyBuilt: boolean, extraOptions: CompilerOptions) {
const bPackageJson: File = {
path: `${projectRoot}/packages/B/package.json`,
content: JSON.stringify({
main: "lib/index.js",
types: "lib/index.d.ts"
})
};
interface Packages {
bPackageJson: File;
aTest: File;
bFoo: File;
bBar: File;
}
function verifySymlinkScenario(packages: () => Packages) {
describe("when solution is not built", () => {
it("with preserveSymlinks turned off", () => {
verifySession(packages(), /*alreadyBuilt*/ false, {});
});

it("with preserveSymlinks turned on", () => {
verifySession(packages(), /*alreadyBuilt*/ false, { preserveSymlinks: true });
});
});

describe("when solution is already built", () => {
it("with preserveSymlinks turned off", () => {
verifySession(packages(), /*alreadyBuilt*/ true, {});
});

it("with preserveSymlinks turned on", () => {
verifySession(packages(), /*alreadyBuilt*/ true, { preserveSymlinks: true });
});
});
}

function verifySession({ bPackageJson, aTest, bFoo, bBar }: Packages, alreadyBuilt: boolean, extraOptions: CompilerOptions) {
const aConfig = config("A", extraOptions, ["../B"]);
const bConfig = config("B", extraOptions);
const aIndex = index("A", `import { foo } from 'b';
import { bar } from 'b/lib/bar';
foo();
bar();`);
const bIndex = index("B", `export function foo() { }`);
const bBar: File = {
path: `${projectRoot}/packages/B/src/bar.ts`,
content: `export function bar() { }`
};
const bSymlink: SymLink = {
path: `${projectRoot}/node_modules/b`,
symLink: `${projectRoot}/packages/B`
};

const files = [libFile, bPackageJson, aConfig, bConfig, aIndex, bIndex, bBar, bSymlink];
const files = [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink];
const host = alreadyBuilt ?
createHost(files, [aConfig.path]) :
createServerHost(files);

// Create symlink in node module
const session = createSession(host, { canUseEvents: true });
openFilesForSession([aIndex], session);
openFilesForSession([aTest], session);
const service = session.getProjectService();
const project = service.configuredProjects.get(aConfig.path.toLowerCase())!;
assert.deepEqual(project.getAllProjectErrors(), []);
checkProjectActualFiles(
project,
[aConfig.path, aIndex.path, bIndex.path, bBar.path, libFile.path]
[aConfig.path, aTest.path, bFoo.path, bBar.path, libFile.path]
);
verifyGetErrRequest({
host,
session,
expected: [
{ file: aIndex, syntax: [], semantic: [], suggestion: [] }
{ file: aTest, syntax: [], semantic: [], suggestion: [] }
]
});
}

function verifySymlinkScenario(alreadyBuilt: boolean) {
it("with preserveSymlinks turned off", () => {
verifySession(alreadyBuilt, {});
});

it("with preserveSymlinks turned on", () => {
verifySession(alreadyBuilt, { preserveSymlinks: true });
});
}

describe("when solution is not built", () => {
verifySymlinkScenario(/*alreadyBuilt*/ false);
});

describe("when solution is already built", () => {
verifySymlinkScenario(/*alreadyBuilt*/ true);
});

function config(packageName: string, extraOptions: CompilerOptions, references?: string[]): File {
return {
path: `${projectRoot}/packages/${packageName}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
baseUrl: ".",
outDir: "lib",
rootDir: "src",
composite: true,
Expand All @@ -1458,12 +1450,45 @@ bar();`);
};
}

function index(packageName: string, content: string): File {
function file(packageName: string, fileName: string, content: string): File {
return {
path: `${projectRoot}/packages/${packageName}/src/index.ts`,
path: `${projectRoot}/packages/${packageName}/src/${fileName}`,
content
};
}

describe("when packageJson has types field and has index.ts", () => {
verifySymlinkScenario(() => ({
bPackageJson: {
path: `${projectRoot}/packages/B/package.json`,
content: JSON.stringify({
main: "lib/index.js",
types: "lib/index.d.ts"
})
},
aTest: file("A", "index.ts", `import { foo } from 'b';
import { bar } from 'b/lib/bar';
foo();
bar();`),
bFoo: file("B", "index.ts", `export function foo() { }`),
bBar: file("B", "bar.ts", `export function bar() { }`)
}));
});

describe("when referencing file from subFolder", () => {
verifySymlinkScenario(() => ({
bPackageJson: {
path: `${projectRoot}/packages/B/package.json`,
content: "{}"
},
aTest: file("A", "test.ts", `import { foo } from 'b/lib/foo';
import { bar } from 'b/lib/bar/foo';
foo();
bar();`),
bFoo: file("B", "foo.ts", `export function foo() { }`),
bBar: file("B", "bar/foo.ts", `export function bar() { }`)
}));
});
});
});
}
5 changes: 5 additions & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Expand Up @@ -8707,6 +8707,11 @@ declare namespace ts.server {
* If it is it returns true irrespective of whether that directory exists on host
*/
directoryExists(path: string): boolean;
/**
* Call super.getDirectories only if directory actually present on the host
* This is needed to ensure that we arent getting directories that we fake about presence for
*/
getDirectories(path: string): string[];
private realpathIfSymlinkedProjectReferenceDts;
private getRealpath;
private handleDirectoryCouldBeSymlink;
Expand Down

0 comments on commit eeacbbf

Please sign in to comment.