Skip to content

Commit

Permalink
Handle output file names descripency between tsc --b and actual progr…
Browse files Browse the repository at this point in the history
…am emit file path calculation (microsoft#41811)

* Baseline showing microsoft#41801 and other issues with output path calculation

* Add a way to note descripencies between clean and incremental build

* Add descripency when no rootDir is specified but project is composite

* if rootDir is specified, irrespective of whether all files belong to rootDir, the paths should be calculated from rootDir

* Fix the output file names api to use the correct common source directory

* Tests for microsoft#41780

* Spelling
  • Loading branch information
sheetalkamat authored and scalder27 committed Dec 7, 2020
1 parent 031ec5c commit e086565
Show file tree
Hide file tree
Showing 23 changed files with 745 additions and 87 deletions.
74 changes: 57 additions & 17 deletions src/compiler/emitter.ts
Expand Up @@ -130,36 +130,32 @@ namespace ts {
return Extension.Js;
}

function rootDirOfOptions(configFile: ParsedCommandLine) {
return configFile.options.rootDir || getDirectoryPath(Debug.checkDefined(configFile.options.configFilePath));
}

function getOutputPathWithoutChangingExt(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, outputDir: string | undefined) {
function getOutputPathWithoutChangingExt(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, outputDir: string | undefined, getCommonSourceDirectory?: () => string) {
return outputDir ?
resolvePath(
outputDir,
getRelativePathFromDirectory(rootDirOfOptions(configFile), inputFileName, ignoreCase)
getRelativePathFromDirectory(getCommonSourceDirectory ? getCommonSourceDirectory() : getCommonSourceDirectoryOfConfig(configFile, ignoreCase), inputFileName, ignoreCase)
) :
inputFileName;
}

/* @internal */
export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) {
export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) {
Debug.assert(!fileExtensionIs(inputFileName, Extension.Dts) && !fileExtensionIs(inputFileName, Extension.Json));
return changeExtension(
getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.declarationDir || configFile.options.outDir),
getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.declarationDir || configFile.options.outDir, getCommonSourceDirectory),
Extension.Dts
);
}

function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) {
function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) {
if (configFile.options.emitDeclarationOnly) return undefined;
const isJsonFile = fileExtensionIs(inputFileName, Extension.Json);
const outputFileName = changeExtension(
getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir),
getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir, getCommonSourceDirectory),
isJsonFile ?
Extension.Json :
fileExtensionIs(inputFileName, Extension.Tsx) && configFile.options.jsx === JsxEmit.Preserve ?
configFile.options.jsx === JsxEmit.Preserve && (fileExtensionIs(inputFileName, Extension.Tsx) || fileExtensionIs(inputFileName, Extension.Jsx)) ?
Extension.Jsx :
Extension.Js
);
Expand Down Expand Up @@ -190,32 +186,75 @@ namespace ts {
addOutput(buildInfoPath);
}

function getOwnOutputFileNames(configFile: ParsedCommandLine, inputFileName: string, ignoreCase: boolean, addOutput: ReturnType<typeof createAddOutput>["addOutput"]) {
function getOwnOutputFileNames(configFile: ParsedCommandLine, inputFileName: string, ignoreCase: boolean, addOutput: ReturnType<typeof createAddOutput>["addOutput"], getCommonSourceDirectory?: () => string) {
if (fileExtensionIs(inputFileName, Extension.Dts)) return;
const js = getOutputJSFileName(inputFileName, configFile, ignoreCase);
const js = getOutputJSFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory);
addOutput(js);
if (fileExtensionIs(inputFileName, Extension.Json)) return;
if (js && configFile.options.sourceMap) {
addOutput(`${js}.map`);
}
if (getEmitDeclarations(configFile.options)) {
const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase);
const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory);
addOutput(dts);
if (configFile.options.declarationMap) {
addOutput(`${dts}.map`);
}
}
}

