mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 08:11:30 -06:00
Resolve project references transitively
This commit is contained in:
parent
10edf6fa58
commit
0ac96580d5
@ -1293,6 +1293,7 @@ namespace ts {
|
||||
|
||||
const result = parseJsonText(configFileName, configFileText);
|
||||
const cwd = host.getCurrentDirectory();
|
||||
result.path = toPath(configFileName, cwd, createGetCanonicalFileName(host.useCaseSensitiveFileNames));
|
||||
return parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd));
|
||||
}
|
||||
|
||||
|
||||
@ -454,6 +454,8 @@ namespace ts {
|
||||
return false;
|
||||
}
|
||||
|
||||
let seenResolvedRefs: ResolvedProjectReference[] | undefined;
|
||||
|
||||
// If project references dont match
|
||||
if (!arrayIsEqualTo(program.getProjectReferences(), projectReferences, projectReferenceUptoDate)) {
|
||||
return false;
|
||||
@ -496,11 +498,29 @@ namespace ts {
|
||||
if (!projectReferenceIsEqualTo(oldRef, newRef)) {
|
||||
return false;
|
||||
}
|
||||
const oldResolvedRef = program!.getResolvedProjectReferences()![index];
|
||||
return resolvedProjectReferenceUptoDate(program!.getResolvedProjectReferences()![index], oldRef);
|
||||
}
|
||||
|
||||
function resolvedProjectReferenceUptoDate(oldResolvedRef: ResolvedProjectReference | undefined, oldRef: ProjectReference): boolean {
|
||||
if (oldResolvedRef) {
|
||||
if (contains(seenResolvedRefs, oldResolvedRef)) {
|
||||
// Assume true
|
||||
return true;
|
||||
}
|
||||
|
||||
// If sourceFile for the oldResolvedRef existed, check the version for uptodate
|
||||
return sourceFileVersionUptoDate(oldResolvedRef.sourceFile);
|
||||
if (!sourceFileVersionUptoDate(oldResolvedRef.sourceFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add to seen before checking the referenced paths of this config file
|
||||
(seenResolvedRefs || (seenResolvedRefs = [])).push(oldResolvedRef);
|
||||
|
||||
// If child project references are upto date, this project reference is uptodate
|
||||
return !forEach(oldResolvedRef.references, (childResolvedRef, index) =>
|
||||
!resolvedProjectReferenceUptoDate(childResolvedRef, oldResolvedRef.commandLine.projectReferences![index]));
|
||||
}
|
||||
|
||||
// In old program, not able to resolve project reference path,
|
||||
// so if config file doesnt exist, it is uptodate.
|
||||
return !fileExists(resolveProjectReferencePath(oldRef));
|
||||
@ -662,8 +682,8 @@ namespace ts {
|
||||
const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createMap<SourceFile>() : undefined;
|
||||
|
||||
// A parallel array to projectReferences storing the results of reading in the referenced tsconfig files
|
||||
let resolvedProjectReferences: (ResolvedProjectReference | undefined)[] | undefined = projectReferences ? [] : undefined;
|
||||
let projectReferenceRedirects: ParsedCommandLine[] | undefined;
|
||||
let resolvedProjectReferences: ReadonlyArray<ResolvedProjectReference | undefined> | undefined;
|
||||
let projectReferenceRedirects: Map<ResolvedProjectReference | false> | undefined;
|
||||
|
||||
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
|
||||
const structuralIsReused = tryReuseStructureFromOldProgram();
|
||||
@ -672,16 +692,16 @@ namespace ts {
|
||||
processingOtherFiles = [];
|
||||
|
||||
if (projectReferences) {
|
||||
for (const ref of projectReferences) {
|
||||
const parsedRef = parseProjectReferenceConfigFile(ref);
|
||||
resolvedProjectReferences!.push(parsedRef);
|
||||
if (!resolvedProjectReferences) {
|
||||
resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile);
|
||||
}
|
||||
for (const parsedRef of resolvedProjectReferences) {
|
||||
if (parsedRef) {
|
||||
const out = parsedRef.commandLine.options.outFile || parsedRef.commandLine.options.out;
|
||||
if (out) {
|
||||
const dtsOutfile = changeExtension(out, ".d.ts");
|
||||
processSourceFile(dtsOutfile, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
|
||||
}
|
||||
addProjectReferenceRedirects(parsedRef.commandLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -740,6 +760,12 @@ namespace ts {
|
||||
|
||||
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
|
||||
oldProgram = undefined;
|
||||
// Do not use our own command line for projectReferenceRedirects
|
||||
if (projectReferenceRedirects) {
|
||||
Debug.assert(!!options.configFilePath);
|
||||
const path = toPath(options.configFilePath!);
|
||||
projectReferenceRedirects.delete(path);
|
||||
}
|
||||
|
||||
program = {
|
||||
getRootFileNames: () => rootNames,
|
||||
@ -1001,6 +1027,48 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function canReuseProjectReferences(
|
||||
newProjectReferences: ReadonlyArray<ProjectReference> | undefined,
|
||||
oldProjectReferences: ReadonlyArray<ProjectReference> | undefined,
|
||||
oldResolvedReferences: ReadonlyArray<ResolvedProjectReference | undefined> | undefined): boolean {
|
||||
// If array of references is changed, we cant resue old program
|
||||
if (!arrayIsEqualTo(oldProjectReferences!, newProjectReferences, projectReferenceIsEqualTo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the json files for the project references
|
||||
if (newProjectReferences) {
|
||||
// Resolved project referenced should be array if projectReferences provided are array
|
||||
Debug.assert(!!oldResolvedReferences);
|
||||
for (let i = 0; i < newProjectReferences.length; i++) {
|
||||
const oldRef = oldResolvedReferences![i];
|
||||
const newRef = parseProjectReferenceConfigFile(newProjectReferences[i]);
|
||||
if (oldRef) {
|
||||
if (!newRef || newRef.sourceFile !== oldRef.sourceFile) {
|
||||
// Resolved project reference has gone missing or changed
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the transitive references can be reused then only this reference can be reused
|
||||
if (!canReuseProjectReferences(newRef.commandLine.projectReferences, oldRef.commandLine.projectReferences, oldRef.references)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// A previously-unresolved reference may be resolved now
|
||||
if (newRef !== undefined) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Resolved project referenced should be undefined if projectReferences is undefined
|
||||
Debug.assert(!oldResolvedReferences);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function tryReuseStructureFromOldProgram(): StructureIsReused {
|
||||
if (!oldProgram) {
|
||||
return StructureIsReused.Not;
|
||||
@ -1026,39 +1094,10 @@ namespace ts {
|
||||
}
|
||||
|
||||
// Check if any referenced project tsconfig files are different
|
||||
|
||||
// If array of references is changed, we cant resue old program
|
||||
const oldProjectReferences = oldProgram.getProjectReferences();
|
||||
if (!arrayIsEqualTo(oldProjectReferences!, projectReferences, projectReferenceIsEqualTo)) {
|
||||
if (!canReuseProjectReferences(projectReferences, oldProgram.getProjectReferences(), oldProgram.getResolvedProjectReferences())) {
|
||||
return oldProgram.structureIsReused = StructureIsReused.Not;
|
||||
}
|
||||
|
||||
// Check the json files for the project references
|
||||
const oldRefs = oldProgram.getResolvedProjectReferences();
|
||||
if (projectReferences) {
|
||||
// Resolved project referenced should be array if projectReferences provided are array
|
||||
Debug.assert(!!oldRefs);
|
||||
for (let i = 0; i < projectReferences.length; i++) {
|
||||
const oldRef = oldRefs![i];
|
||||
const newRef = parseProjectReferenceConfigFile(projectReferences[i]);
|
||||
if (oldRef) {
|
||||
if (!newRef || newRef.sourceFile !== oldRef.sourceFile) {
|
||||
// Resolved project reference has gone missing or changed
|
||||
return oldProgram.structureIsReused = StructureIsReused.Not;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// A previously-unresolved reference may be resolved now
|
||||
if (newRef !== undefined) {
|
||||
return oldProgram.structureIsReused = StructureIsReused.Not;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Resolved project referenced should be undefined if projectReferences is undefined
|
||||
Debug.assert(!oldRefs);
|
||||
}
|
||||
resolvedProjectReferences = oldProgram.getResolvedProjectReferences();
|
||||
|
||||
// check if program source files has changed in the way that can affect structure of the program
|
||||
const newSourceFiles: SourceFile[] = [];
|
||||
@ -1248,14 +1287,6 @@ namespace ts {
|
||||
fileProcessingDiagnostics.reattachFileDiagnostics(modifiedFile.newFile);
|
||||
}
|
||||
resolvedTypeReferenceDirectives = oldProgram.getResolvedTypeReferenceDirectives();
|
||||
resolvedProjectReferences = oldProgram.getResolvedProjectReferences();
|
||||
if (resolvedProjectReferences) {
|
||||
resolvedProjectReferences.forEach(ref => {
|
||||
if (ref) {
|
||||
addProjectReferenceRedirects(ref.commandLine);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sourceFileToPackageName = oldProgram.sourceFileToPackageName;
|
||||
redirectTargetsMap = oldProgram.redirectTargetsMap;
|
||||
@ -2182,22 +2213,22 @@ namespace ts {
|
||||
|
||||
function getProjectReferenceRedirect(fileName: string): string | undefined {
|
||||
// Ignore dts or any of the non ts files
|
||||
if (!projectReferenceRedirects || fileExtensionIs(fileName, Extension.Dts) || !fileExtensionIsOneOf(fileName, supportedTSExtensions)) {
|
||||
if (!resolvedProjectReferences || !resolvedProjectReferences.length || fileExtensionIs(fileName, Extension.Dts) || !fileExtensionIsOneOf(fileName, supportedTSExtensions)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If this file is produced by a referenced project, we need to rewrite it to
|
||||
// look in the output folder of the referenced project rather than the input
|
||||
return forEach(projectReferenceRedirects, referencedProject => {
|
||||
return forEachEntry(projectReferenceRedirects!, referencedProject => {
|
||||
// not input file from the referenced project, ignore
|
||||
if (!contains(referencedProject.fileNames, fileName, isSameFile)) {
|
||||
if (!referencedProject || !contains(referencedProject.commandLine.fileNames, fileName, isSameFile)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const out = referencedProject.options.outFile || referencedProject.options.out;
|
||||
const out = referencedProject.commandLine.options.outFile || referencedProject.commandLine.options.out;
|
||||
return out ?
|
||||
changeExtension(out, Extension.Dts) :
|
||||
getOutputDeclarationFileName(fileName, referencedProject);
|
||||
getOutputDeclarationFileName(fileName, referencedProject.commandLine);
|
||||
});
|
||||
}
|
||||
|
||||
@ -2388,22 +2419,34 @@ namespace ts {
|
||||
return allFilesBelongToPath;
|
||||
}
|
||||
|
||||
function parseProjectReferenceConfigFile(ref: ProjectReference): { commandLine: ParsedCommandLine, sourceFile: SourceFile } | undefined {
|
||||
function parseProjectReferenceConfigFile(ref: ProjectReference): ResolvedProjectReference | undefined {
|
||||
if (!projectReferenceRedirects) {
|
||||
projectReferenceRedirects = createMap<ResolvedProjectReference | false>();
|
||||
}
|
||||
|
||||
// The actual filename (i.e. add "/tsconfig.json" if necessary)
|
||||
const refPath = resolveProjectReferencePath(ref);
|
||||
const sourceFilePath = toPath(refPath);
|
||||
const fromCache = projectReferenceRedirects.get(sourceFilePath);
|
||||
if (fromCache !== undefined) {
|
||||
return fromCache || undefined;
|
||||
}
|
||||
|
||||
// An absolute path pointing to the containing directory of the config file
|
||||
const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory());
|
||||
const sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined;
|
||||
if (sourceFile === undefined) {
|
||||
projectReferenceRedirects.set(sourceFilePath, false);
|
||||
return undefined;
|
||||
}
|
||||
sourceFile.path = toPath(refPath);
|
||||
sourceFile.path = sourceFilePath;
|
||||
const commandLine = parseJsonSourceFileConfigFileContent(sourceFile, configParsingHost, basePath, /*existingOptions*/ undefined, refPath);
|
||||
return { commandLine, sourceFile };
|
||||
}
|
||||
|
||||
function addProjectReferenceRedirects(referencedProject: ParsedCommandLine) {
|
||||
(projectReferenceRedirects || (projectReferenceRedirects = [])).push(referencedProject);
|
||||
const resolvedRef: ResolvedProjectReference = { commandLine, sourceFile };
|
||||
projectReferenceRedirects.set(sourceFilePath, resolvedRef);
|
||||
if (commandLine.projectReferences) {
|
||||
resolvedRef.references = commandLine.projectReferences.map(parseProjectReferenceConfigFile);
|
||||
}
|
||||
return resolvedRef;
|
||||
}
|
||||
|
||||
function verifyCompilerOptions() {
|
||||
|
||||
@ -2829,7 +2829,7 @@ namespace ts {
|
||||
/* @internal */ getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined;
|
||||
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference> | undefined;
|
||||
getResolvedProjectReferences(): (ResolvedProjectReference | undefined)[] | undefined;
|
||||
getResolvedProjectReferences(): ReadonlyArray<ResolvedProjectReference | undefined> | undefined;
|
||||
/*@internal*/ getProjectReferenceRedirect(fileName: string): string | undefined;
|
||||
}
|
||||
|
||||
@ -2839,6 +2839,7 @@ namespace ts {
|
||||
export interface ResolvedProjectReference {
|
||||
commandLine: ParsedCommandLine;
|
||||
sourceFile: SourceFile;
|
||||
references?: ReadonlyArray<ResolvedProjectReference | undefined>;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
||||
@ -281,8 +281,8 @@ namespace ts.server {
|
||||
return this.projectStateVersion.toString();
|
||||
}
|
||||
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference> {
|
||||
return emptyArray;
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference> | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getScriptFileNames() {
|
||||
@ -1391,8 +1391,8 @@ namespace ts.server {
|
||||
return asNormalizedPath(this.getProjectName());
|
||||
}
|
||||
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference> {
|
||||
return this.projectReferences || emptyArray;
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference> | undefined {
|
||||
return this.projectReferences;
|
||||
}
|
||||
|
||||
updateReferences(refs: ReadonlyArray<ProjectReference> | undefined) {
|
||||
|
||||
@ -321,16 +321,6 @@ export class cNew {}`);
|
||||
"/src/tests/index.ts"
|
||||
]);
|
||||
|
||||
function getLibs() {
|
||||
return [
|
||||
"/lib/lib.d.ts",
|
||||
"/lib/lib.es5.d.ts",
|
||||
"/lib/lib.dom.d.ts",
|
||||
"/lib/lib.webworker.importscripts.d.ts",
|
||||
"/lib/lib.scripthost.d.ts"
|
||||
];
|
||||
}
|
||||
|
||||
function getCoreOutputs() {
|
||||
return [
|
||||
"/src/core/index.d.ts",
|
||||
@ -376,6 +366,36 @@ export class cNew {}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("tsbuild - when project reference is referenced transitively", () => {
|
||||
const projFs = loadProjectFromDisk("tests/projects/transitiveReferences");
|
||||
const allExpectedOutputs = [
|
||||
"/src/a.js", "/src/a.d.ts",
|
||||
"/src/b.js", "/src/b.d.ts",
|
||||
"/src/c.js"
|
||||
];
|
||||
it("verify that it builds correctly", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tsconfig.c.json"], { listFiles: true });
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*empty*/);
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
assert.deepEqual(host.traces, [
|
||||
...getLibs(),
|
||||
"/src/a.ts",
|
||||
...getLibs(),
|
||||
"/src/a.d.ts",
|
||||
"/src/b.ts",
|
||||
...getLibs(),
|
||||
"/src/a.d.ts",
|
||||
"/src/b.d.ts",
|
||||
"/src/c.ts"
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export namespace OutFile {
|
||||
@ -584,4 +604,14 @@ export class cNew {}`);
|
||||
fs.makeReadonly();
|
||||
return fs;
|
||||
}
|
||||
|
||||
function getLibs() {
|
||||
return [
|
||||
"/lib/lib.d.ts",
|
||||
"/lib/lib.es5.d.ts",
|
||||
"/lib/lib.dom.d.ts",
|
||||
"/lib/lib.webworker.importscripts.d.ts",
|
||||
"/lib/lib.scripthost.d.ts"
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1812,11 +1812,12 @@ declare namespace ts {
|
||||
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
|
||||
isSourceFileDefaultLibrary(file: SourceFile): boolean;
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference> | undefined;
|
||||
getResolvedProjectReferences(): (ResolvedProjectReference | undefined)[] | undefined;
|
||||
getResolvedProjectReferences(): ReadonlyArray<ResolvedProjectReference | undefined> | undefined;
|
||||
}
|
||||
interface ResolvedProjectReference {
|
||||
commandLine: ParsedCommandLine;
|
||||
sourceFile: SourceFile;
|
||||
references?: ReadonlyArray<ResolvedProjectReference | undefined>;
|
||||
}
|
||||
interface CustomTransformers {
|
||||
/** Custom transformers to evaluate before built-in .js transformations. */
|
||||
@ -8114,7 +8115,7 @@ declare namespace ts.server {
|
||||
getCompilerOptions(): CompilerOptions;
|
||||
getNewLine(): string;
|
||||
getProjectVersion(): string;
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference>;
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference> | undefined;
|
||||
getScriptFileNames(): string[];
|
||||
private getOrCreateScriptInfoAndAttachToProject;
|
||||
getScriptKind(fileName: string): ScriptKind;
|
||||
@ -8231,7 +8232,7 @@ declare namespace ts.server {
|
||||
*/
|
||||
updateGraph(): boolean;
|
||||
getConfigFilePath(): NormalizedPath;
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference>;
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference> | undefined;
|
||||
updateReferences(refs: ReadonlyArray<ProjectReference> | undefined): void;
|
||||
enablePlugins(): void;
|
||||
/**
|
||||
|
||||
@ -1812,11 +1812,12 @@ declare namespace ts {
|
||||
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
|
||||
isSourceFileDefaultLibrary(file: SourceFile): boolean;
|
||||
getProjectReferences(): ReadonlyArray<ProjectReference> | undefined;
|
||||
getResolvedProjectReferences(): (ResolvedProjectReference | undefined)[] | undefined;
|
||||
getResolvedProjectReferences(): ReadonlyArray<ResolvedProjectReference | undefined> | undefined;
|
||||
}
|
||||
interface ResolvedProjectReference {
|
||||
commandLine: ParsedCommandLine;
|
||||
sourceFile: SourceFile;
|
||||
references?: ReadonlyArray<ResolvedProjectReference | undefined>;
|
||||
}
|
||||
interface CustomTransformers {
|
||||
/** Custom transformers to evaluate before built-in .js transformations. */
|
||||
|
||||
1
tests/projects/transitiveReferences/a.ts
Normal file
1
tests/projects/transitiveReferences/a.ts
Normal file
@ -0,0 +1 @@
|
||||
export class A {}
|
||||
2
tests/projects/transitiveReferences/b.ts
Normal file
2
tests/projects/transitiveReferences/b.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import {A} from './a';
|
||||
export const b = new A();
|
||||
2
tests/projects/transitiveReferences/c.ts
Normal file
2
tests/projects/transitiveReferences/c.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import {b} from './b';
|
||||
console.log(b);
|
||||
1
tests/projects/transitiveReferences/tsconfig.a.json
Normal file
1
tests/projects/transitiveReferences/tsconfig.a.json
Normal file
@ -0,0 +1 @@
|
||||
{"compilerOptions": {"composite": true}, "files": ["a.ts"]}
|
||||
1
tests/projects/transitiveReferences/tsconfig.b.json
Normal file
1
tests/projects/transitiveReferences/tsconfig.b.json
Normal file
@ -0,0 +1 @@
|
||||
{"compilerOptions": {"composite": true}, "files": ["b.ts"], "references": [{"path": "tsconfig.a.json"}]}
|
||||
1
tests/projects/transitiveReferences/tsconfig.c.json
Normal file
1
tests/projects/transitiveReferences/tsconfig.c.json
Normal file
@ -0,0 +1 @@
|
||||
{"files": ["c.ts"], "references": [{"path": "tsconfig.b.json"}]}
|
||||
Loading…
x
Reference in New Issue
Block a user