Skip to content

Commit b1dedf2

Browse files
committed
WIP
1 parent b2720ad commit b1dedf2

2 files changed

Lines changed: 211 additions & 2 deletions

File tree

src/compiler/tsbuild.ts

Lines changed: 209 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ namespace ts {
1919
projectStatus: FileMap<UpToDateStatus>;
2020
}
2121

22+
type Mapper = ReturnType<typeof createDependencyMapper>;
23+
interface DependencyGraph {
24+
buildQueue: string[][];
25+
dependencyMap: Mapper;
26+
}
27+
2228
enum BuildResultFlags {
2329
None = 0,
2430

@@ -189,6 +195,47 @@ namespace ts {
189195
}
190196
}
191197

198+
export function createDependencyMapper() {
199+
const childToParents: { [key: string]: string[] } = {};
200+
const parentToChildren: { [key: string]: string[] } = {};
201+
const allKeys: string[] = [];
202+
203+
function addReference(childConfigFileName: string, parentConfigFileName: string): void {
204+
addEntry(childToParents, childConfigFileName, parentConfigFileName);
205+
addEntry(parentToChildren, parentConfigFileName, childConfigFileName);
206+
}
207+
208+
function getReferencesTo(parentConfigFileName: string): string[] {
209+
return parentToChildren[normalizePath(parentConfigFileName)] || [];
210+
}
211+
212+
function getReferencesOf(childConfigFileName: string): string[] {
213+
return childToParents[normalizePath(childConfigFileName)] || [];
214+
}
215+
216+
function getKeys(): ReadonlyArray<string> {
217+
return allKeys;
218+
}
219+
220+
function addEntry(mapToAddTo: typeof childToParents | typeof parentToChildren, key: string, element: string) {
221+
key = normalizePath(key);
222+
element = normalizePath(element);
223+
const arr = (mapToAddTo[key] = mapToAddTo[key] || []);
224+
if (arr.indexOf(element) < 0) {
225+
arr.push(element);
226+
}
227+
if (allKeys.indexOf(key) < 0) allKeys.push(key);
228+
if (allKeys.indexOf(element) < 0) allKeys.push(element);
229+
}
230+
231+
return {
232+
addReference,
233+
getReferencesTo,
234+
getReferencesOf,
235+
getKeys
236+
};
237+
}
238+
192239
function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine) {
193240
const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile.options, configFile.options.configFilePath), inputFileName, /*ignoreCase*/ true);
194241
const outputPath = resolvePath(configFile.options.declarationDir || configFile.options.outDir || getDirectoryPath(configFile.options.configFilePath), relativePath);
@@ -232,7 +279,7 @@ namespace ts {
232279
}
233280

234281
function rootDirOfOptions(opts: CompilerOptions, configFileName: string) {
235-
return opts.rootDir || path.dirname(configFileName);
282+
return opts.rootDir || getDirectoryPath(configFileName);
236283
}
237284