/*@internal*/
export function getCommonSourceDirectory(
options: CompilerOptions,
emittedFiles: () => readonly string[],
currentDirectory: string,
getCanonicalFileName: GetCanonicalFileName,
checkSourceFilesBelongToPath?: (commonSourceDirectory: string) => void
): string {
let commonSourceDirectory;
if (options.rootDir) {
// If a rootDir is specified use it as the commonSourceDirectory
commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory);
checkSourceFilesBelongToPath?.(options.rootDir);
}
else if (options.composite && options.configFilePath) {
// Project compilations never infer their root from the input source paths
commonSourceDirectory = getDirectoryPath(normalizeSlashes(options.configFilePath));
checkSourceFilesBelongToPath?.(commonSourceDirectory);
}
else {
commonSourceDirectory = computeCommonSourceDirectoryOfFilenames(emittedFiles(), currentDirectory, getCanonicalFileName);
}

if (commonSourceDirectory && commonSourceDirectory[commonSourceDirectory.length - 1] !== directorySeparator) {
// Make sure directory path ends with directory separator so this string can directly
// used to replace with "" to get the relative path of the source file and the relative path doesn't
// start with / making it rooted path
commonSourceDirectory += directorySeparator;
}
return commonSourceDirectory;
}

/*@internal*/
export function getCommonSourceDirectoryOfConfig({ options, fileNames }: ParsedCommandLine, ignoreCase: boolean): string {
return getCommonSourceDirectory(
options,
() => filter(fileNames, file => !(options.noEmitForJsFiles && fileExtensionIsOneOf(file, supportedJSExtensions)) && !fileExtensionIs(file, Extension.Dts)),
getDirectoryPath(normalizeSlashes(Debug.checkDefined(options.configFilePath))),
createGetCanonicalFileName(!ignoreCase)
);
}

/*@internal*/
export function getAllProjectOutputs(configFile: ParsedCommandLine, ignoreCase: boolean): readonly string[] {
const { addOutput, getOutputs } = createAddOutput();
if (outFile(configFile.options)) {
getSingleOutputFileNames(configFile, addOutput);
}
else {
const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(configFile, ignoreCase));
for (const inputFileName of configFile.fileNames) {
getOwnOutputFileNames(configFile, inputFileName, ignoreCase, addOutput);
getOwnOutputFileNames(configFile, inputFileName, ignoreCase, addOutput, getCommonSourceDirectory);
}
addOutput(getTsBuildInfoEmitOutputFilePath(configFile.options));
}
Expand All @@ -242,13 +281,14 @@ namespace ts {
return Debug.checkDefined(jsFilePath, `project ${configFile.options.configFilePath} expected to have at least one output`);
}

const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(configFile, ignoreCase));
for (const inputFileName of configFile.fileNames) {
if (fileExtensionIs(inputFileName, Extension.Dts)) continue;
const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase);
const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory);
if (jsFilePath) return jsFilePath;
if (fileExtensionIs(inputFileName, Extension.Json)) continue;
if (getEmitDeclarations(configFile.options)) {
return getOutputDeclarationFileName(inputFileName, configFile, ignoreCase);
return getOutputDeclarationFileName(inputFileName, configFile, ignoreCase, getCommonSourceDirectory);
}
}
const buildInfoPath = getTsBuildInfoEmitOutputFilePath(configFile.options);
Expand Down
44 changes: 17 additions & 27 deletions src/compiler/program.ts
Expand Up @@ -13,7 +13,7 @@ namespace ts {
}

