Skip to content

Commit 81ed511

Browse files
authored
Merge pull request #30402 from Microsoft/resolveJsonModule
Handle json files included in the project from project reference redirect
2 parents da48790 + b7c8e09 commit 81ed511

File tree

17 files changed

+183
-41
lines changed

17 files changed

+183
-41
lines changed

src/compiler/builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,7 @@ namespace ts {
980980
backupState: noop,
981981
restoreState: noop,
982982
getProgram: notImplemented,
983-
getProgramOrUndefined: () => undefined,
983+
getProgramOrUndefined: returnUndefined,
984984
releaseProgram: noop,
985985
getCompilerOptions: () => state.compilerOptions,
986986
getSourceFile: notImplemented,

src/compiler/core.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,6 +1614,9 @@ namespace ts {
16141614
/** Do nothing and return true */
16151615
export function returnTrue(): true { return true; }
16161616

1617+
/** Do nothing and return undefined */
1618+
export function returnUndefined(): undefined { return undefined; }
1619+
16171620
/** Returns its argument. */
16181621
export function identity<T>(x: T) { return x; }
16191622

src/compiler/emitter.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -135,23 +135,34 @@ namespace ts {
135135
return configFile.options.rootDir || getDirectoryPath(Debug.assertDefined(configFile.options.configFilePath));
136136
}
137137

138+
function getOutputPathWithoutChangingExt(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, outputDir: string | undefined) {
139+
return outputDir ?
140+
resolvePath(
141+
outputDir,
142+
getRelativePathFromDirectory(rootDirOfOptions(configFile), inputFileName, ignoreCase)
143+
) :
144+
inputFileName;
145+
}
146+
138147
/* @internal */
139148
export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) {
140149
Debug.assert(!fileExtensionIs(inputFileName, Extension.Dts) && hasTSFileExtension(inputFileName));
141-
const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile), inputFileName, ignoreCase);
142-
const outputPath = resolvePath(configFile.options.declarationDir || configFile.options.outDir || getDirectoryPath(Debug.assertDefined(configFile.options.configFilePath)), relativePath);
143-
return changeExtension(outputPath, Extension.Dts);
150+
return changeExtension(
151+
getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.declarationDir || configFile.options.outDir),
152+
Extension.Dts
153+
);
144154
}
145155

146156
function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) {
147-
const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile), inputFileName, ignoreCase);
148-
const outputPath = resolvePath(configFile.options.outDir || getDirectoryPath(Debug.assertDefined(configFile.options.configFilePath)), relativePath);
149157
const isJsonFile = fileExtensionIs(inputFileName, Extension.Json);
150-
const outputFileName = changeExtension(outputPath, isJsonFile ?
151-
Extension.Json :
152-
fileExtensionIs(inputFileName, Extension.Tsx) && configFile.options.jsx === JsxEmit.Preserve ?
153-
Extension.Jsx :
154-
Extension.Js);
158+
const outputFileName = changeExtension(
159+
getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir),
160+
isJsonFile ?
161+
Extension.Json :
162+
fileExtensionIs(inputFileName, Extension.Tsx) && configFile.options.jsx === JsxEmit.Preserve ?
163+
Extension.Jsx :
164+
Extension.Js
165+
);
155166
return !isJsonFile || comparePaths(inputFileName, outputFileName, Debug.assertDefined(configFile.options.configFilePath), ignoreCase) !== Comparison.EqualTo ?
156167
outputFileName :
157168
undefined;
@@ -203,6 +214,8 @@ namespace ts {
203214
const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase);
204215
if (jsFilePath) return jsFilePath;
205216
}
217+
const buildInfoPath = getOutputPathForBuildInfo(configFile.options);
218+
if (buildInfoPath) return buildInfoPath;
206219
return Debug.fail(`project ${configFile.options.configFilePath} expected to have at least one output`);
207220
}
208221