238285
function createConfigFileCache(host: CompilerHost) {
@@ -261,7 +308,12 @@ namespace ts {
261308
return date2 < date1 ? date2 : date1;
262309
}
263310

311+
function isDeclarationFile(fileName: string) {
312+
return fileExtensionIs(fileName, ".d.ts");
313+
}
314+
264315
function createSolutionBuilder(host: CompilerHost) {
316+
const diagReporter = createDiagnosticReporter(sys, /*pretty*/true);
265317
const configFileCache = createConfigFileCache(host);
266318

267319
function getUpToDateStatus(project: ParsedCommandLine, context: BuildContext): UpToDateStatus {
@@ -394,8 +446,163 @@ namespace ts {
394446
};
395447
}
396448

449+
function createDependencyGraph(roots: string[]): DependencyGraph {
450+
// This is a list of list of projects that need to be built.
451+
// The ordering here is "backwards", i.e. the first entry in the array is the last set of projects that need to be built;
452+
// and the last entry is the first set of projects to be built.
453+
// Each subarray is effectively unordered.
454+
// We traverse the reference graph from each root, then "clean" the list by removing
455+
// any entry that is duplicated to its right.
456+
const buildQueue: string[][] = [];
457+
const dependencyMap = createDependencyMapper();
458+
let buildQueuePosition = 0;
459+
for (const root of roots) {
460+
const config = configFileCache.parseConfigFile(root);
461+
if (config === undefined) {
462+
throw new Error(`Could not parse ${root}`);
463+
}
464+
enumerateReferences(normalizePath(root), config);
465+
}
466+
removeDuplicatesFromBuildQueue(buildQueue);
467+
468+
return {
469+
buildQueue,
470+
dependencyMap
471+
};
472+
473+
function enumerateReferences(fileName: string, root: ts.ParsedCommandLine): void {
474+
const myBuildLevel = buildQueue[buildQueuePosition] = buildQueue[buildQueuePosition] || [];
475+
if (myBuildLevel.indexOf(fileName) < 0) {
476+
myBuildLevel.push(fileName);
477+
}
478+
479+
const refs = root.projectReferences;
480+
if (refs === undefined) return;
481+
buildQueuePosition++;
482+
for (const ref of refs) {
483+
dependencyMap.addReference(fileName, ref.path);
484+
const resolvedRef = configFileCache.parseConfigFile(ref.path);
485+
if (resolvedRef === undefined) continue;
486+
enumerateReferences(normalizePath(ref.path), resolvedRef);
487+
}
488+
buildQueuePosition--;
489+
}
490+
491+
/**
492+
* Removes entries from arrays which appear in later arrays.
493+
* TODO: Use a lookup object to optimize this a bit?
494+
*/
495+
function removeDuplicatesFromBuildQueue(queue: string[][]): void {
496+
// No need to check the last array
497+
for (let i = 0; i < queue.length - 1; i++) {
498+
queue[i] = queue[i].filter(fn => !occursAfter(fn, i + 1));
499+
}
500+
501+
function occursAfter(s: string, start: number) {
502+
for (let i = start; i < queue.length; i++) {
503+
if (queue[i].indexOf(s) >= 0) return true;
504+
}
505+
return false;
506+
}
507+
}
508+
}
509+
510+
function buildSingleProject(proj: string, context: BuildContext) {
511+
let resultFlags = BuildResultFlags.None;
512+
resultFlags |= BuildResultFlags.DeclarationOutputUnchanged;
513+
514+
const configFile = configFileCache.parseConfigFile(proj);
515+
if (!configFile) {
516+
// Failed to read the config file
517+
resultFlags |= BuildResultFlags.ConfigFileErrors;
518+
return resultFlags;
519+
}
520+
521+
if (configFile.fileNames.length === 0) {
522+
// Nothing to build - must be a solution file, basically
523+
return BuildResultFlags.None;
524+
}
525+
526+
const programOptions: CreateProgramOptions = {
527+
projectReferences: configFile.projectReferences,
528+
host: host,
529+
rootNames: configFile.fileNames,
530+
options: configFile.options
531+
};
532+
const program = ts.createProgram(programOptions);
533+
534+
// Don't emit anything in the presence of syntactic errors or options diagnostics
535+
const syntaxDiagnostics = [...program.getOptionsDiagnostics(), ...program.getSyntacticDiagnostics()];
536+
if (syntaxDiagnostics.length) {
537+
resultFlags |= BuildResultFlags.SyntaxErrors;
538+
for (const diag of syntaxDiagnostics) {
539+
diagReporter(diag);
540+
}
541+
return resultFlags;
542+
}
543+
544+
// Don't emit .d.ts if there are decl file errors
545+
if (program.getCompilerOptions().declaration) {
546+
const declDiagnostics = program.getDeclarationDiagnostics();
547+
if (declDiagnostics.length) {
548+
resultFlags |= BuildResultFlags.DeclarationEmitErrors;
549+
for (const diag of declDiagnostics) {
550+
diagReporter(diag);
551+
}
552+
}
553+
return resultFlags;
554+
}
555+
556+
const semanticDiagnostics = [...program.getSemanticDiagnostics()];
557+
if (semanticDiagnostics.length) {
558+
resultFlags |= BuildResultFlags.TypeErrors;
559+
for (const diag of semanticDiagnostics) {
560+
diagReporter(diag);
561+
}
562+
return resultFlags;
563+
}
564+
565+
program.emit(undefined, (fileName, content, writeBom, onError) => {
566+
let priorChangeTime: Date | undefined;
567+
568+
if (isDeclarationFile(fileName) && host.fileExists(fileName)) {
569+
if (host.readFile(fileName) === content) {
570+
resultFlags &= ~BuildResultFlags.DeclarationOutputUnchanged;
571+
priorChangeTime = host.getLastWriteTime && host.getLastWriteTime(fileName);
572+
}
573+
}
574+
575+
host.writeFile(fileName, content, writeBom, onError, emptyArray);
576+
if (priorChangeTime !== undefined) {
577+
context.unchangedOutputs.setValue(fileName, priorChangeTime);
578+
}
579+
});
580+
581+
return resultFlags;
582+
}
583+
584+
function buildProjects(configFileNames: string[], context: BuildContext) {
585+
// Establish what needs to be built
586+
const graph = createDependencyGraph(configFileNames);
587+
588+
const queue = graph.buildQueue;
589+
while (queue.length > 0) {
590+
const next = queue[0].pop()!;
591+
592+
const result = buildSingleProject(next, context);
593+
if (result & BuildResultFlags.AnyErrors) {
594+
break;
595+
}
596+
597+
if (queue[0].length === 0) {
598+
queue.pop();
599+
}
600+
}
601+
}
602+
397603
return {
398-
getUpToDateStatus
604+
getUpToDateStatus,
605+
buildProjects
399606
};
400607
}
401608
}

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4768,6 +4768,8 @@ namespace ts {
47684768
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
47694769
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
47704770
createHash?(data: string): string;
4771+
4772+
getLastWriteTime?(fileName: string): Date;
47714773
}
47724774

47734775
/* @internal */

0 commit comments

Comments
 (0)