/* @internal */
export function computeCommonSourceDirectoryOfFilenames(fileNames: string[], currentDirectory: string, getCanonicalFileName: GetCanonicalFileName): string {
export function computeCommonSourceDirectoryOfFilenames(fileNames: readonly string[], currentDirectory: string, getCanonicalFileName: GetCanonicalFileName): string {
let commonPathComponents: string[] | undefined;
const failed = forEach(fileNames, sourceFile => {
// Each file contributes into common source file path
Expand Down Expand Up @@ -899,9 +899,15 @@ namespace ts {
processSourceFile(changeExtension(out, ".d.ts"), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
}
else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) {
const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(parsedRef.commandLine, !host.useCaseSensitiveFileNames()));
for (const fileName of parsedRef.commandLine.fileNames) {
if (!fileExtensionIs(fileName, Extension.Dts) && !fileExtensionIs(fileName, Extension.Json)) {
processSourceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames()), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
processSourceFile(
getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames(), getCommonSourceDirectory),
/*isDefaultLib*/ false,
/*ignoreNoDefaultLib*/ false,
/*packageId*/ undefined
);
}
}
}
Expand Down Expand Up @@ -1127,25 +1133,13 @@ namespace ts {
function getCommonSourceDirectory() {
if (commonSourceDirectory === undefined) {
const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program));
if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) {
// If a rootDir is specified use it as the commonSourceDirectory
commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory);
}
else if (options.composite && options.configFilePath) {
// Project compilations never infer their root from the input source paths
commonSourceDirectory = getDirectoryPath(normalizeSlashes(options.configFilePath));
checkSourceFilesBelongToPath(emittedFiles, commonSourceDirectory);
}
else {
commonSourceDirectory = computeCommonSourceDirectory(emittedFiles);
}

if (commonSourceDirectory && commonSourceDirectory[commonSourceDirectory.length - 1] !== directorySeparator) {
// Make sure directory path ends with directory separator so this string can directly
// used to replace with "" to get the relative path of the source file and the relative path doesn't
// start with / making it rooted path
commonSourceDirectory += directorySeparator;
}
commonSourceDirectory = ts.getCommonSourceDirectory(
options,
() => mapDefined(emittedFiles, file => file.isDeclarationFile ? undefined : file.fileName),
currentDirectory,
getCanonicalFileName,
commonSourceDirectory => checkSourceFilesBelongToPath(emittedFiles, commonSourceDirectory)
);
}
return commonSourceDirectory;
}
Expand Down Expand Up @@ -2707,9 +2701,10 @@ namespace ts {
mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), true);
}
else {
const getCommonSourceDirectory = memoize(() => getCommonSourceDirectoryOfConfig(resolvedRef.commandLine, !host.useCaseSensitiveFileNames()));
forEach(resolvedRef.commandLine.fileNames, fileName => {
if (!fileExtensionIs(fileName, Extension.Dts) && !fileExtensionIs(fileName, Extension.Json)) {
const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, host.useCaseSensitiveFileNames());
const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, !host.useCaseSensitiveFileNames(), getCommonSourceDirectory);
mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName);
}
});
Expand Down Expand Up @@ -2973,11 +2968,6 @@ namespace ts {
}
}

function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string {
const fileNames = mapDefined(sourceFiles, file => file.isDeclarationFile ? undefined : file.fileName);
return computeCommonSourceDirectoryOfFilenames(fileNames, currentDirectory, getCanonicalFileName);
}

function checkSourceFilesBelongToPath(sourceFiles: readonly SourceFile[], rootDirectory: string): boolean {
let allFilesBelongToPath = true;
const absoluteRootDirectoryPath = host.getCanonicalFileName(getNormalizedAbsolutePath(rootDirectory, currentDirectory));
Expand Down
1 change: 1 addition & 0 deletions src/testRunner/tsconfig.json
Expand Up @@ -125,6 +125,7 @@
"unittests/tsbuild/moduleSpecifiers.ts",
"unittests/tsbuild/noEmitOnError.ts",
"unittests/tsbuild/outFile.ts",
"unittests/tsbuild/outputPaths.ts",
"unittests/tsbuild/referencesWithRootDirInParent.ts",
"unittests/tsbuild/resolveJsonModule.ts",
"unittests/tsbuild/sample.ts",
Expand Down

0 comments on commit e086565

Please sign in to comment.