@@ -665,11 +678,12 @@ namespace ts {
665678
getCompilerOptions: () => config.options,
666679
getCurrentDirectory: () => host.getCurrentDirectory(),
667680
getNewLine: () => host.getNewLine(),
668-
getSourceFile: () => undefined,
669-
getSourceFileByPath: () => undefined,
681+
getSourceFile: returnUndefined,
682+
getSourceFileByPath: returnUndefined,
670683
getSourceFiles: () => sourceFilesForJsEmit,
671684
getLibFileFromReference: notImplemented,
672685
isSourceFileFromExternalLibrary: returnFalse,
686+
getResolvedProjectReferenceToRedirect: returnUndefined,
673687
writeFile: (name, text, writeByteOrderMark) => {
674688
switch (name) {
675689
case jsFilePath:
@@ -706,7 +720,7 @@ namespace ts {
706720
fileExists: f => host.fileExists(f),
707721
directoryExists: host.directoryExists && (f => host.directoryExists!(f)),
708722
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(),
709-
getProgramBuildInfo: () => undefined
723+
getProgramBuildInfo: returnUndefined
710724
};
711725
emitFiles(notImplementedResolver, emitHost, /*targetSourceFile*/ undefined, /*emitOnlyDtsFiles*/ false, getTransformers(config.options));
712726
return outputFiles;

src/compiler/factory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3405,7 +3405,7 @@ namespace ts {
34053405
export const nullTransformationContext: TransformationContext = {
34063406
enableEmitNotification: noop,
34073407
enableSubstitution: noop,
3408-
endLexicalEnvironment: () => undefined,
3408+
endLexicalEnvironment: returnUndefined,
34093409
getCompilerOptions: notImplemented,
34103410
getEmitHost: notImplemented,
34113411
getEmitResolver: notImplemented,

src/compiler/program.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,7 @@ namespace ts {
979979

980980
function getCommonSourceDirectory() {
981981
if (commonSourceDirectory === undefined) {
982-
const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary));
982+
const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect));
983983
if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) {
984984
// If a rootDir is specified use it as the commonSourceDirectory
985985
commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory);
@@ -1425,6 +1425,7 @@ namespace ts {
14251425
getSourceFiles: program.getSourceFiles,
14261426
getLibFileFromReference: program.getLibFileFromReference,
14271427
isSourceFileFromExternalLibrary,
1428+
getResolvedProjectReferenceToRedirect,
14281429
writeFile: writeFileCallback || (
14291430
(fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)),
14301431
isEmitBlocked,
@@ -2740,13 +2741,14 @@ namespace ts {
27402741

27412742
// List of collected files is complete; validate exhautiveness if this is a project with a file list
27422743
if (options.composite) {
2743-
const sourceFiles = files.filter(f => !f.isDeclarationFile);
2744-
if (rootNames.length < sourceFiles.length) {
2745-
const normalizedRootNames = rootNames.map(r => normalizePath(r).toLowerCase());
2746-
for (const file of sourceFiles.map(f => normalizePath(f.path).toLowerCase())) {
2747-
if (normalizedRootNames.indexOf(file) === -1) {
2748-
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_0_is_not_in_project_file_list_Projects_must_list_all_files_or_use_an_include_pattern, file));
2749-
}
2744+
const rootPaths = rootNames.map(toPath);
2745+
for (const file of files) {
2746+
// Ignore declaration files
2747+
if (file.isDeclarationFile) continue;
2748+
// Ignore json file thats from project reference
2749+
if (isJsonSourceFile(file) && getResolvedProjectReferenceToRedirect(file.fileName)) continue;
2750+
if (rootPaths.indexOf(file.path) === -1) {
2751+
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_0_is_not_in_project_file_list_Projects_must_list_all_files_or_use_an_include_pattern, file.fileName));
27502752
}
27512753
}
27522754
}
@@ -3158,7 +3160,7 @@ namespace ts {
31583160
readFile: f => directoryStructureHost.readFile(f),
31593161
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(),
31603162
getCurrentDirectory: () => host.getCurrentDirectory(),
3161-
onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic || (() => undefined),
3163+
onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic || returnUndefined,
31623164
trace: host.trace ? (s) => host.trace!(s) : undefined
31633165
};
31643166
}

src/compiler/tsbuild.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ namespace ts {
339339

340340
function createSolutionBuilderHostBase<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) {
341341
const host = createProgramHost(system, createProgram) as SolutionBuilderHostBase<T>;
342-
host.getModifiedTime = system.getModifiedTime ? path => system.getModifiedTime!(path) : () => undefined;
342+
host.getModifiedTime = system.getModifiedTime ? path => system.getModifiedTime!(path) : returnUndefined;
343343
host.setModifiedTime = system.setModifiedTime ? (path, date) => system.setModifiedTime!(path, date) : noop;
344344
host.deleteFile = system.deleteFile ? path => system.deleteFile!(path) : noop;
345345
host.reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system);
@@ -660,15 +660,16 @@ namespace ts {
660660
}
661661
}
662662

663-
// Collect the expected outputs of this project
664-
const outputs = getAllProjectOutputs(project, !host.useCaseSensitiveFileNames());
665-
666-
if (outputs.length === 0) {
663+
// Container if no files are specified in the project
664+
if (!project.fileNames.length && !canJsonReportNoInutFiles(project.raw)) {
667665
return {
668666
type: UpToDateStatusType.ContainerOnly
669667
};
670668
}
671669

670+
// Collect the expected outputs of this project
671+
const outputs = getAllProjectOutputs(project, !host.useCaseSensitiveFileNames());
672+
672673
// Now see if all outputs are newer than the newest input
673674
let oldestOutputFileName = "(none)";
674675
let oldestOutputFileTime = maximumDate;

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5337,6 +5337,7 @@ namespace ts {
53375337
getCurrentDirectory(): string;
53385338

53395339
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
5340+
getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
53405341
getLibFileFromReference(ref: FileReference): SourceFile | undefined;
53415342

53425343
getCommonSourceDirectory(): string;

src/compiler/utilities.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3422,22 +3422,31 @@ namespace ts {
34223422
export function getSourceFilesToEmit(host: EmitHost, targetSourceFile?: SourceFile): ReadonlyArray<SourceFile> {
34233423
const options = host.getCompilerOptions();
34243424
const isSourceFileFromExternalLibrary = (file: SourceFile) => host.isSourceFileFromExternalLibrary(file);
3425+
const getResolvedProjectReferenceToRedirect = (fileName: string) => host.getResolvedProjectReferenceToRedirect(fileName);
34253426
if (options.outFile || options.out) {
34263427
const moduleKind = getEmitModuleKind(options);
34273428
const moduleEmitEnabled = options.emitDeclarationOnly || moduleKind === ModuleKind.AMD || moduleKind === ModuleKind.System;
34283429
// Can emit only sources that are not declaration file and are either non module code or module with --module or --target es6 specified
34293430
return filter(host.getSourceFiles(), sourceFile =>
3430-
(moduleEmitEnabled || !isExternalModule(sourceFile)) && sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary));
3431+
(moduleEmitEnabled || !isExternalModule(sourceFile)) && sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect));
34313432
}
34323433
else {
34333434
const sourceFiles = targetSourceFile === undefined ? host.getSourceFiles() : [targetSourceFile];
3434-
return filter(sourceFiles, sourceFile => sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary));
3435+
return filter(sourceFiles, sourceFile => sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect));
34353436
}
34363437
}
34373438

34383439
/** Don't call this for `--outFile`, just for `--outDir` or plain emit. `--outFile` needs additional checks. */
3439-
export function sourceFileMayBeEmitted(sourceFile: SourceFile, options: CompilerOptions, isSourceFileFromExternalLibrary: (file: SourceFile) => boolean) {
3440-
return !(options.noEmitForJsFiles && isSourceFileJS(sourceFile)) && !sourceFile.isDeclarationFile && !isSourceFileFromExternalLibrary(sourceFile);
3440+
export function sourceFileMayBeEmitted(
3441+
sourceFile: SourceFile,
3442+
options: CompilerOptions,
3443+
isSourceFileFromExternalLibrary: (file: SourceFile) => boolean,
3444+
getResolvedProjectReferenceToRedirect: (fileName: string) => ResolvedProjectReference | undefined
3445+
) {
3446+
return !(options.noEmitForJsFiles && isSourceFileJS(sourceFile)) &&
3447+
!sourceFile.isDeclarationFile &&
3448+
!isSourceFileFromExternalLibrary(sourceFile) &&
3449+
!(isJsonSourceFile(sourceFile) && getResolvedProjectReferenceToRedirect(sourceFile.fileName));
34413450
}
34423451

34433452
export function getSourceFilePathInNewDir(fileName: string, host: EmitHost, newDirPath: string): string {

src/testRunner/unittests/tsbuild/resolveJsonModule.ts

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
namespace ts {
2-
describe("unittests:: tsbuild:: with resolveJsonModule option", () => {
2+
describe("unittests:: tsbuild:: with resolveJsonModule option on project resolveJsonModuleAndComposite", () => {
33
let projFs: vfs.FileSystem;
44
const { time, tick } = getTime();
55
const allExpectedOutputs = ["/src/dist/src/index.js", "/src/dist/src/index.d.ts", "/src/dist/src/hello.json"];
@@ -64,19 +64,89 @@ export default hello.hello`);
6464
const configFile = "src/tsconfig_withFiles.json";
6565
replaceText(fs, configFile, `"composite": true,`, `"composite": true, "sourceMap": true,`);
6666
const host = new fakes.SolutionBuilderHost(fs);
67-
const builder = createSolutionBuilder(host, [configFile], { verbose: false });
67+
const builder = createSolutionBuilder(host, [configFile], { verbose: true });
6868
builder.buildAllProjects();
69-
host.assertDiagnosticMessages();
69+
host.assertDiagnosticMessages(
70+
getExpectedDiagnosticForProjectsInBuild(configFile),
71+
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, configFile, "src/dist/src/index.js"],
72+
[Diagnostics.Building_project_0, `/${configFile}`]
73+
);
7074
for (const output of [...allExpectedOutputs, "/src/dist/src/index.js.map"]) {
7175
assert(fs.existsSync(output), `Expect file ${output} to exist`);
7276
}
77+
host.clearDiagnostics();
78+
builder.resetBuildContext();
7379
tick();
74-
const newBuilder = createSolutionBuilder(host, [configFile], { verbose: true });
75-
newBuilder.buildAllProjects();
80+
builder.buildAllProjects();
7681
host.assertDiagnosticMessages(
7782
getExpectedDiagnosticForProjectsInBuild(configFile),
7883
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, configFile, "src/src/index.ts", "src/dist/src/index.js"]
7984
);
8085
});
86+
87+
it("with resolveJsonModule and without outDir", () => {
88+
const fs = projFs.shadow();
89+
const configFile = "src/tsconfig_withFiles.json";
90+
replaceText(fs, configFile, `"outDir": "dist",`, "");
91+
const host = new fakes.SolutionBuilderHost(fs);
92+
const builder = createSolutionBuilder(host, [configFile], { verbose: true });
93+
builder.buildAllProjects();
94+
host.assertDiagnosticMessages(
95+
getExpectedDiagnosticForProjectsInBuild(configFile),
96+
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, configFile, "src/src/index.js"],
97+
[Diagnostics.Building_project_0, `/${configFile}`]
98+
);
99+
for (const output of ["/src/src/index.js", "/src/src/index.d.ts"]) {
100+
assert(fs.existsSync(output), `Expect file ${output} to exist`);
101+
}
102+
host.clearDiagnostics();
103+
builder.resetBuildContext();
104+
tick();
105+
builder.buildAllProjects();
106+
host.assertDiagnosticMessages(
107+
getExpectedDiagnosticForProjectsInBuild(configFile),
108+
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, configFile, "src/src/index.ts", "src/src/index.js"]
109+
);
110+
});
111+
});
112+
113+
describe("unittests:: tsbuild:: with resolveJsonModule option on project importJsonFromProjectReference", () => {
114+
const { time, tick } = getTime();
115+
let projFs: vfs.FileSystem;
116+
before(() => {
117+
projFs = loadProjectFromDisk("tests/projects/importJsonFromProjectReference", time);
118+
});
119+
120+
after(() => {
121+
projFs = undefined!; // Release the contents
122+
});
123+
124+
it("when importing json module from project reference", () => {
125+
const expectedOutput = "/src/main/index.js";
126+
const fs = projFs.shadow();
127+
const configFile = "src/tsconfig.json";
128+
const stringsConfigFile = "src/strings/tsconfig.json";
129+
const mainConfigFile = "src/main/tsconfig.json";
130+
const host = new fakes.SolutionBuilderHost(fs);
131+
const builder = createSolutionBuilder(host, [configFile], { verbose: true });
132+
builder.buildAllProjects();
133+
host.assertDiagnosticMessages(
134+
getExpectedDiagnosticForProjectsInBuild(stringsConfigFile, mainConfigFile, configFile),
135+
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, stringsConfigFile, "src/strings/tsconfig.tsbuildinfo"],
136+
[Diagnostics.Building_project_0, `/${stringsConfigFile}`],
137+
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, mainConfigFile, "src/main/index.js"],
138+
[Diagnostics.Building_project_0, `/${mainConfigFile}`],
139+
);
140+
assert(fs.existsSync(expectedOutput), `Expect file ${expectedOutput} to exist`);
141+
host.clearDiagnostics();
142+
builder.resetBuildContext();
143+
tick();
144+
builder.buildAllProjects();
145+
host.assertDiagnosticMessages(
146+
getExpectedDiagnosticForProjectsInBuild(stringsConfigFile, mainConfigFile, configFile),
147+
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, stringsConfigFile, "src/strings/foo.json", "src/strings/tsconfig.tsbuildinfo"],
148+
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, mainConfigFile, "src/main/index.ts", "src/main/index.js"],
149+
);
150+
});
81151
});
82152
}

src/testRunner/unittests/tsserver/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ namespace ts.projectSystem {
6464
msg: noop,
6565
startGroup: noop,
6666
endGroup: noop,
67-
getLogFileName: () => undefined,
67+
getLogFileName: returnUndefined,
6868
};
6969

7070
export function createHasErrorMessageLogger() {

0 commit comments

Comments
 (0)