mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge pull request #29813 from Microsoft/incrementalBuildInfo
Build .tsbuildinfo file when building using tsc --b
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
"declaration": false,
|
||||
"declarationMap": false,
|
||||
"removeComments": true,
|
||||
"incremental": false,
|
||||
"module": "commonjs",
|
||||
"types": [
|
||||
"node"
|
||||
|
||||
+298
-11
@@ -1,5 +1,80 @@
|
||||
/*@internal*/
|
||||
namespace ts {
|
||||
export interface ReusableDiagnostic extends ReusableDiagnosticRelatedInformation {
|
||||
/** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
|
||||
reportsUnnecessary?: {};
|
||||
source?: string;
|
||||
relatedInformation?: ReusableDiagnosticRelatedInformation[];
|
||||
}
|
||||
|
||||
export interface ReusableDiagnosticRelatedInformation {
|
||||
category: DiagnosticCategory;
|
||||
code: number;
|
||||
file: Path | undefined;
|
||||
start: number | undefined;
|
||||
length: number | undefined;
|
||||
messageText: string | ReusableDiagnosticMessageChain;
|
||||
}
|
||||
|
||||
export interface ReusableDiagnosticMessageChain {
|
||||
messageText: string;
|
||||
category: DiagnosticCategory;
|
||||
code: number;
|
||||
next?: ReusableDiagnosticMessageChain;
|
||||
}
|
||||
|
||||
export interface ReusableBuilderProgramState extends ReusableBuilderState {
|
||||
/**
|
||||
* Cache of semantic diagnostics for files with their Path being the key
|
||||
*/
|
||||
semanticDiagnosticsPerFile?: ReadonlyMap<ReadonlyArray<ReusableDiagnostic> | ReadonlyArray<Diagnostic>> | undefined;
|
||||
/**
|
||||
* The map has key by source file's path that has been changed
|
||||
*/
|
||||
changedFilesSet?: ReadonlyMap<true>;
|
||||
/**
|
||||
* Set of affected files being iterated
|
||||
*/
|
||||
affectedFiles?: ReadonlyArray<SourceFile> | undefined;
|
||||
/**
|
||||
* Current changed file for iterating over affected files
|
||||
*/
|
||||
currentChangedFilePath?: Path | undefined;
|
||||
/**
|
||||
* Map of file signatures, with key being file path, calculated while getting current changed file's affected files
|
||||
* These will be committed whenever the iteration through affected files of current changed file is complete
|
||||
*/
|
||||
currentAffectedFilesSignatures?: ReadonlyMap<string> | undefined;
|
||||
/**
|
||||
* Newly computed visible to outside referencedSet
|
||||
*/
|
||||
currentAffectedFilesExportedModulesMap?: Readonly<BuilderState.ComputingExportedModulesMap> | undefined;
|
||||
/**
|
||||
* True if the semantic diagnostics were copied from the old state
|
||||
*/
|
||||
semanticDiagnosticsFromOldState?: Map<true>;
|
||||
/**
|
||||
* program corresponding to this state
|
||||
*/
|
||||
program?: Program | undefined;
|
||||
/**
|
||||
* compilerOptions for the program
|
||||
*/
|
||||
compilerOptions: CompilerOptions;
|
||||
/**
|
||||
* Files pending to be emitted
|
||||
*/
|
||||
affectedFilesPendingEmit?: ReadonlyArray<Path> | undefined;
|
||||
/**
|
||||
* Current index to retrieve pending affected file
|
||||
*/
|
||||
affectedFilesPendingEmitIndex?: number | undefined;
|
||||
/*
|
||||
* true if semantic diagnostics are ReusableDiagnostic instead of Diagnostic
|
||||
*/
|
||||
hasReusableDiagnostic?: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* State to store the changed files, affected files and cache semantic diagnostics
|
||||
*/
|
||||
@@ -62,6 +137,10 @@ namespace ts {
|
||||
* Current index to retrieve pending affected file
|
||||
*/
|
||||
affectedFilesPendingEmitIndex: number | undefined;
|
||||
/**
|
||||
* true if build info is emitted
|
||||
*/
|
||||
emittedBuildInfo?: boolean;
|
||||
/**
|
||||
* Already seen affected files
|
||||
*/
|
||||
@@ -80,7 +159,7 @@ namespace ts {
|
||||
/**
|
||||
* Create the state so that we can iterate on changedFiles/affected files
|
||||
*/
|
||||
function createBuilderProgramState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly<BuilderProgramState>): BuilderProgramState {
|
||||
function createBuilderProgramState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly<ReusableBuilderProgramState>): BuilderProgramState {
|
||||
const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderProgramState;
|
||||
state.program = newProgram;
|
||||
const compilerOptions = newProgram.getCompilerOptions();
|
||||
@@ -102,12 +181,15 @@ namespace ts {
|
||||
const affectedSignatures = oldState!.currentAffectedFilesSignatures;
|
||||
Debug.assert(!oldState!.affectedFiles && (!affectedSignatures || !affectedSignatures.size), "Cannot reuse if only few affected files of currentChangedFile were iterated");
|
||||
}
|
||||
const changedFilesSet = oldState!.changedFilesSet;
|
||||
if (canCopySemanticDiagnostics) {
|
||||
Debug.assert(!forEachKey(oldState!.changedFilesSet, path => oldState!.semanticDiagnosticsPerFile!.has(path)), "Semantic diagnostics shouldnt be available for changed files");
|
||||
Debug.assert(!changedFilesSet || !forEachKey(changedFilesSet, path => oldState!.semanticDiagnosticsPerFile!.has(path)), "Semantic diagnostics shouldnt be available for changed files");
|
||||
}
|
||||
|
||||
// Copy old state's changed files set
|
||||
copyEntries(oldState!.changedFilesSet, state.changedFilesSet);
|
||||
if (changedFilesSet) {
|
||||
copyEntries(changedFilesSet, state.changedFilesSet);
|
||||
}
|
||||
if (!compilerOptions.outFile && !compilerOptions.out && oldState!.affectedFilesPendingEmit) {
|
||||
state.affectedFilesPendingEmit = oldState!.affectedFilesPendingEmit;
|
||||
state.affectedFilesPendingEmitIndex = oldState!.affectedFilesPendingEmitIndex;
|
||||
@@ -145,7 +227,7 @@ namespace ts {
|
||||
// Unchanged file copy diagnostics
|
||||
const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath);
|
||||
if (diagnostics) {
|
||||
state.semanticDiagnosticsPerFile!.set(sourceFilePath, diagnostics);
|
||||
state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as ReadonlyArray<ReusableDiagnostic>, newProgram) : diagnostics as ReadonlyArray<Diagnostic>);
|
||||
if (!state.semanticDiagnosticsFromOldState) {
|
||||
state.semanticDiagnosticsFromOldState = createMap<true>();
|
||||
}
|
||||
@@ -154,9 +236,56 @@ namespace ts {
|
||||
}
|
||||
});
|
||||
|
||||
if (oldCompilerOptions &&
|
||||
(oldCompilerOptions.outDir !== compilerOptions.outDir ||
|
||||
oldCompilerOptions.declarationDir !== compilerOptions.declarationDir ||
|
||||
(oldCompilerOptions.outFile || oldCompilerOptions.out) !== (compilerOptions.outFile || compilerOptions.out))) {
|
||||
// Add all files to affectedFilesPendingEmit since emit changed
|
||||
state.affectedFilesPendingEmit = concatenate(state.affectedFilesPendingEmit, newProgram.getSourceFiles().map(f => f.path));
|
||||
if (state.affectedFilesPendingEmitIndex === undefined) {
|
||||
state.affectedFilesPendingEmitIndex = 0;
|
||||
}
|
||||
Debug.assert(state.seenAffectedFiles === undefined);
|
||||
state.seenAffectedFiles = createMap<true>();
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function convertToDiagnostics(diagnostics: ReadonlyArray<ReusableDiagnostic>, newProgram: Program): ReadonlyArray<Diagnostic> {
|
||||
if (!diagnostics.length) return emptyArray;
|
||||
return diagnostics.map(diagnostic => {
|
||||
const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram);
|
||||
result.reportsUnnecessary = diagnostic.reportsUnnecessary;
|
||||
result.source = diagnostic.source;
|
||||
const { relatedInformation } = diagnostic;
|
||||
result.relatedInformation = relatedInformation ?
|
||||
relatedInformation.length ?
|
||||
relatedInformation.map(r => convertToDiagnosticRelatedInformation(r, newProgram)) :
|
||||
emptyArray :
|
||||
undefined;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program): DiagnosticRelatedInformation {
|
||||
const { file, messageText } = diagnostic;
|
||||
return {
|
||||
...diagnostic,
|
||||
file: file && newProgram.getSourceFileByPath(file),
|
||||
messageText: messageText === undefined || isString(messageText) ?
|
||||
messageText :
|
||||
convertToDiagnosticMessageChain(messageText, newProgram)
|
||||
};
|
||||
}
|
||||
|
||||
function convertToDiagnosticMessageChain(diagnostic: ReusableDiagnosticMessageChain, newProgram: Program): DiagnosticMessageChain {
|
||||
return {
|
||||
...diagnostic,
|
||||
next: diagnostic.next && convertToDiagnosticMessageChain(diagnostic.next, newProgram)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases program and other related not needed properties
|
||||
*/
|
||||
@@ -397,8 +526,11 @@ namespace ts {
|
||||
* This is called after completing operation on the next affected file.
|
||||
* The operations here are postponed to ensure that cancellation during the iteration is handled correctly
|
||||
*/
|
||||
function doneWithAffectedFile(state: BuilderProgramState, affected: SourceFile | Program, isPendingEmit?: boolean) {
|
||||
if (affected === state.program) {
|
||||
function doneWithAffectedFile(state: BuilderProgramState, affected: SourceFile | Program, isPendingEmit?: boolean, isBuildInfoEmit?: boolean) {
|
||||
if (isBuildInfoEmit) {
|
||||
state.emittedBuildInfo = true;
|
||||
}
|
||||
else if (affected === state.program) {
|
||||
state.changedFilesSet.clear();
|
||||
state.programEmitComplete = true;
|
||||
}
|
||||
@@ -416,8 +548,8 @@ namespace ts {
|
||||
/**
|
||||
* Returns the result with affected file
|
||||
*/
|
||||
function toAffectedFileResult<T>(state: BuilderProgramState, result: T, affected: SourceFile | Program, isPendingEmit?: boolean): AffectedFileResult<T> {
|
||||
doneWithAffectedFile(state, affected, isPendingEmit);
|
||||
function toAffectedFileResult<T>(state: BuilderProgramState, result: T, affected: SourceFile | Program, isPendingEmit?: boolean, isBuildInfoEmit?: boolean): AffectedFileResult<T> {
|
||||
doneWithAffectedFile(state, affected, isPendingEmit, isBuildInfoEmit);
|
||||
return { result, affected };
|
||||
}
|
||||
|
||||
@@ -443,6 +575,100 @@ namespace ts {
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
export type ProgramBuildInfoDiagnostic = string | [string, ReadonlyArray<ReusableDiagnostic>];
|
||||
export interface ProgramBuildInfo {
|
||||
fileInfos: MapLike<BuilderState.FileInfo>;
|
||||
options: CompilerOptions;
|
||||
referencedMap?: MapLike<string[]>;
|
||||
exportedModulesMap?: MapLike<string[]>;
|
||||
semanticDiagnosticsPerFile?: ProgramBuildInfoDiagnostic[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the program information to be emitted in buildInfo so that we can use it to create new program
|
||||
*/
|
||||
function getProgramBuildInfo(state: Readonly<ReusableBuilderProgramState>): ProgramBuildInfo | undefined {
|
||||
if (state.compilerOptions.outFile || state.compilerOptions.out) return undefined;
|
||||
const fileInfos: MapLike<BuilderState.FileInfo> = {};
|
||||
state.fileInfos.forEach((value, key) => {
|
||||
const signature = state.currentAffectedFilesSignatures && state.currentAffectedFilesSignatures.get(key);
|
||||
fileInfos[key] = signature === undefined ? value : { version: value.version, signature };
|
||||
});
|
||||
|
||||
const result: ProgramBuildInfo = { fileInfos, options: state.compilerOptions };
|
||||
if (state.referencedMap) {
|
||||
const referencedMap: MapLike<string[]> = {};
|
||||
state.referencedMap.forEach((value, key) => {
|
||||
referencedMap[key] = arrayFrom(value.keys());
|
||||
});
|
||||
result.referencedMap = referencedMap;
|
||||
}
|
||||
|
||||
if (state.exportedModulesMap) {
|
||||
const exportedModulesMap: MapLike<string[]> = {};
|
||||
state.exportedModulesMap.forEach((value, key) => {
|
||||
const newValue = state.currentAffectedFilesExportedModulesMap && state.currentAffectedFilesExportedModulesMap.get(key);
|
||||
// Not in temporary cache, use existing value
|
||||
if (newValue === undefined) exportedModulesMap[key] = arrayFrom(value.keys());
|
||||
// Value in cache and has updated value map, use that
|
||||
else if (newValue) exportedModulesMap[key] = arrayFrom(newValue.keys());
|
||||
});
|
||||
result.exportedModulesMap = exportedModulesMap;
|
||||
}
|
||||
|
||||
if (state.semanticDiagnosticsPerFile) {
|
||||
const semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] = [];
|
||||
// Currently not recording actual errors since those mean no emit for tsc --build
|
||||
state.semanticDiagnosticsPerFile.forEach((value, key) => semanticDiagnosticsPerFile.push(
|
||||
value.length ?
|
||||
[
|
||||
key,
|
||||
state.hasReusableDiagnostic ?
|
||||
value as ReadonlyArray<ReusableDiagnostic> :
|
||||
convertToReusableDiagnostics(value as ReadonlyArray<Diagnostic>)
|
||||
] :
|
||||
key
|
||||
));
|
||||
result.semanticDiagnosticsPerFile = semanticDiagnosticsPerFile;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function convertToReusableDiagnostics(diagnostics: ReadonlyArray<Diagnostic>): ReadonlyArray<ReusableDiagnostic> {
|
||||
Debug.assert(!!diagnostics.length);
|
||||
return diagnostics.map(diagnostic => {
|
||||
const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic);
|
||||
result.reportsUnnecessary = diagnostic.reportsUnnecessary;
|
||||
result.source = diagnostic.source;
|
||||
const { relatedInformation } = diagnostic;
|
||||
result.relatedInformation = relatedInformation ?
|
||||
relatedInformation.length ?
|
||||
relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r)) :
|
||||
emptyArray :
|
||||
undefined;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRelatedInformation): ReusableDiagnosticRelatedInformation {
|
||||
const { file, messageText } = diagnostic;
|
||||
return {
|
||||
...diagnostic,
|
||||
file: file && file.path,
|
||||
messageText: messageText === undefined || isString(messageText) ?
|
||||
messageText :
|
||||
convertToReusableDiagnosticMessageChain(messageText)
|
||||
};
|
||||
}
|
||||
|
||||
function convertToReusableDiagnosticMessageChain(diagnostic: DiagnosticMessageChain): ReusableDiagnosticMessageChain {
|
||||
return {
|
||||
...diagnostic,
|
||||
next: diagnostic.next && convertToReusableDiagnosticMessageChain(diagnostic.next)
|
||||
};
|
||||
}
|
||||
|
||||
export enum BuilderProgramKind {
|
||||
SemanticDiagnosticsBuilderProgram,
|
||||
EmitAndSemanticDiagnosticsBuilderProgram
|
||||
@@ -508,6 +734,7 @@ namespace ts {
|
||||
const computeHash = host.createHash || generateDjb2Hash;
|
||||
let state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState);
|
||||
let backupState: BuilderProgramState | undefined;
|
||||
newProgram.getProgramBuildInfo = () => getProgramBuildInfo(state);
|
||||
|
||||
// To ensure that we arent storing any references to old program or new program without state
|
||||
newProgram = undefined!; // TODO: GH#18217
|
||||
@@ -556,7 +783,20 @@ namespace ts {
|
||||
if (!state.compilerOptions.out && !state.compilerOptions.outFile) {
|
||||
affected = getNextAffectedFilePendingEmit(state);
|
||||
if (!affected) {
|
||||
return undefined;
|
||||
if (state.emittedBuildInfo) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const affected = Debug.assertDefined(state.program);
|
||||
return toAffectedFileResult(
|
||||
state,
|
||||
// When whole program is affected, do emit only once (eg when --out or --outFile is specified)
|
||||
// Otherwise just affected file
|
||||
affected.emitBuildInfo(writeFile || host.writeFile, cancellationToken),
|
||||
affected,
|
||||
/*isPendingEmitFile*/ false,
|
||||
/*isBuildInfoEmit*/ true
|
||||
);
|
||||
}
|
||||
isPendingEmitFile = true;
|
||||
}
|
||||
@@ -712,6 +952,53 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function getMapOfReferencedSet(mapLike: MapLike<ReadonlyArray<string>> | undefined): ReadonlyMap<BuilderState.ReferencedSet> | undefined {
|
||||
if (!mapLike) return undefined;
|
||||
const map = createMap<BuilderState.ReferencedSet>();
|
||||
// Copies keys/values from template. Note that for..in will not throw if
|
||||
// template is undefined, and instead will just exit the loop.
|
||||
for (const key in mapLike) {
|
||||
if (hasProperty(mapLike, key)) {
|
||||
map.set(key, arrayToSet(mapLike[key]));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo): EmitAndSemanticDiagnosticsBuilderProgram & SemanticDiagnosticsBuilderProgram {
|
||||
const fileInfos = createMapFromTemplate(program.fileInfos);
|
||||
const state: ReusableBuilderProgramState = {
|
||||
fileInfos,
|
||||
compilerOptions: program.options,
|
||||
referencedMap: getMapOfReferencedSet(program.referencedMap),
|
||||
exportedModulesMap: getMapOfReferencedSet(program.exportedModulesMap),
|
||||
semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => isString(value) ? value : value[0], value => isString(value) ? emptyArray : value[1]),
|
||||
hasReusableDiagnostic: true
|
||||
};
|
||||
return {
|
||||
getState: () => state,
|
||||
backupState: noop,
|
||||
restoreState: noop,
|
||||
getProgram: notImplemented,
|
||||
getProgramOrUndefined: () => undefined,
|
||||
releaseProgram: noop,
|
||||
getCompilerOptions: () => state.compilerOptions,
|
||||
getSourceFile: notImplemented,
|
||||
getSourceFiles: notImplemented,
|
||||
getOptionsDiagnostics: notImplemented,
|
||||
getGlobalDiagnostics: notImplemented,
|
||||
getConfigFileParsingDiagnostics: notImplemented,
|
||||
getSyntacticDiagnostics: notImplemented,
|
||||
getDeclarationDiagnostics: notImplemented,
|
||||
getSemanticDiagnostics: notImplemented,
|
||||
emit: notImplemented,
|
||||
getAllDependencies: notImplemented,
|
||||
getCurrentDirectory: notImplemented,
|
||||
emitNextAffectedFile: notImplemented,
|
||||
getSemanticDiagnosticsOfNextAffectedFile: notImplemented,
|
||||
};
|
||||
}
|
||||
|
||||
export function createRedirectedBuilderProgram(state: { program: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: ReadonlyArray<Diagnostic>): BuilderProgram {
|
||||
return {
|
||||
getState: notImplemented,
|
||||
@@ -731,7 +1018,7 @@ namespace ts {
|
||||
getSemanticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSemanticDiagnostics(sourceFile, cancellationToken),
|
||||
emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => getProgram().emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers),
|
||||
getAllDependencies: notImplemented,
|
||||
getCurrentDirectory: () => getProgram().getCurrentDirectory()
|
||||
getCurrentDirectory: () => getProgram().getCurrentDirectory(),
|
||||
};
|
||||
|
||||
function getProgram() {
|
||||
@@ -764,7 +1051,7 @@ namespace ts {
|
||||
*/
|
||||
export interface BuilderProgram {
|
||||
/*@internal*/
|
||||
getState(): BuilderProgramState;
|
||||
getState(): ReusableBuilderProgramState;
|
||||
/*@internal*/
|
||||
backupState(): void;
|
||||
/*@internal*/
|
||||
|
||||
@@ -25,6 +25,24 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
export interface ReusableBuilderState {
|
||||
/**
|
||||
* Information of the file eg. its version, signature etc
|
||||
*/
|
||||
fileInfos: ReadonlyMap<BuilderState.FileInfo>;
|
||||
/**
|
||||
* Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled
|
||||
* Otherwise undefined
|
||||
* Thus non undefined value indicates, module emit
|
||||
*/
|
||||
readonly referencedMap?: ReadonlyMap<BuilderState.ReferencedSet> | undefined;
|
||||
/**
|
||||
* Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled
|
||||
* Otherwise undefined
|
||||
*/
|
||||
readonly exportedModulesMap?: ReadonlyMap<BuilderState.ReferencedSet> | undefined;
|
||||
}
|
||||
|
||||
export interface BuilderState {
|
||||
/**
|
||||
* Information of the file eg. its version, signature etc
|
||||
@@ -196,14 +214,14 @@ namespace ts.BuilderState {
|
||||
/**
|
||||
* Returns true if oldState is reusable, that is the emitKind = module/non module has not changed
|
||||
*/
|
||||
export function canReuseOldState(newReferencedMap: ReadonlyMap<ReferencedSet> | undefined, oldState: Readonly<BuilderState> | undefined) {
|
||||
export function canReuseOldState(newReferencedMap: ReadonlyMap<ReferencedSet> | undefined, oldState: Readonly<ReusableBuilderState> | undefined) {
|
||||
return oldState && !oldState.referencedMap === !newReferencedMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the state of file references and signature for the new program from oldState if it is safe
|
||||
*/
|
||||
export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly<BuilderState>): BuilderState {
|
||||
export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly<ReusableBuilderState>): BuilderState {
|
||||
const fileInfos = createMap<FileInfo>();
|
||||
const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createMap<ReferencedSet>() : undefined;
|
||||
const exportedModulesMap = referencedMap ? createMap<ReferencedSet>() : undefined;
|
||||
|
||||
@@ -331,6 +331,22 @@ namespace ts {
|
||||
category: Diagnostics.Basic_Options,
|
||||
description: Diagnostics.Enable_project_compilation,
|
||||
},
|
||||
{
|
||||
name: "incremental",
|
||||
type: "boolean",
|
||||
isTSConfigOnly: true,
|
||||
category: Diagnostics.Basic_Options,
|
||||
description: Diagnostics.Enable_incremental_compilation,
|
||||
},
|
||||
{
|
||||
name: "tsBuildInfoFile",
|
||||
type: "string",
|
||||
isFilePath: true,
|
||||
paramType: Diagnostics.FILE,
|
||||
isTSConfigOnly: true,
|
||||
category: Diagnostics.Basic_Options,
|
||||
description: Diagnostics.Specify_file_to_store_incremental_compilation_information,
|
||||
},
|
||||
{
|
||||
name: "removeComments",
|
||||
type: "boolean",
|
||||
@@ -1427,6 +1443,7 @@ namespace ts {
|
||||
return _tsconfigRootOptions;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
interface JsonConversionNotifier {
|
||||
/**
|
||||
* Notifies parent option object is being set with the optionKey and a valid optionValue
|
||||
|
||||
@@ -3936,7 +3936,6 @@
|
||||
"category": "Message",
|
||||
"code": 6353
|
||||
},
|
||||
|
||||
"Project '{0}' is up to date with .d.ts files from its dependencies": {
|
||||
"category": "Message",
|
||||
"code": 6354
|
||||
@@ -4009,6 +4008,46 @@
|
||||
"category": "Message",
|
||||
"code": 6371
|
||||
},
|
||||
"Project '{0}' is out of date because output of its dependency '{1}' has changed": {
|
||||
"category": "Message",
|
||||
"code": 6372
|
||||
},
|
||||
"Updating output of project '{0}'...": {
|
||||
"category": "Message",
|
||||
"code": 6373
|
||||
},
|
||||
"A non-dry build would update timestamps for output of project '{0}'": {
|
||||
"category": "Message",
|
||||
"code": 6374
|
||||
},
|
||||
"A non-dry build would update output of project '{0}'": {
|
||||
"category": "Message",
|
||||
"code": 6375
|
||||
},
|
||||
"Cannot update output of project '{0}' because there was error reading file '{1}'": {
|
||||
"category": "Message",
|
||||
"code": 6376
|
||||
},
|
||||
"Cannot write file '{0}' because it will overwrite '.tsbuildinfo' file generated by referenced project '{1}'": {
|
||||
"category": "Error",
|
||||
"code": 6377
|
||||
},
|
||||
"Enable incremental compilation": {
|
||||
"category": "Message",
|
||||
"code": 6378
|
||||
},
|
||||
"Composite projects may not disable incremental compilation.": {
|
||||
"category": "Error",
|
||||
"code": 6379
|
||||
},
|
||||
"Specify file to store incremental compilation information": {
|
||||
"category": "Message",
|
||||
"code": 6380
|
||||
},
|
||||
"Project '{0}' is out of date because output for it was generated with version '{1}' that differs with current version '{2}'": {
|
||||
"category": "Message",
|
||||
"code": 6381
|
||||
},
|
||||
|
||||
"The expected type comes from property '{0}' which is declared here on type '{1}'": {
|
||||
"category": "Message",
|
||||
|
||||
+555
-84
@@ -1,8 +1,12 @@
|
||||
namespace ts {
|
||||
const infoExtension = ".tsbundleinfo";
|
||||
const brackets = createBracketsMap();
|
||||
const syntheticParent: TextRange = { pos: -1, end: -1 };
|
||||
|
||||
/*@internal*/
|
||||
export function isBuildInfoFile(file: string) {
|
||||
return fileExtensionIs(file, Extension.TsBuildInfo);
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
/**
|
||||
* Iterates over the source files that are expected to have an emit output.
|
||||
@@ -14,14 +18,17 @@ namespace ts {
|
||||
* Else, calls `getSourceFilesToEmit` with the (optional) target source file to determine the list of source files to emit.
|
||||
*/
|
||||
export function forEachEmittedFile<T>(
|
||||
host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle) => T,
|
||||
host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) => T,
|
||||
sourceFilesOrTargetSourceFile?: ReadonlyArray<SourceFile> | SourceFile,
|
||||
emitOnlyDtsFiles = false) {
|
||||
emitOnlyDtsFiles = false,
|
||||
onlyBuildInfo?: boolean,
|
||||
includeBuildInfo?: boolean) {
|
||||
const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile);
|
||||
const options = host.getCompilerOptions();
|
||||
if (options.outFile || options.out) {
|
||||
if (sourceFiles.length) {
|
||||
const bundle = createBundle(sourceFiles, host.getPrependNodes());
|
||||
const prepends = host.getPrependNodes();
|
||||
if (sourceFiles.length || prepends.length) {
|
||||
const bundle = createBundle(sourceFiles, prepends);
|
||||
const result = action(getOutputPathsFor(bundle, host, emitOnlyDtsFiles), bundle);
|
||||
if (result) {
|
||||
return result;
|
||||
@@ -29,15 +36,42 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const sourceFile of sourceFiles) {
|
||||
const result = action(getOutputPathsFor(sourceFile, host, emitOnlyDtsFiles), sourceFile);
|
||||
if (result) {
|
||||
return result;
|
||||
if (!onlyBuildInfo) {
|
||||
for (const sourceFile of sourceFiles) {
|
||||
const result = action(getOutputPathsFor(sourceFile, host, emitOnlyDtsFiles), sourceFile);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (includeBuildInfo) {
|
||||
const buildInfoPath = getOutputPathForBuildInfo(host.getCompilerOptions());
|
||||
if (buildInfoPath) return action({ buildInfoPath }, /*sourceFileOrBundle*/ undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export function getOutputPathForBuildInfo(options: CompilerOptions) {
|
||||
const configFile = options.configFilePath;
|
||||
if (!configFile || !isIncrementalCompilation(options)) return undefined;
|
||||
if (options.tsBuildInfoFile) return options.tsBuildInfoFile;
|
||||
const outPath = options.outFile || options.out;
|
||||
let buildInfoExtensionLess: string;
|
||||
if (outPath) {
|
||||
buildInfoExtensionLess = removeFileExtension(outPath);
|
||||
}
|
||||
else {
|
||||
const configFileExtensionLess = removeFileExtension(configFile);
|
||||
buildInfoExtensionLess = options.outDir ?
|
||||
options.rootDir ?
|
||||
resolvePath(options.outDir, getRelativePathFromDirectory(options.rootDir, configFileExtensionLess, /*ignoreCase*/ true)) :
|
||||
combinePaths(options.outDir, getBaseFileName(configFileExtensionLess)) :
|
||||
configFileExtensionLess;
|
||||
}
|
||||
return buildInfoExtensionLess + Extension.TsBuildInfo;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export function getOutputPathsForBundle(options: CompilerOptions, forceDtsPaths: boolean): EmitFileNames {
|
||||
const outPath = options.outFile || options.out!;
|
||||
@@ -45,8 +79,8 @@ namespace ts {
|
||||
const sourceMapFilePath = jsFilePath && getSourceMapFilePath(jsFilePath, options);
|
||||
const declarationFilePath = (forceDtsPaths || getEmitDeclarations(options)) ? removeFileExtension(outPath) + Extension.Dts : undefined;
|
||||
const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined;
|
||||
const bundleInfoPath = options.references && jsFilePath ? (removeFileExtension(jsFilePath) + infoExtension) : undefined;
|
||||
return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, bundleInfoPath };
|
||||
const buildInfoPath = getOutputPathForBuildInfo(options);
|
||||
return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath };
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
@@ -66,7 +100,7 @@ namespace ts {
|
||||
const isJs = isSourceFileJS(sourceFile);
|
||||
const declarationFilePath = ((forceDtsPaths || getEmitDeclarations(options)) && !isJs) ? getDeclarationEmitOutputFilePath(sourceFile.fileName, host) : undefined;
|
||||
const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined;
|
||||
return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, bundleInfoPath: undefined };
|
||||
return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath: undefined };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,13 +108,6 @@ namespace ts {
|
||||
return (options.sourceMap && !options.inlineSourceMap) ? jsFilePath + ".map" : undefined;
|
||||
}
|
||||
|
||||
function createDefaultBundleInfo(): BundleInfo {
|
||||
return {
|
||||
originalOffset: -1,
|
||||
totalLength: -1
|
||||
};
|
||||
}
|
||||
|
||||
// JavaScript files are always LanguageVariant.JSX, as JSX syntax is allowed in .js files also.
|
||||
// So for JavaScript files, '.jsx' is only emitted if the input was '.jsx', and JsxEmit.Preserve.
|
||||
// For TypeScript, the only time to emit with a '.jsx' extension, is on JSX input, and JsxEmit.Preserve
|
||||
@@ -106,7 +133,7 @@ namespace ts {
|
||||
|
||||
/*@internal*/
|
||||
// targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature
|
||||
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile, emitOnlyDtsFiles?: boolean, transformers?: TransformerFactory<Bundle | SourceFile>[], declarationTransformers?: TransformerFactory<Bundle | SourceFile>[]): EmitResult {
|
||||
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, emitOnlyDtsFiles?: boolean, transformers?: TransformerFactory<Bundle | SourceFile>[], declarationTransformers?: TransformerFactory<Bundle | SourceFile>[], onlyBuildInfo?: boolean): EmitResult {
|
||||
const compilerOptions = host.getCompilerOptions();
|
||||
const sourceMapDataList: SourceMapEmitResult[] | undefined = (compilerOptions.sourceMap || compilerOptions.inlineSourceMap || getAreDeclarationMapsEnabled(compilerOptions)) ? [] : undefined;
|
||||
const emittedFilesList: string[] | undefined = compilerOptions.listEmittedFiles ? [] : undefined;
|
||||
@@ -114,13 +141,13 @@ namespace ts {
|
||||
const newLine = getNewLineCharacter(compilerOptions, () => host.getNewLine());
|
||||
const writer = createTextWriter(newLine);
|
||||
const { enter, exit } = performance.createTimer("printTime", "beforePrint", "afterPrint");
|
||||
let bundleInfo: BundleInfo = createDefaultBundleInfo();
|
||||
let bundleBuildInfo: BundleBuildInfo | undefined;
|
||||
let emitSkipped = false;
|
||||
let exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined;
|
||||
|
||||
// Emit each output file
|
||||
enter();
|
||||
forEachEmittedFile(host, emitSourceFileOrBundle, getSourceFilesToEmit(host, targetSourceFile), emitOnlyDtsFiles);
|
||||
forEachEmittedFile(host, emitSourceFileOrBundle, getSourceFilesToEmit(host, targetSourceFile), emitOnlyDtsFiles, onlyBuildInfo, !targetSourceFile);
|
||||
exit();
|
||||
|
||||
|
||||
@@ -132,9 +159,16 @@ namespace ts {
|
||||
exportedModulesFromDeclarationEmit
|
||||
};
|
||||
|
||||
function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, bundleInfoPath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle) {
|
||||
emitJsFileOrBundle(sourceFileOrBundle, jsFilePath, sourceMapFilePath, bundleInfoPath);
|
||||
function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) {
|
||||
if (buildInfoPath && sourceFileOrBundle && isBundle(sourceFileOrBundle)) {
|
||||
bundleBuildInfo = {
|
||||
commonSourceDirectory: host.getCommonSourceDirectory(),
|
||||
sourceFiles: sourceFileOrBundle.sourceFiles.map(file => file.fileName)
|
||||
};
|
||||
}
|
||||
emitJsFileOrBundle(sourceFileOrBundle, jsFilePath, sourceMapFilePath);
|
||||
emitDeclarationFileOrBundle(sourceFileOrBundle, declarationFilePath, declarationMapPath);
|
||||
emitBuildInfo(bundleBuildInfo, buildInfoPath);
|
||||
|
||||
if (!emitSkipped && emittedFilesList) {
|
||||
if (!emitOnlyDtsFiles) {
|
||||
@@ -144,8 +178,8 @@ namespace ts {
|
||||
if (sourceMapFilePath) {
|
||||
emittedFilesList.push(sourceMapFilePath);
|
||||
}
|
||||
if (bundleInfoPath) {
|
||||
emittedFilesList.push(bundleInfoPath);
|
||||
if (buildInfoPath) {
|
||||
emittedFilesList.push(buildInfoPath);
|
||||
}
|
||||
}
|
||||
if (declarationFilePath) {
|
||||
@@ -157,8 +191,20 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function emitJsFileOrBundle(sourceFileOrBundle: SourceFile | Bundle, jsFilePath: string | undefined, sourceMapFilePath: string | undefined, bundleInfoPath: string | undefined) {
|
||||
if (emitOnlyDtsFiles || !jsFilePath) {
|
||||
function emitBuildInfo(bundle: BundleBuildInfo | undefined, buildInfoPath: string | undefined) {
|
||||
// Write build information if applicable
|
||||
if (!buildInfoPath || targetSourceFile || emitSkipped) return;
|
||||
const program = host.getProgramBuildInfo();
|
||||
if (!bundle && !program) return;
|
||||
if (host.isEmitBlocked(buildInfoPath) || compilerOptions.noEmit) {
|
||||
emitSkipped = true;
|
||||
return;
|
||||
}
|
||||
writeFile(host, emitterDiagnostics, buildInfoPath, getBuildInfoText({ bundle, program, version }), /*writeByteOrderMark*/ false);
|
||||
}
|
||||
|
||||
function emitJsFileOrBundle(sourceFileOrBundle: SourceFile | Bundle | undefined, jsFilePath: string | undefined, sourceMapFilePath: string | undefined) {
|
||||
if (!sourceFileOrBundle || emitOnlyDtsFiles || !jsFilePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -180,6 +226,7 @@ namespace ts {
|
||||
inlineSourceMap: compilerOptions.inlineSourceMap,
|
||||
inlineSources: compilerOptions.inlineSources,
|
||||
extendedDiagnostics: compilerOptions.extendedDiagnostics,
|
||||
writeBundleFileInfo: !!bundleBuildInfo
|
||||
};
|
||||
|
||||
// Create a printer to print the nodes
|
||||
@@ -193,14 +240,15 @@ namespace ts {
|
||||
});
|
||||
|
||||
Debug.assert(transform.transformed.length === 1, "Should only see one output from the transform");
|
||||
printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform.transformed[0], bundleInfoPath, printer, compilerOptions);
|
||||
printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform.transformed[0], printer, compilerOptions);
|
||||
|
||||
// Clean up emit nodes on parse tree
|
||||
transform.dispose();
|
||||
if (bundleBuildInfo) bundleBuildInfo.js = printer.bundleFileInfo;
|
||||
}
|
||||
|
||||
function emitDeclarationFileOrBundle(sourceFileOrBundle: SourceFile | Bundle, declarationFilePath: string | undefined, declarationMapPath: string | undefined) {
|
||||
if (!(declarationFilePath && !isInJSFile(sourceFileOrBundle))) {
|
||||
function emitDeclarationFileOrBundle(sourceFileOrBundle: SourceFile | Bundle | undefined, declarationFilePath: string | undefined, declarationMapPath: string | undefined) {
|
||||
if (!sourceFileOrBundle || !(declarationFilePath && !isInJSFile(sourceFileOrBundle))) {
|
||||
return;
|
||||
}
|
||||
const sourceFiles = isSourceFile(sourceFileOrBundle) ? [sourceFileOrBundle] : sourceFileOrBundle.sourceFiles;
|
||||
@@ -229,6 +277,8 @@ namespace ts {
|
||||
inlineSourceMap: compilerOptions.inlineSourceMap,
|
||||
extendedDiagnostics: compilerOptions.extendedDiagnostics,
|
||||
onlyPrintJsDocStyle: true,
|
||||
writeBundleFileInfo: !!bundleBuildInfo,
|
||||
recordInternalSection: !!bundleBuildInfo
|
||||
};
|
||||
|
||||
const declarationPrinter = createPrinter(printerOptions, {
|
||||
@@ -243,19 +293,26 @@ namespace ts {
|
||||
emitSkipped = emitSkipped || declBlocked;
|
||||
if (!declBlocked || emitOnlyDtsFiles) {
|
||||
Debug.assert(declarationTransform.transformed.length === 1, "Should only see one output from the decl transform");
|
||||
printSourceFileOrBundle(declarationFilePath, declarationMapPath, declarationTransform.transformed[0], /* bundleInfopath*/ undefined, declarationPrinter, {
|
||||
sourceMap: compilerOptions.declarationMap,
|
||||
sourceRoot: compilerOptions.sourceRoot,
|
||||
mapRoot: compilerOptions.mapRoot,
|
||||
extendedDiagnostics: compilerOptions.extendedDiagnostics,
|
||||
// Explicitly do not passthru either `inline` option
|
||||
});
|
||||
printSourceFileOrBundle(
|
||||
declarationFilePath,
|
||||
declarationMapPath,
|
||||
declarationTransform.transformed[0],
|
||||
declarationPrinter,
|
||||
{
|
||||
sourceMap: compilerOptions.declarationMap,
|
||||
sourceRoot: compilerOptions.sourceRoot,
|
||||
mapRoot: compilerOptions.mapRoot,
|
||||
extendedDiagnostics: compilerOptions.extendedDiagnostics,
|
||||
// Explicitly do not passthru either `inline` option
|
||||
}
|
||||
);
|
||||
if (emitOnlyDtsFiles && declarationTransform.transformed[0].kind === SyntaxKind.SourceFile) {
|
||||
const sourceFile = declarationTransform.transformed[0] as SourceFile;
|
||||
exportedModulesFromDeclarationEmit = sourceFile.exportedModulesFromDeclarationEmit;
|
||||
}
|
||||
}
|
||||
declarationTransform.dispose();
|
||||
if (bundleBuildInfo) bundleBuildInfo.dts = declarationPrinter.bundleFileInfo;
|
||||
}
|
||||
|
||||
function collectLinkedAliases(node: Node) {
|
||||
@@ -272,7 +329,7 @@ namespace ts {
|
||||
forEachChild(node, collectLinkedAliases);
|
||||
}
|
||||
|
||||
function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, sourceFileOrBundle: SourceFile | Bundle, bundleInfoPath: string | undefined, printer: Printer, mapOptions: SourceMapOptions) {
|
||||
function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, sourceFileOrBundle: SourceFile | Bundle, printer: Printer, mapOptions: SourceMapOptions) {
|
||||
const bundle = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle : undefined;
|
||||
const sourceFile = sourceFileOrBundle.kind === SyntaxKind.SourceFile ? sourceFileOrBundle : undefined;
|
||||
const sourceFiles = bundle ? bundle.sourceFiles : [sourceFile!];
|
||||
@@ -288,7 +345,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
if (bundle) {
|
||||
printer.writeBundle(bundle, bundleInfo, writer, sourceMapGenerator);
|
||||
printer.writeBundle(bundle, writer, sourceMapGenerator);
|
||||
}
|
||||
else {
|
||||
printer.writeFile(sourceFile!, writer, sourceMapGenerator);
|
||||
@@ -327,16 +384,8 @@ namespace ts {
|
||||
// Write the output file
|
||||
writeFile(host, emitterDiagnostics, jsFilePath, writer.getText(), !!compilerOptions.emitBOM, sourceFiles);
|
||||
|
||||
// Write bundled offset information if applicable
|
||||
if (bundleInfoPath) {
|
||||
bundleInfo.totalLength = writer.getTextPos();
|
||||
writeFile(host, emitterDiagnostics, bundleInfoPath, JSON.stringify(bundleInfo, undefined, 2), /*writeByteOrderMark*/ false);
|
||||
}
|
||||
|
||||
// Reset state
|
||||
writer.clear();
|
||||
|
||||
bundleInfo = createDefaultBundleInfo();
|
||||
}
|
||||
|
||||
interface SourceMapOptions {
|
||||
@@ -412,6 +461,182 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export function getBuildInfoText(buildInfo: BuildInfo) {
|
||||
return JSON.stringify(buildInfo, undefined, 2);
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export function getBuildInfo(buildInfoText: string) {
|
||||
return JSON.parse(buildInfoText) as BuildInfo;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export const notImplementedResolver: EmitResolver = {
|
||||
hasGlobalName: notImplemented,
|
||||
getReferencedExportContainer: notImplemented,
|
||||
getReferencedImportDeclaration: notImplemented,
|
||||
getReferencedDeclarationWithCollidingName: notImplemented,
|
||||
isDeclarationWithCollidingName: notImplemented,
|
||||
isValueAliasDeclaration: notImplemented,
|
||||
isReferencedAliasDeclaration: notImplemented,
|
||||
isTopLevelValueImportEqualsWithEntityName: notImplemented,
|
||||
getNodeCheckFlags: notImplemented,
|
||||
isDeclarationVisible: notImplemented,
|
||||
isLateBound: (_node): _node is LateBoundDeclaration => false,
|
||||
collectLinkedAliases: notImplemented,
|
||||
isImplementationOfOverload: notImplemented,
|
||||
isRequiredInitializedParameter: notImplemented,
|
||||
isOptionalUninitializedParameterProperty: notImplemented,
|
||||
isExpandoFunctionDeclaration: notImplemented,
|
||||
getPropertiesOfContainerFunction: notImplemented,
|
||||
createTypeOfDeclaration: notImplemented,
|
||||
createReturnTypeOfSignatureDeclaration: notImplemented,
|
||||
createTypeOfExpression: notImplemented,
|
||||
createLiteralConstValue: notImplemented,
|
||||
isSymbolAccessible: notImplemented,
|
||||
isEntityNameVisible: notImplemented,
|
||||
// Returns the constant value this property access resolves to: notImplemented, or 'undefined' for a non-constant
|
||||
getConstantValue: notImplemented,
|
||||
getReferencedValueDeclaration: notImplemented,
|
||||
getTypeReferenceSerializationKind: notImplemented,
|
||||
isOptionalParameter: notImplemented,
|
||||
moduleExportsSomeValue: notImplemented,
|
||||
isArgumentsLocalBinding: notImplemented,
|
||||
getExternalModuleFileFromDeclaration: notImplemented,
|
||||
getTypeReferenceDirectivesForEntityName: notImplemented,
|
||||
getTypeReferenceDirectivesForSymbol: notImplemented,
|
||||
isLiteralConstDeclaration: notImplemented,
|
||||
getJsxFactoryEntity: notImplemented,
|
||||
getAllAccessorDeclarations: notImplemented,
|
||||
getSymbolOfExternalModuleSpecifier: notImplemented,
|
||||
isBindingCapturedByNode: notImplemented,
|
||||
};
|
||||
|
||||
/*@internal*/
|
||||
/** File that isnt present resulting in error or output files */
|
||||
export type EmitUsingBuildInfoResult = string | ReadonlyArray<OutputFile>;
|
||||
|
||||
/*@internal*/
|
||||
export interface EmitUsingBuildInfoHost extends ModuleResolutionHost {
|
||||
getCurrentDirectory(): string;
|
||||
getCanonicalFileName(fileName: string): string;
|
||||
useCaseSensitiveFileNames(): boolean;
|
||||
getNewLine(): string;
|
||||
}
|
||||
|
||||
function createSourceFilesFromBundleBuildInfo(bundle: BundleBuildInfo): ReadonlyArray<SourceFile> {
|
||||
const sourceFiles = bundle.sourceFiles.map(fileName => {
|
||||
const sourceFile = createNode(SyntaxKind.SourceFile, 0, 0) as SourceFile;
|
||||
sourceFile.fileName = fileName;
|
||||
sourceFile.text = "";
|
||||
sourceFile.statements = createNodeArray();
|
||||
return sourceFile;
|
||||
});
|
||||
const jsBundle = Debug.assertDefined(bundle.js);
|
||||
forEach(jsBundle.sources && jsBundle.sources.prologues, prologueInfo => {
|
||||
const sourceFile = sourceFiles[prologueInfo.file];
|
||||
sourceFile.text = prologueInfo.text;
|
||||
sourceFile.end = prologueInfo.text.length;
|
||||
sourceFile.statements = createNodeArray(prologueInfo.directives.map(directive => {
|
||||
const statement = createNode(SyntaxKind.ExpressionStatement, directive.pos, directive.end) as PrologueDirective;
|
||||
statement.expression = createNode(SyntaxKind.StringLiteral, directive.expression.pos, directive.expression.end) as StringLiteral;
|
||||
statement.expression.text = directive.expression.text;
|
||||
return statement;
|
||||
}));
|
||||
});
|
||||
return sourceFiles;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export function emitUsingBuildInfo(config: ParsedCommandLine, host: EmitUsingBuildInfoHost, getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined): EmitUsingBuildInfoResult {
|
||||
const { buildInfoPath, jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath } = getOutputPathsForBundle(config.options, /*forceDtsPaths*/ false);
|
||||
const buildInfoText = host.readFile(Debug.assertDefined(buildInfoPath));
|
||||
if (!buildInfoText) return buildInfoPath!;
|
||||
const jsFileText = host.readFile(Debug.assertDefined(jsFilePath));
|
||||
if (!jsFileText) return jsFilePath!;
|
||||
const sourceMapText = sourceMapFilePath && host.readFile(sourceMapFilePath);
|
||||
// error if no source map or for now if inline sourcemap
|
||||
if ((sourceMapFilePath && !sourceMapText) || config.options.inlineSourceMap) return sourceMapFilePath || "inline sourcemap decoding";
|
||||
// read declaration text
|
||||
const declarationText = declarationFilePath && host.readFile(declarationFilePath);
|
||||
if (declarationFilePath && !declarationText) return declarationFilePath;
|
||||
const declarationMapText = declarationMapPath && host.readFile(declarationMapPath);
|
||||
// error if no source map or for now if inline sourcemap
|
||||
if ((declarationMapPath && !declarationMapText) || config.options.inlineSourceMap) return declarationMapPath || "inline sourcemap decoding";
|
||||
|
||||
const buildInfo = getBuildInfo(buildInfoText);
|
||||
if (!buildInfo.bundle || !buildInfo.bundle.js || (declarationText && !buildInfo.bundle.dts)) return buildInfoPath!;
|
||||
const ownPrependInput = createInputFiles(
|
||||
jsFileText,
|
||||
declarationText!,
|
||||
sourceMapFilePath,
|
||||
sourceMapText,
|
||||
declarationMapPath,
|
||||
declarationMapText,
|
||||
jsFilePath,
|
||||
declarationFilePath,
|
||||
buildInfoPath,
|
||||
buildInfo,
|
||||
/*onlyOwnText*/ true
|
||||
);
|
||||
const outputFiles: OutputFile[] = [];
|
||||
const prependNodes = createPrependNodes(config.projectReferences, getCommandLine, f => host.readFile(f));
|
||||
const sourceFilesForJsEmit = createSourceFilesFromBundleBuildInfo(buildInfo.bundle);
|
||||
const emitHost: EmitHost = {
|
||||
getPrependNodes: memoize(() => [...prependNodes, ownPrependInput]),
|
||||
getCanonicalFileName: host.getCanonicalFileName,
|
||||
getCommonSourceDirectory: () => buildInfo.bundle!.commonSourceDirectory,
|
||||
getCompilerOptions: () => config.options,
|
||||
getCurrentDirectory: () => host.getCurrentDirectory(),
|
||||
getNewLine: () => host.getNewLine(),
|
||||
getSourceFile: () => undefined,
|
||||
getSourceFileByPath: () => undefined,
|
||||
getSourceFiles: () => sourceFilesForJsEmit,
|
||||
getLibFileFromReference: notImplemented,
|
||||
isSourceFileFromExternalLibrary: returnFalse,
|
||||
writeFile: (name, text, writeByteOrderMark) => {
|
||||
switch (name) {
|
||||
case jsFilePath:
|
||||
if (jsFileText === text) return;
|
||||
break;
|
||||
case sourceMapFilePath:
|
||||
if (sourceMapText === text) return;
|
||||
break;
|
||||
case buildInfoPath:
|
||||
const newBuildInfo = getBuildInfo(text);
|
||||
newBuildInfo.program = buildInfo.program;
|
||||
// Update sourceFileInfo
|
||||
const { js, dts, sourceFiles } = buildInfo.bundle!;
|
||||
newBuildInfo.bundle!.js!.sources = js!.sources;
|
||||
if (dts) {
|
||||
newBuildInfo.bundle!.dts!.sources = dts.sources;
|
||||
}
|
||||
newBuildInfo.bundle!.sourceFiles = sourceFiles;
|
||||
outputFiles.push({ name, text: getBuildInfoText(newBuildInfo), writeByteOrderMark });
|
||||
return;
|
||||
case declarationFilePath:
|
||||
if (declarationText === text) return;
|
||||
break;
|
||||
case declarationMapPath:
|
||||
if (declarationMapText === text) return;
|
||||
break;
|
||||
default:
|
||||
Debug.fail(`Unexpected path: ${name}`);
|
||||
}
|
||||
outputFiles.push({ name, text, writeByteOrderMark });
|
||||
},
|
||||
isEmitBlocked: returnFalse,
|
||||
readFile: f => host.readFile(f),
|
||||
fileExists: f => host.fileExists(f),
|
||||
directoryExists: host.directoryExists && (f => host.directoryExists!(f)),
|
||||
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(),
|
||||
getProgramBuildInfo: () => undefined
|
||||
};
|
||||
emitFiles(notImplementedResolver, emitHost, /*targetSourceFile*/ undefined, /*emitOnlyDtsFiles*/ false, getTransformers(config.options));
|
||||
return outputFiles;
|
||||
}
|
||||
|
||||
const enum PipelinePhase {
|
||||
Notification,
|
||||
Substitution,
|
||||
@@ -449,6 +674,10 @@ namespace ts {
|
||||
let ownWriter: EmitTextWriter; // Reusable `EmitTextWriter` for basic printing.
|
||||
let write = writeBase;
|
||||
let isOwnFileEmit: boolean;
|
||||
const bundleFileInfo = printerOptions.writeBundleFileInfo ? { sections: [] } as BundleFileInfo : undefined;
|
||||
const recordInternalSection = printerOptions.recordInternalSection;
|
||||
let sourceFileTextPos = 0;
|
||||
let sourceFileTextKind: BundleFileTextLikeKind = BundleFileSectionKind.Text;
|
||||
|
||||
// Source Maps
|
||||
let sourceMapsDisabled = true;
|
||||
@@ -478,7 +707,8 @@ namespace ts {
|
||||
writeNode,
|
||||
writeList,
|
||||
writeFile,
|
||||
writeBundle
|
||||
writeBundle,
|
||||
bundleFileInfo
|
||||
};
|
||||
|
||||
function printNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string {
|
||||
@@ -508,7 +738,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function printBundle(bundle: Bundle): string {
|
||||
writeBundle(bundle, /*bundleInfo*/ undefined, beginPrint(), /*sourceMapEmitter*/ undefined);
|
||||
writeBundle(bundle, beginPrint(), /*sourceMapEmitter*/ undefined);
|
||||
return endPrint();
|
||||
}
|
||||
|
||||
@@ -546,7 +776,53 @@ namespace ts {
|
||||
writer = previousWriter;
|
||||
}
|
||||
|
||||
function writeBundle(bundle: Bundle, bundleInfo: BundleInfo | undefined, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) {
|
||||
function getTextPosWithWriteLine() {
|
||||
return writer.getTextPosWithWriteLine ? writer.getTextPosWithWriteLine() : writer.getTextPos();
|
||||
}
|
||||
|
||||
function updateOrPushBundleFileTextLike(pos: number, end: number, kind: BundleFileTextLikeKind) {
|
||||
const last = lastOrUndefined(bundleFileInfo!.sections);
|
||||
if (last && last.kind === kind) {
|
||||
last.end = end;
|
||||
}
|
||||
else {
|
||||
bundleFileInfo!.sections.push({ pos, end, kind });
|
||||
}
|
||||
}
|
||||
|
||||
function recordBundleFileInternalSectionStart(node: Node) {
|
||||
if (recordInternalSection &&
|
||||
bundleFileInfo &&
|
||||
currentSourceFile &&
|
||||
(isDeclaration(node) || isVariableStatement(node)) &&
|
||||
isInternalDeclaration(node, currentSourceFile) &&
|
||||
sourceFileTextKind !== BundleFileSectionKind.Internal) {
|
||||
const prevSourceFileTextKind = sourceFileTextKind;
|
||||
recordBundleFileTextLikeSection(writer.getTextPos());
|
||||
sourceFileTextPos = getTextPosWithWriteLine();
|
||||
sourceFileTextKind = BundleFileSectionKind.Internal;
|
||||
return prevSourceFileTextKind;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function recordBundleFileInternalSectionEnd(prevSourceFileTextKind: ReturnType<typeof recordBundleFileInternalSectionStart>) {
|
||||
if (prevSourceFileTextKind) {
|
||||
recordBundleFileTextLikeSection(writer.getTextPos());
|
||||
sourceFileTextPos = getTextPosWithWriteLine();
|
||||
sourceFileTextKind = prevSourceFileTextKind;
|
||||
}
|
||||
}
|
||||
|
||||
function recordBundleFileTextLikeSection(end: number) {
|
||||
if (sourceFileTextPos < end) {
|
||||
updateOrPushBundleFileTextLike(sourceFileTextPos, end, sourceFileTextKind);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function writeBundle(bundle: Bundle, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) {
|
||||
isOwnFileEmit = false;
|
||||
const previousWriter = writer;
|
||||
setWriter(output, sourceMapGenerator);
|
||||
@@ -557,16 +833,44 @@ namespace ts {
|
||||
|
||||
for (const prepend of bundle.prepends) {
|
||||
writeLine();
|
||||
const pos = writer.getTextPos();
|
||||
const savedSections = bundleFileInfo && bundleFileInfo.sections;
|
||||
if (savedSections) bundleFileInfo!.sections = [];
|
||||
print(EmitHint.Unspecified, prepend, /*sourceFile*/ undefined);
|
||||
if (bundleFileInfo) {
|
||||
const newSections = bundleFileInfo.sections;
|
||||
bundleFileInfo.sections = savedSections!;
|
||||
if (prepend.oldFileOfCurrentEmit) bundleFileInfo.sections.push(...newSections);
|
||||
else {
|
||||
newSections.forEach(section => Debug.assert(isBundleFileTextLike(section)));
|
||||
bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prepend, data: (prepend as UnparsedSource).fileName, texts: newSections as BundleFileTextLike[] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bundleInfo) {
|
||||
bundleInfo.originalOffset = writer.getTextPos();
|
||||
}
|
||||
|
||||
sourceFileTextPos = getTextPosWithWriteLine();
|
||||
for (const sourceFile of bundle.sourceFiles) {
|
||||
print(EmitHint.SourceFile, sourceFile, sourceFile);
|
||||
}
|
||||
if (bundleFileInfo && bundle.sourceFiles.length) {
|
||||
const end = writer.getTextPos();
|
||||
if (recordBundleFileTextLikeSection(end)) {
|
||||
// Store prologues
|
||||
const prologues = getPrologueDirectivesFromBundledSourceFiles(bundle);
|
||||
if (prologues) {
|
||||
if (!bundleFileInfo.sources) bundleFileInfo.sources = {};
|
||||
bundleFileInfo.sources.prologues = prologues;
|
||||
}
|
||||
|
||||
// Store helpes
|
||||
const helpers = getHelpersFromBundledSourceFiles(bundle);
|
||||
if (helpers) {
|
||||
if (!bundleFileInfo.sources) bundleFileInfo.sources = {};
|
||||
bundleFileInfo.sources.helpers = helpers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reset();
|
||||
writer = previousWriter;
|
||||
}
|
||||
@@ -647,8 +951,10 @@ namespace ts {
|
||||
|
||||
function emit(node: Node | undefined) {
|
||||
if (node === undefined) return;
|
||||
const prevSourceFileTextKind = recordBundleFileInternalSectionStart(node);
|
||||
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node);
|
||||
pipelinePhase(EmitHint.Unspecified, node);
|
||||
recordBundleFileInternalSectionEnd(prevSourceFileTextKind);
|
||||
}
|
||||
|
||||
function emitIdentifierName(node: Identifier | undefined) {
|
||||
@@ -725,7 +1031,19 @@ namespace ts {
|
||||
return emitLiteral(<LiteralExpression>node);
|
||||
|
||||
case SyntaxKind.UnparsedSource:
|
||||
return emitUnparsedSource(<UnparsedSource>node);
|
||||
case SyntaxKind.UnparsedPrepend:
|
||||
return emitUnparsedSourceOrPrepend(<UnparsedSource>node);
|
||||
|
||||
case SyntaxKind.UnparsedPrologue:
|
||||
return writeUnparsedNode(<UnparsedNode>node);
|
||||
|
||||
case SyntaxKind.UnparsedText:
|
||||
case SyntaxKind.UnparsedInternalText:
|
||||
return emitUnparsedTextLike(<UnparsedTextLike>node);
|
||||
|
||||
case SyntaxKind.UnparsedSyntheticReference:
|
||||
return emitUnparsedSyntheticReference(<UnparsedSyntheticReference>node);
|
||||
|
||||
|
||||
// Identifiers
|
||||
case SyntaxKind.Identifier:
|
||||
@@ -1127,22 +1445,43 @@ namespace ts {
|
||||
pipelinePhase(hint, substituteNode(hint, node));
|
||||
}
|
||||
|
||||
function getHelpersFromBundledSourceFiles(bundle: Bundle): string[] | undefined {
|
||||
let result: string[] | undefined;
|
||||
if (moduleKind === ModuleKind.None || printerOptions.noEmitHelpers) {
|
||||
return undefined;
|
||||
}
|
||||
const bundledHelpers = createMap<boolean>();
|
||||
for (const sourceFile of bundle.sourceFiles) {
|
||||
const shouldSkip = getExternalHelpersModuleName(sourceFile) !== undefined;
|
||||
const helpers = getSortedEmitHelpers(sourceFile);
|
||||
if (!helpers) continue;
|
||||
for (const helper of helpers) {
|
||||
if (!helper.scoped && !shouldSkip && !bundledHelpers.get(helper.name)) {
|
||||
bundledHelpers.set(helper.name, true);
|
||||
(result || (result = [])).push(helper.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function emitHelpers(node: Node) {
|
||||
let helpersEmitted = false;
|
||||
const bundle = node.kind === SyntaxKind.Bundle ? <Bundle>node : undefined;
|
||||
if (bundle && moduleKind === ModuleKind.None) {
|
||||
return;
|
||||
}
|
||||
|
||||
const numNodes = bundle ? bundle.sourceFiles.length : 1;
|
||||
const numPrepends = bundle ? bundle.prepends.length : 0;
|
||||
const numNodes = bundle ? bundle.sourceFiles.length + numPrepends : 1;
|
||||
for (let i = 0; i < numNodes; i++) {
|
||||
const currentNode = bundle ? bundle.sourceFiles[i] : node;
|
||||
const sourceFile = isSourceFile(currentNode) ? currentNode : currentSourceFile!;
|
||||
const shouldSkip = printerOptions.noEmitHelpers || getExternalHelpersModuleName(sourceFile) !== undefined;
|
||||
const shouldBundle = isSourceFile(currentNode) && !isOwnFileEmit;
|
||||
const helpers = getEmitHelpers(currentNode);
|
||||
const currentNode = bundle ? i < numPrepends ? bundle.prepends[i] : bundle.sourceFiles[i - numPrepends] : node;
|
||||
const sourceFile = isSourceFile(currentNode) ? currentNode : isUnparsedSource(currentNode) ? undefined : currentSourceFile!;
|
||||
const shouldSkip = printerOptions.noEmitHelpers || (!!sourceFile && getExternalHelpersModuleName(sourceFile) !== undefined);
|
||||
const shouldBundle = (isSourceFile(currentNode) || isUnparsedSource(currentNode)) && !isOwnFileEmit;
|
||||
const helpers = isUnparsedSource(currentNode) ? currentNode.helpers : getSortedEmitHelpers(currentNode);
|
||||
if (helpers) {
|
||||
for (const helper of stableSort(helpers, compareEmitHelpers)) {
|
||||
for (const helper of helpers) {
|
||||
if (!helper.scoped) {
|
||||
// Skip the helper if it can be skipped and the noEmitHelpers compiler
|
||||
// option is set, or if it can be imported and the importHelpers compiler
|
||||
@@ -1163,13 +1502,14 @@ namespace ts {
|
||||
// Skip the helper if it is scoped and we are emitting bundled helpers
|
||||
continue;
|
||||
}
|
||||
|
||||
const pos = getTextPosWithWriteLine();
|
||||
if (typeof helper.text === "string") {
|
||||
writeLines(helper.text);
|
||||
}
|
||||
else {
|
||||
writeLines(helper.text(makeFileLevelOptimisticUniqueName));
|
||||
}
|
||||
if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.EmitHelpers, data: helper.name });
|
||||
helpersEmitted = true;
|
||||
}
|
||||
}
|
||||
@@ -1178,6 +1518,11 @@ namespace ts {
|
||||
return helpersEmitted;
|
||||
}
|
||||
|
||||
function getSortedEmitHelpers(node: Node) {
|
||||
const helpers = getEmitHelpers(node);
|
||||
return helpers && stableSort(helpers, compareEmitHelpers);
|
||||
}
|
||||
|
||||
//
|
||||
// Literals/Pseudo-literals
|
||||
//
|
||||
@@ -1207,8 +1552,48 @@ namespace ts {
|
||||
}
|
||||
|
||||
// SyntaxKind.UnparsedSource
|
||||
function emitUnparsedSource(unparsed: UnparsedSource) {
|
||||
writer.rawWrite(unparsed.text);
|
||||
// SyntaxKind.UnparsedPrepend
|
||||
function emitUnparsedSourceOrPrepend(unparsed: UnparsedSource | UnparsedPrepend) {
|
||||
for (const text of unparsed.texts) {
|
||||
writeLine();
|
||||
emit(text);
|
||||
}
|
||||
}
|
||||
|
||||
// SyntaxKind.UnparsedPrologue
|
||||
// SyntaxKind.UnparsedText
|
||||
// SyntaxKind.UnparsedInternal
|
||||
// SyntaxKind.UnparsedSyntheticReference
|
||||
function writeUnparsedNode(unparsed: UnparsedNode) {
|
||||
writer.rawWrite(unparsed.parent.text.substring(unparsed.pos, unparsed.end));
|
||||
}
|
||||
|
||||
// SyntaxKind.UnparsedText
|
||||
// SyntaxKind.UnparsedInternal
|
||||
function emitUnparsedTextLike(unparsed: UnparsedTextLike) {
|
||||
const pos = getTextPosWithWriteLine();
|
||||
writeUnparsedNode(unparsed);
|
||||
if (bundleFileInfo) {
|
||||
updateOrPushBundleFileTextLike(
|
||||
pos,
|
||||
writer.getTextPos(),
|
||||
unparsed.kind === SyntaxKind.UnparsedText ?
|
||||
BundleFileSectionKind.Text :
|
||||
BundleFileSectionKind.Internal
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// SyntaxKind.UnparsedSyntheticReference
|
||||
function emitUnparsedSyntheticReference(unparsed: UnparsedSyntheticReference) {
|
||||
const pos = getTextPosWithWriteLine();
|
||||
writeUnparsedNode(unparsed);
|
||||
if (bundleFileInfo) {
|
||||
const section = clone(unparsed.section);
|
||||
section.pos = pos;
|
||||
section.end = writer.getTextPos();
|
||||
bundleFileInfo.sections.push(section);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -2294,7 +2679,7 @@ namespace ts {
|
||||
|
||||
function emitBlockFunctionBodyWorker(body: Block, emitBlockFunctionBodyOnSingleLine?: boolean) {
|
||||
// Emit all the prologue directives (like "use strict").
|
||||
const statementOffset = emitPrologueDirectives(body.statements, /*startWithNewLine*/ true);
|
||||
const statementOffset = emitPrologueDirectives(body.statements);
|
||||
const pos = writer.getTextPos();
|
||||
emitHelpers(body);
|
||||
if (statementOffset === 0 && pos === writer.getTextPos() && emitBlockFunctionBodyOnSingleLine) {
|
||||
@@ -2916,6 +3301,14 @@ namespace ts {
|
||||
|
||||
function emitSyntheticTripleSlashReferencesIfNeeded(node: Bundle) {
|
||||
emitTripleSlashDirectives(!!node.hasNoDefaultLib, node.syntheticFileReferences || [], node.syntheticTypeReferences || [], node.syntheticLibReferences || []);
|
||||
for (const prepend of node.prepends) {
|
||||
if (isUnparsedSource(prepend) && prepend.syntheticReferences) {
|
||||
for (const ref of prepend.syntheticReferences) {
|
||||
emit(ref);
|
||||
writeLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emitTripleSlashDirectivesIfNeeded(node: SourceFile) {
|
||||
@@ -2924,7 +3317,9 @@ namespace ts {
|
||||
|
||||
function emitTripleSlashDirectives(hasNoDefaultLib: boolean, files: ReadonlyArray<FileReference>, types: ReadonlyArray<FileReference>, libs: ReadonlyArray<FileReference>) {
|
||||
if (hasNoDefaultLib) {
|
||||
const pos = writer.getTextPos();
|
||||
writeComment(`/// <reference no-default-lib="true"/>`);
|
||||
if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.NoDefaultLib });
|
||||
writeLine();
|
||||
}
|
||||
if (currentSourceFile && currentSourceFile.moduleName) {
|
||||
@@ -2943,15 +3338,21 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
for (const directive of files) {
|
||||
const pos = writer.getTextPos();
|
||||
writeComment(`/// <reference path="${directive.fileName}" />`);
|
||||
if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Reference, data: directive.fileName });
|
||||
writeLine();
|
||||
}
|
||||
for (const directive of types) {
|
||||
const pos = writer.getTextPos();
|
||||
writeComment(`/// <reference types="${directive.fileName}" />`);
|
||||
if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Type, data: directive.fileName });
|
||||
writeLine();
|
||||
}
|
||||
for (const directive of libs) {
|
||||
const pos = writer.getTextPos();
|
||||
writeComment(`/// <reference lib="${directive.fileName}" />`);
|
||||
if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Lib, data: directive.fileName });
|
||||
writeLine();
|
||||
}
|
||||
}
|
||||
@@ -2981,16 +3382,21 @@ namespace ts {
|
||||
* Emits any prologue directives at the start of a Statement list, returning the
|
||||
* number of prologue directives written to the output.
|
||||
*/
|
||||
function emitPrologueDirectives(statements: ReadonlyArray<Node>, startWithNewLine?: boolean, seenPrologueDirectives?: Map<true>): number {
|
||||
function emitPrologueDirectives(statements: ReadonlyArray<Node>, sourceFile?: SourceFile, seenPrologueDirectives?: Map<true>, recordBundleFileSection?: true): number {
|
||||
let needsToSetSourceFile = !!sourceFile;
|
||||
for (let i = 0; i < statements.length; i++) {
|
||||
const statement = statements[i];
|
||||
if (isPrologueDirective(statement)) {
|
||||
const shouldEmitPrologueDirective = seenPrologueDirectives ? !seenPrologueDirectives.has(statement.expression.text) : true;
|
||||
if (shouldEmitPrologueDirective) {
|
||||
if (startWithNewLine || i > 0) {
|
||||
writeLine();
|
||||
if (needsToSetSourceFile) {
|
||||
needsToSetSourceFile = false;
|
||||
setSourceFile(sourceFile!);
|
||||
}
|
||||
writeLine();
|
||||
const pos = writer.getTextPos();
|
||||
emit(statement);
|
||||
if (recordBundleFileSection && bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: statement.expression.text });
|
||||
if (seenPrologueDirectives) {
|
||||
seenPrologueDirectives.set(statement.expression.text, true);
|
||||
}
|
||||
@@ -3005,23 +3411,65 @@ namespace ts {
|
||||
return statements.length;
|
||||
}
|
||||
|
||||
function emitUnparsedPrologues(prologues: ReadonlyArray<UnparsedPrologue>, seenPrologueDirectives: Map<true>) {
|
||||
for (const prologue of prologues) {
|
||||
if (!seenPrologueDirectives.has(prologue.data)) {
|
||||
writeLine();
|
||||
const pos = writer.getTextPos();
|
||||
emit(prologue);
|
||||
if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prologue, data: prologue.data });
|
||||
if (seenPrologueDirectives) {
|
||||
seenPrologueDirectives.set(prologue.data, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emitPrologueDirectivesIfNeeded(sourceFileOrBundle: Bundle | SourceFile) {
|
||||
if (isSourceFile(sourceFileOrBundle)) {
|
||||
setSourceFile(sourceFileOrBundle);
|
||||
emitPrologueDirectives(sourceFileOrBundle.statements);
|
||||
emitPrologueDirectives(sourceFileOrBundle.statements, sourceFileOrBundle);
|
||||
}
|
||||
else {
|
||||
const seenPrologueDirectives = createMap<true>();
|
||||
for (const prepend of sourceFileOrBundle.prepends) {
|
||||
emitUnparsedPrologues((prepend as UnparsedSource).prologues, seenPrologueDirectives);
|
||||
}
|
||||
for (const sourceFile of sourceFileOrBundle.sourceFiles) {
|
||||
setSourceFile(sourceFile);
|
||||
emitPrologueDirectives(sourceFile.statements, /*startWithNewLine*/ true, seenPrologueDirectives);
|
||||
emitPrologueDirectives(sourceFile.statements, sourceFile, seenPrologueDirectives, /*recordBundleFileSection*/ true);
|
||||
}
|
||||
setSourceFile(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
function emitShebangIfNeeded(sourceFileOrBundle: Bundle | SourceFile) {
|
||||
if (isSourceFile(sourceFileOrBundle)) {
|
||||
function getPrologueDirectivesFromBundledSourceFiles(bundle: Bundle): SourceFilePrologueInfo[] | undefined {
|
||||
const seenPrologueDirectives = createMap<true>();
|
||||
let prologues: SourceFilePrologueInfo[] | undefined;
|
||||
for (let index = 0; index < bundle.sourceFiles.length; index++) {
|
||||
const sourceFile = bundle.sourceFiles[index];
|
||||
let directives: SourceFilePrologueDirective[] | undefined;
|
||||
let end = 0;
|
||||
for (const statement of sourceFile.statements) {
|
||||
if (!isPrologueDirective(statement)) break;
|
||||
if (seenPrologueDirectives.has(statement.expression.text)) continue;
|
||||
seenPrologueDirectives.set(statement.expression.text, true);
|
||||
(directives || (directives = [])).push({
|
||||
pos: statement.pos,
|
||||
end: statement.end,
|
||||
expression: {
|
||||
pos: statement.expression.pos,
|
||||
end: statement.expression.end,
|
||||
text: statement.expression.text
|
||||
}
|
||||
});
|
||||
end = end < statement.end ? statement.end : end;
|
||||
}
|
||||
if (directives) (prologues || (prologues = [])).push({ file: index, text: sourceFile.text.substring(0, end), directives });
|
||||
}
|
||||
return prologues;
|
||||
}
|
||||
|
||||
function emitShebangIfNeeded(sourceFileOrBundle: Bundle | SourceFile | UnparsedSource) {
|
||||
if (isSourceFile(sourceFileOrBundle) || isUnparsedSource(sourceFileOrBundle)) {
|
||||
const shebang = getShebang(sourceFileOrBundle.text);
|
||||
if (shebang) {
|
||||
writeComment(shebang);
|
||||
@@ -3030,10 +3478,16 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const prepend of sourceFileOrBundle.prepends) {
|
||||
Debug.assertNode(prepend, isUnparsedSource);
|
||||
if (emitShebangIfNeeded(prepend as UnparsedSource)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const sourceFile of sourceFileOrBundle.sourceFiles) {
|
||||
// Emit only the first encountered shebang
|
||||
if (emitShebangIfNeeded(sourceFile)) {
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3260,6 +3714,7 @@ namespace ts {
|
||||
|
||||
// Emit each child.
|
||||
let previousSibling: Node | undefined;
|
||||
let previousSourceFileTextKind: ReturnType<typeof recordBundleFileInternalSectionStart>;
|
||||
let shouldDecreaseIndentAfterEmit = false;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const child = children![start + i];
|
||||
@@ -3281,6 +3736,7 @@ namespace ts {
|
||||
emitLeadingCommentsOfPosition(previousSibling.end);
|
||||
}
|
||||
writeDelimiter(format);
|
||||
recordBundleFileInternalSectionEnd(previousSourceFileTextKind);
|
||||
|
||||
// Write either a line terminator or whitespace to separate the elements.
|
||||
if (shouldWriteSeparatingLineTerminator(previousSibling, child, format)) {
|
||||
@@ -3300,6 +3756,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
// Emit this child.
|
||||
previousSourceFileTextKind = recordBundleFileInternalSectionStart(child);
|
||||
if (shouldEmitInterveningComments) {
|
||||
if (emitTrailingCommentsOfPosition) {
|
||||
const commentRange = getCommentRange(child);
|
||||
@@ -3342,6 +3799,8 @@ namespace ts {
|
||||
decreaseIndent();
|
||||
}
|
||||
|
||||
recordBundleFileInternalSectionEnd(previousSourceFileTextKind);
|
||||
|
||||
// Write the closing line terminator or closing whitespace.
|
||||
if (shouldWriteClosingLineTerminator(parentNode, children!, format)) {
|
||||
writeLine();
|
||||
@@ -3467,7 +3926,6 @@ namespace ts {
|
||||
if (line.length) {
|
||||
writeLine();
|
||||
write(line);
|
||||
writer.rawWrite(newLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4337,16 +4795,29 @@ namespace ts {
|
||||
|
||||
// Source Maps
|
||||
|
||||
function getParsedSourceMap(node: UnparsedSource) {
|
||||
if (node.parsedSourceMap === undefined && node.sourceMapText !== undefined) {
|
||||
node.parsedSourceMap = tryParseRawSourceMap(node.sourceMapText) || false;
|
||||
}
|
||||
return node.parsedSourceMap || undefined;
|
||||
}
|
||||
|
||||
function pipelineEmitWithSourceMap(hint: EmitHint, node: Node) {
|
||||
const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, node);
|
||||
if (isUnparsedSource(node) && node.sourceMapText !== undefined) {
|
||||
const parsed = tryParseRawSourceMap(node.sourceMapText);
|
||||
if (parsed) {
|
||||
sourceMapGenerator!.appendSourceMap(
|
||||
if (isUnparsedSource(node) || isUnparsedPrepend(node)) {
|
||||
pipelinePhase(hint, node);
|
||||
}
|
||||
else if (isUnparsedNode(node)) {
|
||||
const parsed = getParsedSourceMap(node.parent);
|
||||
if (parsed && sourceMapGenerator) {
|
||||
sourceMapGenerator.appendSourceMap(
|
||||
writer.getLine(),
|
||||
writer.getColumn(),
|
||||
parsed,
|
||||
node.sourceMapPath!);
|
||||
node.parent.sourceMapPath!,
|
||||
node.parent.getLineAndCharacterOfPosition(node.pos),
|
||||
node.parent.getLineAndCharacterOfPosition(node.end)
|
||||
);
|
||||
}
|
||||
pipelinePhase(hint, node);
|
||||
}
|
||||
|
||||
+226
-14
@@ -143,8 +143,8 @@ namespace ts {
|
||||
export function updateIdentifier(node: Identifier, typeArguments: NodeArray<TypeNode | TypeParameterDeclaration> | undefined): Identifier; // tslint:disable-line unified-signatures
|
||||
export function updateIdentifier(node: Identifier, typeArguments?: NodeArray<TypeNode | TypeParameterDeclaration> | undefined): Identifier {
|
||||
return node.typeArguments !== typeArguments
|
||||
? updateNode(createIdentifier(idText(node), typeArguments), node)
|
||||
: node;
|
||||
? updateNode(createIdentifier(idText(node), typeArguments), node)
|
||||
: node;
|
||||
}
|
||||
|
||||
let nextAutoGenerateId = 0;
|
||||
@@ -2629,27 +2629,204 @@ namespace ts {
|
||||
return node;
|
||||
}
|
||||
|
||||
export function createUnparsedSourceFile(text: string): UnparsedSource;
|
||||
export function createUnparsedSourceFile(inputFile: InputFiles, type: "js" | "dts"): UnparsedSource;
|
||||
export function createUnparsedSourceFile(text: string, mapPath: string | undefined, map: string | undefined): UnparsedSource;
|
||||
export function createUnparsedSourceFile(textOrInputFiles: string | InputFiles, mapPathOrType?: string, map?: string): UnparsedSource {
|
||||
let allUnscopedEmitHelpers: ReadonlyMap<UnscopedEmitHelper> | undefined;
|
||||
function getAllUnscopedEmitHelpers() {
|
||||
return allUnscopedEmitHelpers || (allUnscopedEmitHelpers = arrayToMap([
|
||||
valuesHelper,
|
||||
readHelper,
|
||||
spreadHelper,
|
||||
restHelper,
|
||||
decorateHelper,
|
||||
metadataHelper,
|
||||
paramHelper,
|
||||
awaiterHelper,
|
||||
assignHelper,
|
||||
awaitHelper,
|
||||
asyncGeneratorHelper,
|
||||
asyncDelegator,
|
||||
asyncValues,
|
||||
extendsHelper,
|
||||
templateObjectHelper,
|
||||
generatorHelper,
|
||||
importStarHelper,
|
||||
importDefaultHelper
|
||||
], helper => helper.name));
|
||||
}
|
||||
|
||||
function createUnparsedSource() {
|
||||
const node = <UnparsedSource>createNode(SyntaxKind.UnparsedSource);
|
||||
node.prologues = emptyArray;
|
||||
node.referencedFiles = emptyArray;
|
||||
node.libReferenceDirectives = emptyArray;
|
||||
node.getLineAndCharacterOfPosition = pos => getLineAndCharacterOfPosition(node, pos);
|
||||
return node;
|
||||
}
|
||||
|
||||
export function createUnparsedSourceFile(text: string): UnparsedSource;
|
||||
export function createUnparsedSourceFile(inputFile: InputFiles, type: "js" | "dts", stripInternal?: boolean): UnparsedSource;
|
||||
export function createUnparsedSourceFile(text: string, mapPath: string | undefined, map: string | undefined): UnparsedSource;
|
||||
export function createUnparsedSourceFile(textOrInputFiles: string | InputFiles, mapPathOrType?: string, mapTextOrStripInternal?: string | boolean): UnparsedSource {
|
||||
const node = createUnparsedSource();
|
||||
let stripInternal: boolean | undefined;
|
||||
let bundleFileInfo: BundleFileInfo | undefined;
|
||||
if (!isString(textOrInputFiles)) {
|
||||
Debug.assert(mapPathOrType === "js" || mapPathOrType === "dts");
|
||||
node.fileName = mapPathOrType === "js" ? textOrInputFiles.javascriptPath : textOrInputFiles.declarationPath;
|
||||
node.fileName = (mapPathOrType === "js" ? textOrInputFiles.javascriptPath : textOrInputFiles.declarationPath) || "";
|
||||
node.sourceMapPath = mapPathOrType === "js" ? textOrInputFiles.javascriptMapPath : textOrInputFiles.declarationMapPath;
|
||||
Object.defineProperties(node, {
|
||||
text: { get() { return mapPathOrType === "js" ? textOrInputFiles.javascriptText : textOrInputFiles.declarationText; } },
|
||||
sourceMapText: { get() { return mapPathOrType === "js" ? textOrInputFiles.javascriptMapText : textOrInputFiles.declarationMapText; } },
|
||||
});
|
||||
|
||||
|
||||
if (textOrInputFiles.buildInfo && textOrInputFiles.buildInfo.bundle) {
|
||||
node.oldFileOfCurrentEmit = textOrInputFiles.oldFileOfCurrentEmit;
|
||||
Debug.assert(mapTextOrStripInternal === undefined || typeof mapTextOrStripInternal === "boolean");
|
||||
stripInternal = mapTextOrStripInternal as boolean | undefined;
|
||||
bundleFileInfo = mapPathOrType === "js" ? textOrInputFiles.buildInfo.bundle.js : textOrInputFiles.buildInfo.bundle.dts;
|
||||
if (node.oldFileOfCurrentEmit) {
|
||||
parseOldFileOfCurrentEmit(node, Debug.assertDefined(bundleFileInfo));
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
node.fileName = "";
|
||||
node.text = textOrInputFiles;
|
||||
node.sourceMapPath = mapPathOrType;
|
||||
node.sourceMapText = map;
|
||||
node.sourceMapText = mapTextOrStripInternal as string;
|
||||
}
|
||||
Debug.assert(!node.oldFileOfCurrentEmit);
|
||||
parseUnparsedSourceFile(node, bundleFileInfo, stripInternal);
|
||||
return node;
|
||||
}
|
||||
|
||||
function parseUnparsedSourceFile(node: UnparsedSource, bundleFileInfo: BundleFileInfo | undefined, stripInternal: boolean | undefined) {
|
||||
let prologues: UnparsedPrologue[] | undefined;
|
||||
let helpers: UnscopedEmitHelper[] | undefined;
|
||||
let referencedFiles: FileReference[] | undefined;
|
||||
let typeReferenceDirectives: string[] | undefined;
|
||||
let libReferenceDirectives: FileReference[] | undefined;
|
||||
let texts: UnparsedSourceText[] | undefined;
|
||||
|
||||
for (const section of bundleFileInfo ? bundleFileInfo.sections : emptyArray) {
|
||||
switch (section.kind) {
|
||||
case BundleFileSectionKind.Prologue:
|
||||
(prologues || (prologues = [])).push(createUnparsedNode(section, node) as UnparsedPrologue);
|
||||
break;
|
||||
case BundleFileSectionKind.EmitHelpers:
|
||||
(helpers || (helpers = [])).push(getAllUnscopedEmitHelpers().get(section.data)!);
|
||||
break;
|
||||
case BundleFileSectionKind.NoDefaultLib:
|
||||
node.hasNoDefaultLib = true;
|
||||
break;
|
||||
case BundleFileSectionKind.Reference:
|
||||
(referencedFiles || (referencedFiles = [])).push({ pos: -1, end: -1, fileName: section.data });
|
||||
break;
|
||||
case BundleFileSectionKind.Type:
|
||||
(typeReferenceDirectives || (typeReferenceDirectives = [])).push(section.data);
|
||||
break;
|
||||
case BundleFileSectionKind.Lib:
|
||||
(libReferenceDirectives || (libReferenceDirectives = [])).push({ pos: -1, end: -1, fileName: section.data });
|
||||
break;
|
||||
case BundleFileSectionKind.Prepend:
|
||||
const prependNode = createUnparsedNode(section, node) as UnparsedPrepend;
|
||||
let prependTexts: UnparsedTextLike[] | undefined;
|
||||
for (const text of section.texts) {
|
||||
if (!stripInternal || text.kind !== BundleFileSectionKind.Internal) {
|
||||
(prependTexts || (prependTexts = [])).push(createUnparsedNode(text, node) as UnparsedTextLike);
|
||||
}
|
||||
}
|
||||
prependNode.texts = prependTexts || emptyArray;
|
||||
(texts || (texts = [])).push(prependNode);
|
||||
break;
|
||||
case BundleFileSectionKind.Internal:
|
||||
if (stripInternal) break;
|
||||
// falls through
|
||||
case BundleFileSectionKind.Text:
|
||||
(texts || (texts = [])).push(createUnparsedNode(section, node) as UnparsedTextLike);
|
||||
break;
|
||||
default:
|
||||
Debug.assertNever(section);
|
||||
}
|
||||
}
|
||||
|
||||
node.prologues = prologues || emptyArray;
|
||||
node.helpers = helpers;
|
||||
node.referencedFiles = referencedFiles || emptyArray;
|
||||
node.typeReferenceDirectives = typeReferenceDirectives;
|
||||
node.libReferenceDirectives = libReferenceDirectives || emptyArray;
|
||||
node.texts = texts || [<UnparsedTextLike>createUnparsedNode({ kind: BundleFileSectionKind.Text, pos: 0, end: node.text.length }, node)];
|
||||
}
|
||||
|
||||
function parseOldFileOfCurrentEmit(node: UnparsedSource, bundleFileInfo: BundleFileInfo) {
|
||||
Debug.assert(!!node.oldFileOfCurrentEmit);
|
||||
let texts: UnparsedTextLike[] | undefined;
|
||||
let syntheticReferences: UnparsedSyntheticReference[] | undefined;
|
||||
for (const section of bundleFileInfo.sections) {
|
||||
switch (section.kind) {
|
||||
case BundleFileSectionKind.Internal:
|
||||
case BundleFileSectionKind.Text:
|
||||
(texts || (texts = [])).push(createUnparsedNode(section, node) as UnparsedTextLike);
|
||||
break;
|
||||
|
||||
case BundleFileSectionKind.NoDefaultLib:
|
||||
case BundleFileSectionKind.Reference:
|
||||
case BundleFileSectionKind.Type:
|
||||
case BundleFileSectionKind.Lib:
|
||||
(syntheticReferences || (syntheticReferences = [])).push(createUnparsedSyntheticReference(section, node));
|
||||
break;
|
||||
|
||||
// Ignore
|
||||
case BundleFileSectionKind.Prologue:
|
||||
case BundleFileSectionKind.EmitHelpers:
|
||||
case BundleFileSectionKind.Prepend:
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.assertNever(section);
|
||||
}
|
||||
}
|
||||
node.texts = texts || emptyArray;
|
||||
node.helpers = map(bundleFileInfo.sources && bundleFileInfo.sources.helpers, name => getAllUnscopedEmitHelpers().get(name)!);
|
||||
node.syntheticReferences = syntheticReferences;
|
||||
return node;
|
||||
}
|
||||
|
||||
function mapBundleFileSectionKindToSyntaxKind(kind: BundleFileSectionKind): SyntaxKind {
|
||||
switch (kind) {
|
||||
case BundleFileSectionKind.Prologue: return SyntaxKind.UnparsedPrologue;
|
||||
case BundleFileSectionKind.Prepend: return SyntaxKind.UnparsedPrepend;
|
||||
case BundleFileSectionKind.Internal: return SyntaxKind.UnparsedInternalText;
|
||||
case BundleFileSectionKind.Text: return SyntaxKind.UnparsedText;
|
||||
|
||||
case BundleFileSectionKind.EmitHelpers:
|
||||
case BundleFileSectionKind.NoDefaultLib:
|
||||
case BundleFileSectionKind.Reference:
|
||||
case BundleFileSectionKind.Type:
|
||||
case BundleFileSectionKind.Lib:
|
||||
return Debug.fail(`BundleFileSectionKind: ${kind} not yet mapped to SyntaxKind`);
|
||||
|
||||
default:
|
||||
return Debug.assertNever(kind);
|
||||
}
|
||||
}
|
||||
|
||||
function createUnparsedNode(section: BundleFileSection, parent: UnparsedSource): UnparsedNode {
|
||||
const node = createNode(mapBundleFileSectionKindToSyntaxKind(section.kind), section.pos, section.end) as UnparsedNode;
|
||||
node.parent = parent;
|
||||
node.data = section.data;
|
||||
return node;
|
||||
}
|
||||
|
||||
function createUnparsedSyntheticReference(section: BundleFileHasNoDefaultLib | BundleFileReference, parent: UnparsedSource) {
|
||||
const node = createNode(SyntaxKind.UnparsedSyntheticReference, section.pos, section.end) as UnparsedSyntheticReference;
|
||||
node.parent = parent;
|
||||
node.data = section.data;
|
||||
node.section = section;
|
||||
return node;
|
||||
}
|
||||
|
||||
export function createInputFiles(
|
||||
javascriptText: string,
|
||||
declarationText: string
|
||||
@@ -2660,6 +2837,7 @@ namespace ts {
|
||||
javascriptMapPath: string | undefined,
|
||||
declarationPath: string,
|
||||
declarationMapPath: string | undefined,
|
||||
buildInfoPath: string | undefined
|
||||
): InputFiles;
|
||||
export function createInputFiles(
|
||||
javascriptText: string,
|
||||
@@ -2669,13 +2847,32 @@ namespace ts {
|
||||
declarationMapPath: string | undefined,
|
||||
declarationMapText: string | undefined
|
||||
): InputFiles;
|
||||
/*@internal*/
|
||||
export function createInputFiles(
|
||||
javascriptText: string,
|
||||
declarationText: string,
|
||||
javascriptMapPath: string | undefined,
|
||||
javascriptMapText: string | undefined,
|
||||
declarationMapPath: string | undefined,
|
||||
declarationMapText: string | undefined,
|
||||
javascriptPath: string | undefined,
|
||||
declarationPath: string | undefined,
|
||||
buildInfoPath?: string | undefined,
|
||||
buildInfo?: BuildInfo,
|
||||
oldFileOfCurrentEmit?: boolean
|
||||
): InputFiles;
|
||||
export function createInputFiles(
|
||||
javascriptTextOrReadFileText: string | ((path: string) => string | undefined),
|
||||
declarationTextOrJavascriptPath: string,
|
||||
javascriptMapPath?: string,
|
||||
javascriptMapTextOrDeclarationPath?: string,
|
||||
declarationMapPath?: string,
|
||||
declarationMapText?: string
|
||||
declarationMapTextOrBuildInfoPath?: string,
|
||||
javascriptPath?: string | undefined,
|
||||
declarationPath?: string | undefined,
|
||||
buildInfoPath?: string | undefined,
|
||||
buildInfo?: BuildInfo,
|
||||
oldFileOfCurrentEmit?: boolean
|
||||
): InputFiles {
|
||||
const node = <InputFiles>createNode(SyntaxKind.InputFiles);
|
||||
if (!isString(javascriptTextOrReadFileText)) {
|
||||
@@ -2693,15 +2890,25 @@ namespace ts {
|
||||
const result = textGetter(path);
|
||||
return result !== undefined ? result : `/* Input file ${path} was missing */\r\n`;
|
||||
};
|
||||
let buildInfo: BuildInfo | false;
|
||||
const getAndCacheBuildInfo = (getText: () => string | undefined) => {
|
||||
if (buildInfo === undefined) {
|
||||
const result = getText();
|
||||
buildInfo = result !== undefined ? getBuildInfo(result) : false;
|
||||
}
|
||||
return buildInfo || undefined;
|
||||
};
|
||||
node.javascriptPath = declarationTextOrJavascriptPath;
|
||||
node.javascriptMapPath = javascriptMapPath;
|
||||
node.declarationPath = Debug.assertDefined(javascriptMapTextOrDeclarationPath);
|
||||
node.declarationMapPath = declarationMapPath;
|
||||
node.buildInfoPath = declarationMapTextOrBuildInfoPath;
|
||||
Object.defineProperties(node, {
|
||||
javascriptText: { get() { return definedTextGetter(declarationTextOrJavascriptPath); } },
|
||||
javascriptMapText: { get() { return textGetter(javascriptMapPath); } }, // TODO:: if there is inline sourceMap in jsFile, use that
|
||||
declarationText: { get() { return definedTextGetter(Debug.assertDefined(javascriptMapTextOrDeclarationPath)); } },
|
||||
declarationMapText: { get() { return textGetter(declarationMapPath); } } // TODO:: if there is inline sourceMap in dtsFile, use that
|
||||
declarationMapText: { get() { return textGetter(declarationMapPath); } }, // TODO:: if there is inline sourceMap in dtsFile, use that
|
||||
buildInfo: { get() { return getAndCacheBuildInfo(() => textGetter(declarationMapTextOrBuildInfoPath)); } }
|
||||
});
|
||||
}
|
||||
else {
|
||||
@@ -2710,7 +2917,12 @@ namespace ts {
|
||||
node.javascriptMapText = javascriptMapTextOrDeclarationPath;
|
||||
node.declarationText = declarationTextOrJavascriptPath;
|
||||
node.declarationMapPath = declarationMapPath;
|
||||
node.declarationMapText = declarationMapText;
|
||||
node.declarationMapText = declarationMapTextOrBuildInfoPath;
|
||||
node.javascriptPath = javascriptPath;
|
||||
node.declarationPath = declarationPath,
|
||||
node.buildInfoPath = buildInfoPath;
|
||||
node.buildInfo = buildInfo;
|
||||
node.oldFileOfCurrentEmit = oldFileOfCurrentEmit;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
@@ -3372,7 +3584,7 @@ namespace ts {
|
||||
return setEmitFlags(createIdentifier(name), EmitFlags.HelperName | EmitFlags.AdviseOnEmitNode);
|
||||
}
|
||||
|
||||
const valuesHelper: EmitHelper = {
|
||||
export const valuesHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:values",
|
||||
scoped: false,
|
||||
text: `
|
||||
@@ -3400,7 +3612,7 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
const readHelper: EmitHelper = {
|
||||
export const readHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:read",
|
||||
scoped: false,
|
||||
text: `
|
||||
@@ -3436,7 +3648,7 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
const spreadHelper: EmitHelper = {
|
||||
export const spreadHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:spread",
|
||||
scoped: false,
|
||||
text: `
|
||||
|
||||
@@ -437,6 +437,8 @@ namespace ts.moduleSpecifiers {
|
||||
case Extension.Jsx:
|
||||
case Extension.Json:
|
||||
return ext;
|
||||
case Extension.TsBuildInfo:
|
||||
return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported:: FileName:: ${fileName}`);
|
||||
default:
|
||||
return Debug.assertNever(ext);
|
||||
}
|
||||
|
||||
@@ -7762,6 +7762,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
type PragmaDiagnosticReporter = (pos: number, length: number, message: DiagnosticMessage) => void;
|
||||
|
||||
/*@internal*/
|
||||
|
||||
+69
-27
@@ -203,6 +203,7 @@ namespace ts {
|
||||
return compilerHost;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
interface CompilerHostLikeForCache {
|
||||
fileExists(fileName: string): boolean;
|
||||
readFile(fileName: string, encoding?: string): string | undefined;
|
||||
@@ -242,7 +243,8 @@ namespace ts {
|
||||
const key = toPath(fileName);
|
||||
const value = readFileCache.get(key);
|
||||
if (value !== undefined) return value !== false ? value : undefined; // could be .d.ts from output
|
||||
if (!fileExtensionIs(fileName, Extension.Json)) {
|
||||
// Cache json or buildInfo
|
||||
if (!fileExtensionIs(fileName, Extension.Json) && !isBuildInfoFile(fileName)) {
|
||||
return originalReadFile.call(host, fileName);
|
||||
}
|
||||
|
||||
@@ -276,7 +278,7 @@ namespace ts {
|
||||
fileExistsCache.delete(key);
|
||||
|
||||
const value = readFileCache.get(key);
|
||||
if (value && value !== data) {
|
||||
if (value !== undefined && value !== data) {
|
||||
readFileCache.delete(key);
|
||||
sourceFileCache.delete(key);
|
||||
}
|
||||
@@ -937,7 +939,8 @@ namespace ts {
|
||||
getProjectReferenceRedirect,
|
||||
getResolvedProjectReferenceToRedirect,
|
||||
getResolvedProjectReferenceByPath,
|
||||
forEachResolvedProjectReference
|
||||
forEachResolvedProjectReference,
|
||||
emitBuildInfo
|
||||
};
|
||||
|
||||
verifyCompilerOptions();
|
||||
@@ -1431,9 +1434,28 @@ namespace ts {
|
||||
},
|
||||
...(host.directoryExists ? { directoryExists: f => host.directoryExists!(f) } : {}),
|
||||
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(),
|
||||
getProgramBuildInfo: () => program.getProgramBuildInfo && program.getProgramBuildInfo()
|
||||
};
|
||||
}
|
||||
|
||||
function emitBuildInfo(writeFileCallback?: WriteFileCallback): EmitResult {
|
||||
Debug.assert(!options.out && !options.outFile);
|
||||
performance.mark("beforeEmit");
|
||||
const emitResult = emitFiles(
|
||||
notImplementedResolver,
|
||||
getEmitHost(writeFileCallback),
|
||||
/*targetSourceFile*/ undefined,
|
||||
/*emitOnlyDtsFiles*/ false,
|
||||
/*transformers*/ undefined,
|
||||
/*declaraitonTransformers*/ undefined,
|
||||
/*onlyBuildInfo*/ true
|
||||
);
|
||||
|
||||
performance.mark("afterEmit");
|
||||
performance.measure("Emit", "beforeEmit", "afterEmit");
|
||||
return emitResult;
|
||||
}
|
||||
|
||||
function getResolvedProjectReferences() {
|
||||
return resolvedProjectReferences;
|
||||
}
|
||||
@@ -1442,30 +1464,16 @@ namespace ts {
|
||||
return projectReferences;
|
||||
}
|
||||
|
||||
function getPrependNodes(): InputFiles[] {
|
||||
if (!projectReferences) {
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
const nodes: InputFiles[] = [];
|
||||
for (let i = 0; i < projectReferences.length; i++) {
|
||||
const ref = projectReferences[i];
|
||||
const resolvedRefOpts = resolvedProjectReferences![i]!.commandLine;
|
||||
if (ref.prepend && resolvedRefOpts && resolvedRefOpts.options) {
|
||||
const out = resolvedRefOpts.options.outFile || resolvedRefOpts.options.out;
|
||||
// Upstream project didn't have outFile set -- skip (error will have been issued earlier)
|
||||
if (!out) continue;
|
||||
|
||||
const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath } = getOutputPathsForBundle(resolvedRefOpts.options, /*forceDtsPaths*/ true);
|
||||
const node = createInputFiles(fileName => {
|
||||
const path = toPath(fileName);
|
||||
const sourceFile = getSourceFileByPath(path);
|
||||
return sourceFile ? sourceFile.text : filesByName.has(path) ? undefined : host.readFile(path);
|
||||
}, jsFilePath! , sourceMapFilePath, declarationFilePath! , declarationMapPath);
|
||||
nodes.push(node);
|
||||
function getPrependNodes() {
|
||||
return createPrependNodes(
|
||||
projectReferences,
|
||||
(_ref, index) => resolvedProjectReferences![index]!.commandLine,
|
||||
fileName => {
|
||||
const path = toPath(fileName);
|
||||
const sourceFile = getSourceFileByPath(path);
|
||||
return sourceFile ? sourceFile.text : filesByName.has(path) ? undefined : host.readFile(path);
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
);
|
||||
}
|
||||
|
||||
function isSourceFileFromExternalLibrary(file: SourceFile): boolean {
|
||||
@@ -1562,7 +1570,7 @@ namespace ts {
|
||||
const emitResult = emitFiles(
|
||||
emitResolver,
|
||||
getEmitHost(writeFileCallback),
|
||||
sourceFile!, // TODO: GH#18217
|
||||
sourceFile,
|
||||
emitOnlyDtsFiles,
|
||||
transformers,
|
||||
customTransformers && customTransformers.afterDeclarations
|
||||
@@ -2712,6 +2720,15 @@ namespace ts {
|
||||
if (options.declaration === false) {
|
||||
createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_declaration_emit, "declaration");
|
||||
}
|
||||
if (options.incremental === false) {
|
||||
createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_incremental_compilation, "declaration");
|
||||
}
|
||||
}
|
||||
|
||||
if (options.tsBuildInfoFile) {
|
||||
if (!isIncrementalCompilation(options)) {
|
||||
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "tsBuildInfoFile", "incremental", "composite");
|
||||
}
|
||||
}
|
||||
|
||||
verifyProjectReferences();
|
||||
@@ -2933,6 +2950,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function verifyProjectReferences() {
|
||||
const buildInfoPath = !options.noEmit && !options.suppressOutputPathCheck ? getOutputPathForBuildInfo(options) : undefined;
|
||||
forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, index, parent) => {
|
||||
const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index];
|
||||
const parentFile = parent && parent.sourceFile as JsonSourceFile;
|
||||
@@ -2959,6 +2977,10 @@ namespace ts {
|
||||
createDiagnosticForReference(parentFile, index, Diagnostics.Cannot_prepend_project_0_because_it_does_not_have_outFile_set, ref.path);
|
||||
}
|
||||
}
|
||||
if (!parent && buildInfoPath && buildInfoPath === getOutputPathForBuildInfo(options)) {
|
||||
createDiagnosticForReference(parentFile, index, Diagnostics.Cannot_write_file_0_because_it_will_overwrite_tsbuildinfo_file_generated_by_referenced_project_1, buildInfoPath, ref.path);
|
||||
hasEmitBlockingDiagnostics.set(toPath(buildInfoPath), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3109,6 +3131,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
interface CompilerHostLike {
|
||||
useCaseSensitiveFileNames(): boolean;
|
||||
getCurrentDirectory(): string;
|
||||
@@ -3140,6 +3163,25 @@ namespace ts {
|
||||
fileExists(fileName: string): boolean;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function createPrependNodes(projectReferences: ReadonlyArray<ProjectReference> | undefined, getCommandLine: (ref: ProjectReference, index: number) => ParsedCommandLine | undefined, readFile: (path: string) => string | undefined) {
|
||||
if (!projectReferences) return emptyArray;
|
||||
let nodes: InputFiles[] | undefined;
|
||||
for (let i = 0; i < projectReferences.length; i++) {
|
||||
const ref = projectReferences[i];
|
||||
const resolvedRefOpts = getCommandLine(ref, i);
|
||||
if (ref.prepend && resolvedRefOpts && resolvedRefOpts.options) {
|
||||
const out = resolvedRefOpts.options.outFile || resolvedRefOpts.options.out;
|
||||
// Upstream project didn't have outFile set -- skip (error will have been issued earlier)
|
||||
if (!out) continue;
|
||||
|
||||
const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(resolvedRefOpts.options, /*forceDtsPaths*/ true);
|
||||
const node = createInputFiles(readFile, jsFilePath!, sourceMapFilePath, declarationFilePath!, declarationMapPath, buildInfoPath);
|
||||
(nodes || (nodes = [])).push(node);
|
||||
}
|
||||
}
|
||||
return nodes || emptyArray;
|
||||
}
|
||||
/**
|
||||
* Returns the target config filename of a project reference.
|
||||
* Note: The file might not exist.
|
||||
|
||||
@@ -623,13 +623,15 @@ namespace ts {
|
||||
|
||||
const shebangTriviaRegex = /^#!.*/;
|
||||
|
||||
function isShebangTrivia(text: string, pos: number) {
|
||||
/*@internal*/
|
||||
export function isShebangTrivia(text: string, pos: number) {
|
||||
// Shebangs check must only be done at the start of the file
|
||||
Debug.assert(pos === 0);
|
||||
return shebangTriviaRegex.test(text);
|
||||
}
|
||||
|
||||
function scanShebangTrivia(text: string, pos: number) {
|
||||
/*@internal*/
|
||||
export function scanShebangTrivia(text: string, pos: number) {
|
||||
const shebang = shebangTriviaRegex.exec(text)![0];
|
||||
pos = pos + shebang.length;
|
||||
return pos;
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace ts {
|
||||
exit();
|
||||
}
|
||||
|
||||
function appendSourceMap(generatedLine: number, generatedCharacter: number, map: RawSourceMap, sourceMapPath: string) {
|
||||
function appendSourceMap(generatedLine: number, generatedCharacter: number, map: RawSourceMap, sourceMapPath: string, start?: LineAndCharacter, end?: LineAndCharacter) {
|
||||
Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack");
|
||||
Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative");
|
||||
enter();
|
||||
@@ -149,6 +149,17 @@ namespace ts {
|
||||
let nameIndexToNewNameIndexMap: number[] | undefined;
|
||||
const mappingIterator = decodeMappings(map.mappings);
|
||||
for (let { value: raw, done } = mappingIterator.next(); !done; { value: raw, done } = mappingIterator.next()) {
|
||||
if (end && (
|
||||
raw.generatedLine > end.line ||
|
||||
(raw.generatedLine === end.line && raw.generatedCharacter > end.character))) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (start && (
|
||||
raw.generatedLine < start.line ||
|
||||
(start.line === raw.generatedLine && raw.generatedCharacter < start.character))) {
|
||||
continue;
|
||||
}
|
||||
// Then reencode all the updated mappings into the overall map
|
||||
let newSourceIndex: number | undefined;
|
||||
let newSourceLine: number | undefined;
|
||||
@@ -178,8 +189,10 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
const newGeneratedLine = raw.generatedLine + generatedLine;
|
||||
const newGeneratedCharacter = raw.generatedLine === 0 ? raw.generatedCharacter + generatedCharacter : raw.generatedCharacter;
|
||||
const rawGeneratedLine = raw.generatedLine - (start ? start.line : 0);
|
||||
const newGeneratedLine = rawGeneratedLine + generatedLine;
|
||||
const rawGeneratedCharacter = start && start.line === raw.generatedLine ? raw.generatedCharacter - start.character : raw.generatedCharacter;
|
||||
const newGeneratedCharacter = rawGeneratedLine === 0 ? rawGeneratedCharacter + generatedCharacter : rawGeneratedCharacter;
|
||||
addMapping(newGeneratedLine, newGeneratedCharacter, newSourceIndex, newSourceLine, newSourceCharacter, newNameIndex);
|
||||
}
|
||||
exit();
|
||||
|
||||
@@ -469,6 +469,66 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
interface NodeBuffer extends Uint8Array {
|
||||
write(str: string, offset?: number, length?: number, encoding?: string): number;
|
||||
toString(encoding?: string, start?: number, end?: number): string;
|
||||
toJSON(): { type: "Buffer", data: any[] };
|
||||
equals(otherBuffer: Buffer): boolean;
|
||||
compare(otherBuffer: Buffer, targetStart?: number, targetEnd?: number, sourceStart?: number, sourceEnd?: number): number;
|
||||
copy(targetBuffer: Buffer, targetStart?: number, sourceStart?: number, sourceEnd?: number): number;
|
||||
slice(start?: number, end?: number): Buffer;
|
||||
writeUIntLE(value: number, offset: number, byteLength: number, noAssert?: boolean): number;
|
||||
writeUIntBE(value: number, offset: number, byteLength: number, noAssert?: boolean): number;
|
||||
writeIntLE(value: number, offset: number, byteLength: number, noAssert?: boolean): number;
|
||||
writeIntBE(value: number, offset: number, byteLength: number, noAssert?: boolean): number;
|
||||
readUIntLE(offset: number, byteLength: number, noAssert?: boolean): number;
|
||||
readUIntBE(offset: number, byteLength: number, noAssert?: boolean): number;
|
||||
readIntLE(offset: number, byteLength: number, noAssert?: boolean): number;
|
||||
readIntBE(offset: number, byteLength: number, noAssert?: boolean): number;
|
||||
readUInt8(offset: number, noAssert?: boolean): number;
|
||||
readUInt16LE(offset: number, noAssert?: boolean): number;
|
||||
readUInt16BE(offset: number, noAssert?: boolean): number;
|
||||
readUInt32LE(offset: number, noAssert?: boolean): number;
|
||||
readUInt32BE(offset: number, noAssert?: boolean): number;
|
||||
readInt8(offset: number, noAssert?: boolean): number;
|
||||
readInt16LE(offset: number, noAssert?: boolean): number;
|
||||
readInt16BE(offset: number, noAssert?: boolean): number;
|
||||
readInt32LE(offset: number, noAssert?: boolean): number;
|
||||
readInt32BE(offset: number, noAssert?: boolean): number;
|
||||
readFloatLE(offset: number, noAssert?: boolean): number;
|
||||
readFloatBE(offset: number, noAssert?: boolean): number;
|
||||
readDoubleLE(offset: number, noAssert?: boolean): number;
|
||||
readDoubleBE(offset: number, noAssert?: boolean): number;
|
||||
swap16(): Buffer;
|
||||
swap32(): Buffer;
|
||||
swap64(): Buffer;
|
||||
writeUInt8(value: number, offset: number, noAssert?: boolean): number;
|
||||
writeUInt16LE(value: number, offset: number, noAssert?: boolean): number;
|
||||
writeUInt16BE(value: number, offset: number, noAssert?: boolean): number;
|
||||
writeUInt32LE(value: number, offset: number, noAssert?: boolean): number;
|
||||
writeUInt32BE(value: number, offset: number, noAssert?: boolean): number;
|
||||
writeInt8(value: number, offset: number, noAssert?: boolean): number;
|
||||
writeInt16LE(value: number, offset: number, noAssert?: boolean): number;
|
||||
writeInt16BE(value: number, offset: number, noAssert?: boolean): number;
|
||||
writeInt32LE(value: number, offset: number, noAssert?: boolean): number;
|
||||
writeInt32BE(value: number, offset: number, noAssert?: boolean): number;
|
||||
writeFloatLE(value: number, offset: number, noAssert?: boolean): number;
|
||||
writeFloatBE(value: number, offset: number, noAssert?: boolean): number;
|
||||
writeDoubleLE(value: number, offset: number, noAssert?: boolean): number;
|
||||
writeDoubleBE(value: number, offset: number, noAssert?: boolean): number;
|
||||
fill(value: any, offset?: number, end?: number): this;
|
||||
indexOf(value: string | number | Buffer, byteOffset?: number, encoding?: string): number;
|
||||
lastIndexOf(value: string | number | Buffer, byteOffset?: number, encoding?: string): number;
|
||||
entries(): IterableIterator<[number, number]>;
|
||||
includes(value: string | number | Buffer, byteOffset?: number, encoding?: string): boolean;
|
||||
keys(): IterableIterator<number>;
|
||||
values(): IterableIterator<number>;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
interface Buffer extends NodeBuffer { }
|
||||
|
||||
// TODO: GH#18217 Methods on System are often used as if they are certainly defined
|
||||
export interface System {
|
||||
args: string[];
|
||||
|
||||
@@ -9,6 +9,15 @@ namespace ts {
|
||||
return result.diagnostics;
|
||||
}
|
||||
|
||||
export function isInternalDeclaration(node: Node, currentSourceFile: SourceFile) {
|
||||
const parseTreeNode = getParseTreeNode(node);
|
||||
const leadingCommentRanges = parseTreeNode && getLeadingCommentRangesOfNode(parseTreeNode, currentSourceFile);
|
||||
return !!forEach(leadingCommentRanges, range => {
|
||||
const comment = currentSourceFile.text.substring(range.pos, range.end);
|
||||
return stringContains(comment, "@internal");
|
||||
});
|
||||
}
|
||||
|
||||
const declarationEmitNodeBuilderFlags =
|
||||
NodeBuilderFlags.MultilineObjectLiterals |
|
||||
NodeBuilderFlags.WriteClassExpressionAsTypeLiteral |
|
||||
@@ -61,7 +70,7 @@ namespace ts {
|
||||
const { noResolve, stripInternal } = options;
|
||||
return transformRoot;
|
||||
|
||||
function recordTypeReferenceDirectivesIfNecessary(typeReferenceDirectives: string[] | undefined): void {
|
||||
function recordTypeReferenceDirectivesIfNecessary(typeReferenceDirectives: ReadonlyArray<string> | undefined): void {
|
||||
if (!typeReferenceDirectives) {
|
||||
return;
|
||||
}
|
||||
@@ -207,8 +216,14 @@ namespace ts {
|
||||
}
|
||||
), mapDefined(node.prepends, prepend => {
|
||||
if (prepend.kind === SyntaxKind.InputFiles) {
|
||||
return createUnparsedSourceFile(prepend, "dts");
|
||||
const sourceFile = createUnparsedSourceFile(prepend, "dts", stripInternal);
|
||||
hasNoDefaultLib = hasNoDefaultLib || !!sourceFile.hasNoDefaultLib;
|
||||
collectReferences(sourceFile, refs);
|
||||
recordTypeReferenceDirectivesIfNecessary(sourceFile.typeReferenceDirectives);
|
||||
collectLibs(sourceFile, libs);
|
||||
return sourceFile;
|
||||
}
|
||||
return prepend;
|
||||
}));
|
||||
bundle.syntheticFileReferences = [];
|
||||
bundle.syntheticTypeReferences = getFileReferencesForUsedTypeReferences();
|
||||
@@ -311,8 +326,8 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function collectReferences(sourceFile: SourceFile, ret: Map<SourceFile>) {
|
||||
if (noResolve || isSourceFileJS(sourceFile)) return ret;
|
||||
function collectReferences(sourceFile: SourceFile | UnparsedSource, ret: Map<SourceFile>) {
|
||||
if (noResolve || (!isUnparsedSource(sourceFile) && isSourceFileJS(sourceFile))) return ret;
|
||||
forEach(sourceFile.referencedFiles, f => {
|
||||
const elem = tryResolveScriptReference(host, sourceFile, f);
|
||||
if (elem) {
|
||||
@@ -322,7 +337,7 @@ namespace ts {
|
||||
return ret;
|
||||
}
|
||||
|
||||
function collectLibs(sourceFile: SourceFile, ret: Map<boolean>) {
|
||||
function collectLibs(sourceFile: SourceFile | UnparsedSource, ret: Map<boolean>) {
|
||||
forEach(sourceFile.libReferenceDirectives, ref => {
|
||||
const lib = host.getLibFileFromReference(ref);
|
||||
if (lib) {
|
||||
@@ -1222,19 +1237,8 @@ namespace ts {
|
||||
errorNameNode = undefined;
|
||||
}
|
||||
|
||||
function hasInternalAnnotation(range: CommentRange) {
|
||||
const comment = currentSourceFile.text.substring(range.pos, range.end);
|
||||
return stringContains(comment, "@internal");
|
||||
}
|
||||
|
||||
function shouldStripInternal(node: Node) {
|
||||
if (stripInternal && node) {
|
||||
const leadingCommentRanges = getLeadingCommentRangesOfNode(getParseTreeNode(node), currentSourceFile);
|
||||
if (forEach(leadingCommentRanges, hasInternalAnnotation)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return !!stripInternal && !!node && isInternalDeclaration(node, currentSourceFile);
|
||||
}
|
||||
|
||||
function isScopeMarker(node: Node) {
|
||||
|
||||
@@ -512,7 +512,7 @@ namespace ts {
|
||||
return name;
|
||||
}
|
||||
|
||||
const restHelper: EmitHelper = {
|
||||
export const restHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:rest",
|
||||
scoped: false,
|
||||
text: `
|
||||
|
||||
@@ -4338,7 +4338,7 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
const extendsHelper: EmitHelper = {
|
||||
export const extendsHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:extends",
|
||||
scoped: false,
|
||||
priority: 0,
|
||||
@@ -4359,7 +4359,7 @@ namespace ts {
|
||||
})();`
|
||||
};
|
||||
|
||||
const templateObjectHelper: EmitHelper = {
|
||||
export const templateObjectHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:makeTemplateObject",
|
||||
scoped: false,
|
||||
priority: 0,
|
||||
|
||||
@@ -750,7 +750,7 @@ namespace ts {
|
||||
NodeFlags.Const));
|
||||
}
|
||||
|
||||
const awaiterHelper: EmitHelper = {
|
||||
export const awaiterHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:awaiter",
|
||||
scoped: false,
|
||||
priority: 5,
|
||||
|
||||
@@ -916,7 +916,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
const assignHelper: EmitHelper = {
|
||||
export const assignHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:assign",
|
||||
scoped: false,
|
||||
priority: 1,
|
||||
@@ -948,7 +948,7 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
const awaitHelper: EmitHelper = {
|
||||
export const awaitHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:await",
|
||||
scoped: false,
|
||||
text: `
|
||||
@@ -960,7 +960,7 @@ namespace ts {
|
||||
return createCall(getHelperName("__await"), /*typeArguments*/ undefined, [expression]);
|
||||
}
|
||||
|
||||
const asyncGeneratorHelper: EmitHelper = {
|
||||
export const asyncGeneratorHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:asyncGenerator",
|
||||
scoped: false,
|
||||
text: `
|
||||
@@ -995,7 +995,7 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
const asyncDelegator: EmitHelper = {
|
||||
export const asyncDelegator: UnscopedEmitHelper = {
|
||||
name: "typescript:asyncDelegator",
|
||||
scoped: false,
|
||||
text: `
|
||||
@@ -1019,7 +1019,7 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
const asyncValues: EmitHelper = {
|
||||
export const asyncValues: UnscopedEmitHelper = {
|
||||
name: "typescript:asyncValues",
|
||||
scoped: false,
|
||||
text: `
|
||||
|
||||
@@ -3240,7 +3240,7 @@ namespace ts {
|
||||
// entering a finally block.
|
||||
//
|
||||
// For examples of how these are used, see the comments in ./transformers/generators.ts
|
||||
const generatorHelper: EmitHelper = {
|
||||
export const generatorHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:generator",
|
||||
scoped: false,
|
||||
priority: 6,
|
||||
|
||||
@@ -1806,7 +1806,7 @@ namespace ts {
|
||||
};
|
||||
|
||||
// emit helper for `import * as Name from "foo"`
|
||||
const importStarHelper: EmitHelper = {
|
||||
export const importStarHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:commonjsimportstar",
|
||||
scoped: false,
|
||||
text: `
|
||||
@@ -1820,7 +1820,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||
};
|
||||
|
||||
// emit helper for `import Name from "foo"`
|
||||
const importDefaultHelper: EmitHelper = {
|
||||
export const importDefaultHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:commonjsimportdefault",
|
||||
scoped: false,
|
||||
text: `
|
||||
|
||||
@@ -3587,7 +3587,7 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
const decorateHelper: EmitHelper = {
|
||||
export const decorateHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:decorate",
|
||||
scoped: false,
|
||||
priority: 2,
|
||||
@@ -3612,7 +3612,7 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
const metadataHelper: EmitHelper = {
|
||||
export const metadataHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:metadata",
|
||||
scoped: false,
|
||||
priority: 3,
|
||||
@@ -3637,7 +3637,7 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
const paramHelper: EmitHelper = {
|
||||
export const paramHelper: UnscopedEmitHelper = {
|
||||
name: "typescript:param",
|
||||
scoped: false,
|
||||
priority: 4,
|
||||
|
||||
+184
-73
@@ -67,12 +67,19 @@ namespace ts {
|
||||
* This means we can Pseudo-build (just touch timestamps), as if we had actually built this project.
|
||||
*/
|
||||
UpToDateWithUpstreamTypes,
|
||||
/**
|
||||
* The project appears out of date because its upstream inputs are newer than its outputs,
|
||||
* but all of its outputs are actually newer than the previous identical outputs of its (.d.ts) inputs.
|
||||
* This means we can Pseudo-build (just manipulate outputs), as if we had actually built this project.
|
||||
*/
|
||||
OutOfDateWithPrepend,
|
||||
OutputMissing,
|
||||
OutOfDateWithSelf,
|
||||
OutOfDateWithUpstream,
|
||||
UpstreamOutOfDate,
|
||||
UpstreamBlocked,
|
||||
ComputingUpstream,
|
||||
TsVersionOutputOfDate,
|
||||
|
||||
/**
|
||||
* Projects with no outputs (i.e. "solution" files)
|
||||
@@ -83,12 +90,14 @@ namespace ts {
|
||||
export type UpToDateStatus =
|
||||
| Status.Unbuildable
|
||||
| Status.UpToDate
|
||||
| Status.OutOfDateWithPrepend
|
||||
| Status.OutputMissing
|
||||
| Status.OutOfDateWithSelf
|
||||
| Status.OutOfDateWithUpstream
|
||||
| Status.UpstreamOutOfDate
|
||||
| Status.UpstreamBlocked
|
||||
| Status.ComputingUpstream
|
||||
| Status.TsVersionOutOfDate
|
||||
| Status.ContainerOnly;
|
||||
|
||||
export namespace Status {
|
||||
@@ -122,6 +131,15 @@ namespace ts {
|
||||
oldestOutputFileName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The project is up to date with respect to its inputs except for prepend output changed (no declaration file change in prepend).
|
||||
*/
|
||||
export interface OutOfDateWithPrepend {
|
||||
type: UpToDateStatusType.OutOfDateWithPrepend;
|
||||
outOfDateOutputFileName: string;
|
||||
newerProjectName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* One or more of the outputs of the project does not exist.
|
||||
*/
|
||||
@@ -165,6 +183,11 @@ namespace ts {
|
||||
type: UpToDateStatusType.ComputingUpstream;
|
||||
}
|
||||
|
||||
export interface TsVersionOutOfDate {
|
||||
type: UpToDateStatusType.TsVersionOutputOfDate;
|
||||
version: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* One or more of the project's outputs is older than the newest output of
|
||||
* an upstream project.
|
||||
@@ -289,24 +312,18 @@ namespace ts {
|
||||
return outputs;
|
||||
}
|
||||
|
||||
function getOutFileOutputs(project: ParsedCommandLine): ReadonlyArray<string> {
|
||||
const out = project.options.outFile || project.options.out;
|
||||
if (!out) {
|
||||
return Debug.fail("outFile must be set");
|
||||
}
|
||||
const outputs: string[] = [];
|
||||
outputs.push(out);
|
||||
if (project.options.sourceMap) {
|
||||
outputs.push(`${out}.map`);
|
||||
}
|
||||
if (getEmitDeclarations(project.options)) {
|
||||
const dts = changeExtension(out, Extension.Dts);
|
||||
outputs.push(dts);
|
||||
if (project.options.declarationMap) {
|
||||
outputs.push(`${dts}.map`);
|
||||
}
|
||||
}
|
||||
return outputs;
|
||||
function getOutFileOutputs(project: ParsedCommandLine, ignoreBuildInfo?: boolean): ReadonlyArray<string> {
|
||||
Debug.assert(!!project.options.outFile || !!project.options.out, "outFile must be set");
|
||||
const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(project.options, /*forceDtsPaths*/ false);
|
||||
|
||||
let outputs: string[] | undefined = [];
|
||||
const addOutput = (path: string | undefined) => path && (outputs || (outputs = [])).push(path);
|
||||
addOutput(jsFilePath);
|
||||
addOutput(sourceMapFilePath);
|
||||
addOutput(declarationFilePath);
|
||||
addOutput(declarationMapPath);
|
||||
if (!ignoreBuildInfo) addOutput(buildInfoPath);
|
||||
return outputs || emptyArray;
|
||||
}
|
||||
|
||||
function rootDirOfOptions(opts: CompilerOptions, configFileName: string) {
|
||||
@@ -374,7 +391,7 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
function createSolutionBuilderHostBase<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) {
|
||||
function createSolutionBuilderHostBase<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) {
|
||||
const host = createProgramHost(system, createProgram) as SolutionBuilderHostBase<T>;
|
||||
host.getModifiedTime = system.getModifiedTime ? path => system.getModifiedTime!(path) : () => undefined;
|
||||
host.setModifiedTime = system.setModifiedTime ? (path, date) => system.setModifiedTime!(path, date) : noop;
|
||||
@@ -385,13 +402,13 @@ namespace ts {
|
||||
}
|
||||
|
||||
export function createSolutionBuilderHost<T extends BuilderProgram = BuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) {
|
||||
const host = createSolutionBuilderHostBase(system, createProgram || createAbstractBuilder as any as CreateProgram<T>, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost<T>;
|
||||
const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost<T>;
|
||||
host.reportErrorSummary = reportErrorSummary;
|
||||
return host;
|
||||
}
|
||||
|
||||
export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = SemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) {
|
||||
const host = createSolutionBuilderHostBase(system, createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost<T>;
|
||||
const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost<T>;
|
||||
const watchHost = createWatchHost(system, reportWatchStatus);
|
||||
copyProperties(host, watchHost);
|
||||
return host;
|
||||
@@ -436,7 +453,15 @@ namespace ts {
|
||||
const compilerHost = createCompilerHostFromProgramHost(host, () => projectCompilerOptions);
|
||||
const originalGetSourceFile = compilerHost.getSourceFile;
|
||||
const computeHash = host.createHash || generateDjb2Hash;
|
||||
updateGetSourceFile();
|
||||
compilerHost.getSourceFile = (...args) => {
|
||||
const result = originalGetSourceFile.call(compilerHost, ...args);
|
||||
if (result) {
|
||||
result.version = computeHash.call(host, result.text);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const buildInfoChecked = createFileMap<true>(toPath);
|
||||
|
||||
// Watch state
|
||||
const builderPrograms = createFileMap<T>(toPath);
|
||||
@@ -483,6 +508,7 @@ namespace ts {
|
||||
projectStatus.clear();
|
||||
missingRoots.clear();
|
||||
globalDependencyGraph = undefined;
|
||||
buildInfoChecked.clear();
|
||||
|
||||
diagnostics.clear();
|
||||
projectPendingBuild.clear();
|
||||
@@ -498,26 +524,6 @@ namespace ts {
|
||||
clearMap(allWatchedInputFiles, inputFileWatches => clearMap(inputFileWatches, closeFileWatcher));
|
||||
clearMap(allWatchedConfigFiles, closeFileWatcher);
|
||||
builderPrograms.clear();
|
||||
updateGetSourceFile();
|
||||
}
|
||||
|
||||
function updateGetSourceFile() {
|
||||
if (options.watch) {
|
||||
if (compilerHost.getSourceFile === originalGetSourceFile) {
|
||||
compilerHost.getSourceFile = (...args) => {
|
||||
const result = originalGetSourceFile.call(compilerHost, ...args);
|
||||
if (result && options.watch) {
|
||||
result.version = computeHash.call(host, result.text);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (compilerHost.getSourceFile !== originalGetSourceFile) {
|
||||
compilerHost.getSourceFile = originalGetSourceFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isParsedCommandLine(entry: ConfigFileCacheEntry): entry is ParsedCommandLine {
|
||||
@@ -806,27 +812,30 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
// If the upstream project's newest file is older than our oldest output, we
|
||||
// can't be out of date because of it
|
||||
if (refStatus.newestInputFileTime && refStatus.newestInputFileTime <= oldestOutputFileTime) {
|
||||
continue;
|
||||
}
|
||||
// Check oldest output file name only if there is no missing output file name
|
||||
if (!missingOutputFileName) {
|
||||
// If the upstream project's newest file is older than our oldest output, we
|
||||
// can't be out of date because of it
|
||||
if (refStatus.newestInputFileTime && refStatus.newestInputFileTime <= oldestOutputFileTime) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the upstream project has only change .d.ts files, and we've built
|
||||
// *after* those files, then we're "psuedo up to date" and eligible for a fast rebuild
|
||||
if (refStatus.newestDeclarationFileContentChangedTime && refStatus.newestDeclarationFileContentChangedTime <= oldestOutputFileTime) {
|
||||
pseudoUpToDate = true;
|
||||
upstreamChangedProject = ref.path;
|
||||
continue;
|
||||
}
|
||||
// If the upstream project has only change .d.ts files, and we've built
|
||||
// *after* those files, then we're "psuedo up to date" and eligible for a fast rebuild
|
||||
if (refStatus.newestDeclarationFileContentChangedTime && refStatus.newestDeclarationFileContentChangedTime <= oldestOutputFileTime) {
|
||||
pseudoUpToDate = true;
|
||||
upstreamChangedProject = ref.path;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have an output older than an upstream output - we are out of date
|
||||
Debug.assert(oldestOutputFileName !== undefined, "Should have an oldest output filename here");
|
||||
return {
|
||||
type: UpToDateStatusType.OutOfDateWithUpstream,
|
||||
outOfDateOutputFileName: oldestOutputFileName,
|
||||
newerProjectName: ref.path
|
||||
};
|
||||
// We have an output older than an upstream output - we are out of date
|
||||
Debug.assert(oldestOutputFileName !== undefined, "Should have an oldest output filename here");
|
||||
return {
|
||||
type: UpToDateStatusType.OutOfDateWithUpstream,
|
||||
outOfDateOutputFileName: oldestOutputFileName,
|
||||
newerProjectName: ref.path
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -845,9 +854,24 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
if (!buildInfoChecked.hasKey(project.options.configFilePath as ResolvedConfigFileName)) {
|
||||
buildInfoChecked.setValue(project.options.configFilePath as ResolvedConfigFileName, true);
|
||||
const buildInfoPath = getOutputPathForBuildInfo(project.options);
|
||||
if (buildInfoPath) {
|
||||
const value = readFileWithCache(buildInfoPath);
|
||||
const buildInfo = value && getBuildInfo(value);
|
||||
if (buildInfo && buildInfo.version !== version) {
|
||||
return {
|
||||
type: UpToDateStatusType.TsVersionOutputOfDate,
|
||||
version: buildInfo.version
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (usesPrepend && pseudoUpToDate) {
|
||||
return {
|
||||
type: UpToDateStatusType.OutOfDateWithUpstream,
|
||||
type: UpToDateStatusType.OutOfDateWithPrepend,
|
||||
outOfDateOutputFileName: oldestOutputFileName,
|
||||
newerProjectName: upstreamChangedProject!
|
||||
};
|
||||
@@ -996,7 +1020,9 @@ namespace ts {
|
||||
return;
|
||||
}
|
||||
|
||||
const buildResult = buildSingleProject(resolved);
|
||||
const buildResult = status.type === UpToDateStatusType.OutOfDateWithPrepend ?
|
||||
updateBundle(resolved) : // Fake that files have been built by manipulating prepend and existing output
|
||||
buildSingleProject(resolved); // Actual build
|
||||
if (buildResult & BuildResultFlags.AnyErrors) return;
|
||||
|
||||
const { referencingProjectsMap, buildQueue } = getGlobalDependencyGraph();
|
||||
@@ -1012,17 +1038,26 @@ namespace ts {
|
||||
// If declaration output is changed, build the project
|
||||
// otherwise mark the project UpToDateWithUpstreamTypes so it updates output time stamps
|
||||
const status = projectStatus.getValue(project);
|
||||
if (prepend || !(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) {
|
||||
if (status && (status.type === UpToDateStatusType.UpToDate || status.type === UpToDateStatusType.UpToDateWithUpstreamTypes)) {
|
||||
if (!(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) {
|
||||
if (status && (status.type === UpToDateStatusType.UpToDate || status.type === UpToDateStatusType.UpToDateWithUpstreamTypes || status.type === UpToDateStatusType.OutOfDateWithPrepend)) {
|
||||
projectStatus.setValue(project, {
|
||||
type: UpToDateStatusType.OutOfDateWithUpstream,
|
||||
outOfDateOutputFileName: status.oldestOutputFileName,
|
||||
outOfDateOutputFileName: status.type === UpToDateStatusType.OutOfDateWithPrepend ? status.outOfDateOutputFileName : status.oldestOutputFileName,
|
||||
newerProjectName: resolved
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (status && status.type === UpToDateStatusType.UpToDate) {
|
||||
status.type = UpToDateStatusType.UpToDateWithUpstreamTypes;
|
||||
if (prepend) {
|
||||
projectStatus.setValue(project, {
|
||||
type: UpToDateStatusType.OutOfDateWithPrepend,
|
||||
outOfDateOutputFileName: status.oldestOutputFileName,
|
||||
newerProjectName: resolved
|
||||
});
|
||||
}
|
||||
else {
|
||||
status.type = UpToDateStatusType.UpToDateWithUpstreamTypes;
|
||||
}
|
||||
}
|
||||
addProjToQueue(project);
|
||||
}
|
||||
@@ -1083,8 +1118,7 @@ namespace ts {
|
||||
|
||||
if (options.verbose) reportStatus(Diagnostics.Building_project_0, proj);
|
||||
|
||||
let resultFlags = BuildResultFlags.None;
|
||||
resultFlags |= BuildResultFlags.DeclarationOutputUnchanged;
|
||||
let resultFlags = BuildResultFlags.DeclarationOutputUnchanged;
|
||||
|
||||
const configFile = parseConfigFile(proj);
|
||||
if (!configFile) {
|
||||
@@ -1106,11 +1140,10 @@ namespace ts {
|
||||
configFile.fileNames,
|
||||
configFile.options,
|
||||
compilerHost,
|
||||
builderPrograms.getValue(proj),
|
||||
getOldProgram(proj, configFile),
|
||||
configFile.errors,
|
||||
configFile.projectReferences
|
||||
);
|
||||
projectCompilerOptions = baseCompilerOptions;
|
||||
|
||||
// Don't emit anything in the presence of syntactic errors or options diagnostics
|
||||
const syntaxDiagnostics = [
|
||||
@@ -1182,6 +1215,7 @@ namespace ts {
|
||||
diagnostics.removeKey(proj);
|
||||
projectStatus.setValue(proj, status);
|
||||
afterProgramCreate(proj, program);
|
||||
projectCompilerOptions = baseCompilerOptions;
|
||||
return resultFlags;
|
||||
|
||||
function buildErrors(diagnostics: ReadonlyArray<Diagnostic>, errorFlags: BuildResultFlags, errorType: string) {
|
||||
@@ -1189,6 +1223,7 @@ namespace ts {
|
||||
reportAndStoreErrors(proj, diagnostics);
|
||||
projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: `${errorType} errors` });
|
||||
afterProgramCreate(proj, program);
|
||||
projectCompilerOptions = baseCompilerOptions;
|
||||
return resultFlags;
|
||||
}
|
||||
}
|
||||
@@ -1203,9 +1238,72 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function getOldProgram(proj: ResolvedConfigFileName, parsed: ParsedCommandLine) {
|
||||
const value = builderPrograms.getValue(proj);
|
||||
if (value) return value;
|
||||
const buildInfoPath = getOutputPathForBuildInfo(parsed.options);
|
||||
if (!buildInfoPath) return undefined;
|
||||
const content = readFileWithCache(buildInfoPath);
|
||||
if (!content) return undefined;
|
||||
const buildInfo = getBuildInfo(content);
|
||||
if (buildInfo.version !== version) return undefined;
|
||||
return buildInfo.program && createBuildProgramUsingProgramBuildInfo(buildInfo.program) as any as T;
|
||||
}
|
||||
|
||||
function updateBundle(proj: ResolvedConfigFileName): BuildResultFlags {
|
||||
if (options.dry) {
|
||||
reportStatus(Diagnostics.A_non_dry_build_would_update_output_of_project_0, proj);
|
||||
return BuildResultFlags.Success;
|
||||
}
|
||||
|
||||
if (options.verbose) reportStatus(Diagnostics.Updating_output_of_project_0, proj);
|
||||
|
||||
// Update js, and source map
|
||||
const config = Debug.assertDefined(parseConfigFile(proj));
|
||||
projectCompilerOptions = config.options;
|
||||
const outputFiles = emitUsingBuildInfo(
|
||||
config,
|
||||
compilerHost,
|
||||
ref => parseConfigFile(resolveProjectName(ref.path)));
|
||||
if (isString(outputFiles)) {
|
||||
reportStatus(Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, proj, relName(outputFiles));
|
||||
return buildSingleProject(proj);
|
||||
}
|
||||
|
||||
// Actual Emit
|
||||
Debug.assert(!!outputFiles.length);
|
||||
const emitterDiagnostics = createDiagnosticCollection();
|
||||
const emittedOutputs = createFileMap<true>(toPath as ToPath);
|
||||
outputFiles.forEach(({ name, text, writeByteOrderMark }) => {
|
||||
emittedOutputs.setValue(name, true);
|
||||
writeFile(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark);
|
||||
});
|
||||
const emitDiagnostics = emitterDiagnostics.getDiagnostics();
|
||||
if (emitDiagnostics.length) {
|
||||
reportAndStoreErrors(proj, emitDiagnostics);
|
||||
projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Emit errors" });
|
||||
projectCompilerOptions = baseCompilerOptions;
|
||||
return BuildResultFlags.DeclarationOutputUnchanged | BuildResultFlags.EmitErrors;
|
||||
}
|
||||
|
||||
// Update timestamps for dts
|
||||
const newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(config, minimumDate, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs);
|
||||
|
||||
const status: UpToDateStatus = {
|
||||
type: UpToDateStatusType.UpToDate,
|
||||
newestDeclarationFileContentChangedTime,
|
||||
oldestOutputFileName: outputFiles[0].name
|
||||
};
|
||||
|
||||
diagnostics.removeKey(proj);
|
||||
projectStatus.setValue(proj, status);
|
||||
projectCompilerOptions = baseCompilerOptions;
|
||||
return BuildResultFlags.DeclarationOutputUnchanged;
|
||||
}
|
||||
|
||||
function updateOutputTimestamps(proj: ParsedCommandLine) {
|
||||
if (options.dry) {
|
||||
return reportStatus(Diagnostics.A_non_dry_build_would_build_project_0, proj.options.configFilePath!);
|
||||
return reportStatus(Diagnostics.A_non_dry_build_would_update_timestamps_for_output_of_project_0, proj.options.configFilePath!);
|
||||
}
|
||||
const priorNewestUpdateTime = updateOutputTimestampsWorker(proj, minimumDate, Diagnostics.Updating_output_timestamps_of_project_0);
|
||||
projectStatus.setValue(proj.options.configFilePath as ResolvedConfigFilePath, { type: UpToDateStatusType.UpToDate, newestDeclarationFileContentChangedTime: priorNewestUpdateTime } as UpToDateStatus);
|
||||
@@ -1340,7 +1438,9 @@ namespace ts {
|
||||
continue;
|
||||
}
|
||||
|
||||
const buildResult = buildSingleProject(next);
|
||||
const buildResult = status.type === UpToDateStatusType.OutOfDateWithPrepend && !options.force ?
|
||||
updateBundle(next) : // Fake that files have been built by manipulating prepend and existing output
|
||||
buildSingleProject(next); // Actual build
|
||||
anyFailed = anyFailed || !!(buildResult & BuildResultFlags.AnyErrors);
|
||||
}
|
||||
reportErrorSummary();
|
||||
@@ -1407,6 +1507,8 @@ namespace ts {
|
||||
for (const inputFile of project.fileNames) {
|
||||
outputs.push(...getOutputFileNames(inputFile, project));
|
||||
}
|
||||
const buildInfoPath = getOutputPathForBuildInfo(project.options);
|
||||
if (buildInfoPath) outputs.push(buildInfoPath);
|
||||
return outputs;
|
||||
}
|
||||
}
|
||||
@@ -1450,6 +1552,10 @@ namespace ts {
|
||||
}
|
||||
// Don't report anything for "up to date because it was already built" -- too verbose
|
||||
break;
|
||||
case UpToDateStatusType.OutOfDateWithPrepend:
|
||||
return formatMessage(Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed,
|
||||
relName(configFileName),
|
||||
relName(status.newerProjectName));
|
||||
case UpToDateStatusType.UpToDateWithUpstreamTypes:
|
||||
return formatMessage(Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies,
|
||||
relName(configFileName));
|
||||
@@ -1465,6 +1571,11 @@ namespace ts {
|
||||
return formatMessage(Diagnostics.Failed_to_parse_file_0_Colon_1,
|
||||
relName(configFileName),
|
||||
status.reason);
|
||||
case UpToDateStatusType.TsVersionOutputOfDate:
|
||||
return formatMessage(Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2,
|
||||
relName(configFileName),
|
||||
status.version,
|
||||
version);
|
||||
case UpToDateStatusType.ContainerOnly:
|
||||
// Don't report status on "solution" projects
|
||||
case UpToDateStatusType.ComputingUpstream:
|
||||
|
||||
+186
-18
@@ -428,6 +428,13 @@ namespace ts {
|
||||
|
||||
// Enum
|
||||
EnumMember,
|
||||
// Unparsed
|
||||
UnparsedPrologue,
|
||||
UnparsedPrepend,
|
||||
UnparsedText,
|
||||
UnparsedInternalText,
|
||||
UnparsedSyntheticReference,
|
||||
|
||||
// Top-level nodes
|
||||
SourceFile,
|
||||
Bundle,
|
||||
@@ -2775,14 +2782,66 @@ namespace ts {
|
||||
declarationText: string;
|
||||
declarationMapPath?: string;
|
||||
declarationMapText?: string;
|
||||
/*@internal*/ buildInfoPath?: string;
|
||||
/*@internal*/ buildInfo?: BuildInfo;
|
||||
/*@internal*/ oldFileOfCurrentEmit?: boolean;
|
||||
}
|
||||
|
||||
export interface UnparsedSource extends Node {
|
||||
kind: SyntaxKind.UnparsedSource;
|
||||
fileName?: string;
|
||||
fileName: string;
|
||||
text: string;
|
||||
prologues: ReadonlyArray<UnparsedPrologue>;
|
||||
helpers: ReadonlyArray<UnscopedEmitHelper> | undefined;
|
||||
|
||||
// References and noDefaultLibAre Dts only
|
||||
referencedFiles: ReadonlyArray<FileReference>;
|
||||
typeReferenceDirectives: ReadonlyArray<string> | undefined;
|
||||
libReferenceDirectives: ReadonlyArray<FileReference>;
|
||||
hasNoDefaultLib?: boolean;
|
||||
|
||||
sourceMapPath?: string;
|
||||
sourceMapText?: string;
|
||||
syntheticReferences?: ReadonlyArray<UnparsedSyntheticReference>;
|
||||
texts: ReadonlyArray<UnparsedSourceText>;
|
||||
/*@internal*/ oldFileOfCurrentEmit?: boolean;
|
||||
/*@internal*/ parsedSourceMap?: RawSourceMap | false | undefined;
|
||||
// Adding this to satisfy services, fix later
|
||||
/*@internal*/
|
||||
getLineAndCharacterOfPosition(pos: number): LineAndCharacter;
|
||||
}
|
||||
|
||||
export type UnparsedSourceText = UnparsedPrepend | UnparsedTextLike;
|
||||
export type UnparsedNode = UnparsedPrologue | UnparsedSourceText | UnparsedSyntheticReference;
|
||||
|
||||
export interface UnparsedSection extends Node {
|
||||
kind: SyntaxKind;
|
||||
data?: string;
|
||||
parent: UnparsedSource;
|
||||
}
|
||||
|
||||
export interface UnparsedPrologue extends UnparsedSection {
|
||||
kind: SyntaxKind.UnparsedPrologue;
|
||||
data: string;
|
||||
parent: UnparsedSource;
|
||||
}
|
||||
|
||||
export interface UnparsedPrepend extends UnparsedSection {
|
||||
kind: SyntaxKind.UnparsedPrepend;
|
||||
data: string;
|
||||
parent: UnparsedSource;
|
||||
texts: ReadonlyArray<UnparsedTextLike>;
|
||||
}
|
||||
|
||||
export interface UnparsedTextLike extends UnparsedSection {
|
||||
kind: SyntaxKind.UnparsedText | SyntaxKind.UnparsedInternalText;
|
||||
parent: UnparsedSource;
|
||||
}
|
||||
|
||||
export interface UnparsedSyntheticReference extends UnparsedSection {
|
||||
kind: SyntaxKind.UnparsedSyntheticReference;
|
||||
parent: UnparsedSource;
|
||||
/*@internal*/ section: BundleFileHasNoDefaultLib | BundleFileReference;
|
||||
}
|
||||
|
||||
export interface JsonSourceFile extends SourceFile {
|
||||
@@ -2935,6 +2994,8 @@ namespace ts {
|
||||
/*@internal*/ getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
|
||||
/*@internal*/ forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined;
|
||||
/*@internal*/ getResolvedProjectReferenceByPath(projectReferencePath: Path): ResolvedProjectReference | undefined;
|
||||
/*@internal*/ getProgramBuildInfo?(): ProgramBuildInfo | undefined;
|
||||
/*@internal*/ emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
@@ -4587,6 +4648,8 @@ namespace ts {
|
||||
reactNamespace?: string;
|
||||
jsxFactory?: string;
|
||||
composite?: boolean;
|
||||
incremental?: boolean;
|
||||
tsBuildInfoFile?: string;
|
||||
removeComments?: boolean;
|
||||
rootDir?: string;
|
||||
rootDirs?: string[];
|
||||
@@ -4997,7 +5060,8 @@ namespace ts {
|
||||
Dts = ".d.ts",
|
||||
Js = ".js",
|
||||
Jsx = ".jsx",
|
||||
Json = ".json"
|
||||
Json = ".json",
|
||||
TsBuildInfo = ".tsbuildinfo"
|
||||
}
|
||||
|
||||
export interface ResolvedModuleWithFailedLookupLocations {
|
||||
@@ -5203,6 +5267,11 @@ namespace ts {
|
||||
readonly priority?: number; // Helpers with a higher priority are emitted earlier than other helpers on the node.
|
||||
}
|
||||
|
||||
export interface UnscopedEmitHelper extends EmitHelper {
|
||||
readonly scoped: false; // Indicates whether the helper MUST be emitted in the current scope.
|
||||
readonly text: string; // ES3-compatible raw script text, or a function yielding such a string
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export type UniqueNameHandler = (baseName: string, checkFn?: (name: string) => boolean, optimistic?: boolean) => string;
|
||||
|
||||
@@ -5275,9 +5344,10 @@ namespace ts {
|
||||
|
||||
isEmitBlocked(emitFileName: string): boolean;
|
||||
|
||||
getPrependNodes(): ReadonlyArray<InputFiles>;
|
||||
getPrependNodes(): ReadonlyArray<InputFiles | UnparsedSource>;
|
||||
|
||||
writeFile: WriteFileCallback;
|
||||
getProgramBuildInfo(): ProgramBuildInfo | undefined;
|
||||
}
|
||||
|
||||
export interface TransformationContext {
|
||||
@@ -5428,23 +5498,116 @@ namespace ts {
|
||||
/*@internal*/ writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined, writer: EmitTextWriter): void;
|
||||
/*@internal*/ writeList<T extends Node>(format: ListFormat, list: NodeArray<T> | undefined, sourceFile: SourceFile | undefined, writer: EmitTextWriter): void;
|
||||
/*@internal*/ writeFile(sourceFile: SourceFile, writer: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined): void;
|
||||
/*@internal*/ writeBundle(bundle: Bundle, info: BundleInfo | undefined, writer: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined): void;
|
||||
/*@internal*/ writeBundle(bundle: Bundle, writer: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined): void;
|
||||
/*@internal*/ bundleFileInfo?: BundleFileInfo;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export const enum BundleFileSectionKind {
|
||||
Prologue = "prologue",
|
||||
EmitHelpers = "emitHelpers",
|
||||
NoDefaultLib = "no-default-lib",
|
||||
Reference = "reference",
|
||||
Type = "type",
|
||||
Lib = "lib",
|
||||
Prepend = "prepend",
|
||||
Text = "text",
|
||||
Internal = "internal",
|
||||
// comments?
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export interface BundleFileSectionBase extends TextRange {
|
||||
kind: BundleFileSectionKind;
|
||||
data?: string;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export interface BundleFilePrologue extends BundleFileSectionBase {
|
||||
kind: BundleFileSectionKind.Prologue;
|
||||
data: string;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export interface BundleFileEmitHelpers extends BundleFileSectionBase {
|
||||
kind: BundleFileSectionKind.EmitHelpers;
|
||||
data: string;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export interface BundleFileHasNoDefaultLib extends BundleFileSectionBase {
|
||||
kind: BundleFileSectionKind.NoDefaultLib;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export interface BundleFileReference extends BundleFileSectionBase {
|
||||
kind: BundleFileSectionKind.Reference | BundleFileSectionKind.Type | BundleFileSectionKind.Lib;
|
||||
data: string;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export interface BundleFilePrepend extends BundleFileSectionBase {
|
||||
kind: BundleFileSectionKind.Prepend;
|
||||
data: string;
|
||||
texts: BundleFileTextLike[];
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export type BundleFileTextLikeKind = BundleFileSectionKind.Text | BundleFileSectionKind.Internal;
|
||||
|
||||
/*@internal*/
|
||||
export interface BundleFileTextLike extends BundleFileSectionBase {
|
||||
kind: BundleFileTextLikeKind;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export type BundleFileSection = BundleFilePrologue | BundleFileEmitHelpers |
|
||||
BundleFileHasNoDefaultLib | BundleFileReference | BundleFilePrepend |
|
||||
BundleFileTextLike;
|
||||
|
||||
/*@internal*/
|
||||
export interface SourceFilePrologueDirectiveExpression extends TextRange {
|
||||
text: string;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export interface SourceFilePrologueDirective extends TextRange {
|
||||
expression: SourceFilePrologueDirectiveExpression;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export interface SourceFilePrologueInfo {
|
||||
file: number;
|
||||
text: string;
|
||||
directives: SourceFilePrologueDirective[];
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export interface SourceFileInfo {
|
||||
// List of helpers in own source files emitted if no prepend is present
|
||||
helpers?: string[];
|
||||
prologues?: SourceFilePrologueInfo[];
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export interface BundleFileInfo {
|
||||
sections: BundleFileSection[];
|
||||
sources?: SourceFileInfo;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export interface BundleBuildInfo {
|
||||
js?: BundleFileInfo;
|
||||
dts?: BundleFileInfo;
|
||||
commonSourceDirectory: string;
|
||||
sourceFiles: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a bundle contains prepended content, we store a file on disk indicating where the non-prepended
|
||||
* content of that file starts. On a subsequent build where there are no upstream .d.ts changes, we
|
||||
* read the bundle info file and the original .js file to quickly re-use portion of the file
|
||||
* that didn't originate in prepended content.
|
||||
*/
|
||||
/* @internal */
|
||||
export interface BundleInfo {
|
||||
// The offset (in characters, i.e. suitable for .substr) at which the
|
||||
// non-prepended portion of the emitted file starts.
|
||||
originalOffset: number;
|
||||
// The total length of this bundle. Used to ensure we're pulling from
|
||||
// the same source as we originally wrote.
|
||||
totalLength: number;
|
||||
export interface BuildInfo {
|
||||
bundle?: BundleBuildInfo;
|
||||
program?: ProgramBuildInfo;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface PrintHandlers {
|
||||
@@ -5512,6 +5675,9 @@ namespace ts {
|
||||
/*@internal*/ extendedDiagnostics?: boolean;
|
||||
/*@internal*/ onlyPrintJsDocStyle?: boolean;
|
||||
/*@internal*/ neverAsciiEscape?: boolean;
|
||||
/*@internal*/ writeBundleFileInfo?: boolean;
|
||||
/*@internal*/ recordInternalSection?: boolean;
|
||||
/*@internal*/ stripInternal?: boolean;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
@@ -5554,7 +5720,7 @@ namespace ts {
|
||||
/**
|
||||
* Appends a source map.
|
||||
*/
|
||||
appendSourceMap(generatedLine: number, generatedCharacter: number, sourceMap: RawSourceMap, sourceMapPath: string): void;
|
||||
appendSourceMap(generatedLine: number, generatedCharacter: number, sourceMap: RawSourceMap, sourceMapPath: string, start?: LineAndCharacter, end?: LineAndCharacter): void;
|
||||
/**
|
||||
* Gets the source map as a `RawSourceMap` object.
|
||||
*/
|
||||
@@ -5600,6 +5766,7 @@ namespace ts {
|
||||
getColumn(): number;
|
||||
getIndent(): number;
|
||||
isAtStartOfLine(): boolean;
|
||||
getTextPosWithWriteLine?(): number;
|
||||
}
|
||||
|
||||
export interface GetEffectiveTypeRootsHost {
|
||||
@@ -5857,6 +6024,7 @@ namespace ts {
|
||||
// The above fallback to `object` when there's no args to allow `{}` (as intended), but not the number 2, for example
|
||||
// TODO: Swap to `undefined` for a cleaner API once strictNullChecks is enabled
|
||||
|
||||
/* @internal */
|
||||
type ConcretePragmaSpecs = typeof commentPragmas;
|
||||
|
||||
/* @internal */
|
||||
|
||||
@@ -2601,7 +2601,7 @@ namespace ts {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function tryResolveScriptReference(host: ScriptReferenceHost, sourceFile: SourceFile, reference: FileReference) {
|
||||
export function tryResolveScriptReference(host: ScriptReferenceHost, sourceFile: SourceFile | UnparsedSource, reference: FileReference) {
|
||||
if (!host.getCompilerOptions().noResolve) {
|
||||
const referenceFileName = isRootedDiskPath(reference.fileName) ? reference.fileName : combinePaths(getDirectoryPath(sourceFile.fileName), reference.fileName);
|
||||
return host.getSourceFile(referenceFileName);
|
||||
@@ -3244,6 +3244,10 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function getTextPosWithWriteLine() {
|
||||
return lineStart ? output.length : (output.length + newLine.length);
|
||||
}
|
||||
|
||||
reset();
|
||||
|
||||
return {
|
||||
@@ -3273,7 +3277,8 @@ namespace ts {
|
||||
writeStringLiteral: write,
|
||||
writeSymbol: (s, _) => write(s),
|
||||
writeTrailingSemicolon: write,
|
||||
writeComment: write
|
||||
writeComment: write,
|
||||
getTextPosWithWriteLine
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3398,11 +3403,11 @@ namespace ts {
|
||||
}
|
||||
|
||||
export interface EmitFileNames {
|
||||
jsFilePath: string | undefined;
|
||||
sourceMapFilePath: string | undefined;
|
||||
declarationFilePath: string | undefined;
|
||||
declarationMapPath: string | undefined;
|
||||
bundleInfoPath: string | undefined;
|
||||
jsFilePath?: string | undefined;
|
||||
sourceMapFilePath?: string | undefined;
|
||||
declarationFilePath?: string | undefined;
|
||||
declarationMapPath?: string | undefined;
|
||||
buildInfoPath?: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4638,6 +4643,16 @@ namespace ts {
|
||||
export function isAccessExpression(node: Node): node is AccessExpression {
|
||||
return node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.ElementAccessExpression;
|
||||
}
|
||||
|
||||
export function isBundleFileTextLike(section: BundleFileSection): section is BundleFileTextLike {
|
||||
switch (section.kind) {
|
||||
case BundleFileSectionKind.Text:
|
||||
case BundleFileSectionKind.Internal:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace ts {
|
||||
@@ -6030,6 +6045,26 @@ namespace ts {
|
||||
return node.kind === SyntaxKind.UnparsedSource;
|
||||
}
|
||||
|
||||
export function isUnparsedPrepend(node: Node): node is UnparsedPrepend {
|
||||
return node.kind === SyntaxKind.UnparsedPrepend;
|
||||
}
|
||||
|
||||
export function isUnparsedTextLike(node: Node): node is UnparsedTextLike {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.UnparsedText:
|
||||
case SyntaxKind.UnparsedInternalText:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function isUnparsedNode(node: Node): node is UnparsedNode {
|
||||
return isUnparsedTextLike(node) ||
|
||||
node.kind === SyntaxKind.UnparsedPrologue ||
|
||||
node.kind === SyntaxKind.UnparsedSyntheticReference;
|
||||
}
|
||||
|
||||
// JSDoc
|
||||
|
||||
export function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression {
|
||||
@@ -6785,7 +6820,7 @@ namespace ts {
|
||||
/* @internal */
|
||||
export function isDeclaration(node: Node): node is NamedDeclaration {
|
||||
if (node.kind === SyntaxKind.TypeParameter) {
|
||||
return node.parent.kind !== SyntaxKind.JSDocTemplateTag || isInJSFile(node);
|
||||
return (node.parent && node.parent.kind !== SyntaxKind.JSDocTemplateTag) || isInJSFile(node);
|
||||
}
|
||||
|
||||
return isDeclarationKind(node.kind);
|
||||
@@ -7257,6 +7292,10 @@ namespace ts {
|
||||
return !!(compilerOptions.declaration || compilerOptions.composite);
|
||||
}
|
||||
|
||||
export function isIncrementalCompilation(options: CompilerOptions) {
|
||||
return !!(options.incremental || options.composite);
|
||||
}
|
||||
|
||||
export type StrictOptionName = "noImplicitAny" | "noImplicitThis" | "strictNullChecks" | "strictFunctionTypes" | "strictBindCallApply" | "strictPropertyInitialization" | "alwaysStrict";
|
||||
|
||||
export function getStrictOptionValue(compilerOptions: CompilerOptions, flag: StrictOptionName): boolean {
|
||||
|
||||
@@ -283,7 +283,7 @@ namespace ts {
|
||||
/**
|
||||
* Creates the watch compiler host that can be extended with config file or root file names and options host
|
||||
*/
|
||||
export function createProgramHost<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T>): ProgramHost<T> {
|
||||
export function createProgramHost<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined): ProgramHost<T> {
|
||||
const getDefaultLibLocation = memoize(() => getDirectoryPath(normalizePath(system.getExecutingFilePath())));
|
||||
let host: DirectoryStructureHost = system;
|
||||
host; // tslint:disable-line no-unused-expression (TODO: `host` is unused!)
|
||||
@@ -305,7 +305,7 @@ namespace ts {
|
||||
writeFile: (path, data, writeByteOrderMark) => system.writeFile(path, data, writeByteOrderMark),
|
||||
onCachedDirectoryStructureHostCreate: cacheHost => host = cacheHost || system,
|
||||
createHash: maybeBind(system, system.createHash),
|
||||
createProgram
|
||||
createProgram: createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>
|
||||
};
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ namespace ts {
|
||||
*/
|
||||
function createWatchCompilerHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram: CreateProgram<T> | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHost<T> {
|
||||
const writeFileName = (s: string) => system.write(s + system.newLine);
|
||||
const result = createProgramHost(system, createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>) as WatchCompilerHost<T>;
|
||||
const result = createProgramHost(system, createProgram) as WatchCompilerHost<T>;
|
||||
copyProperties(result, createWatchHost(system, reportWatchStatus));
|
||||
result.afterProgramCreate = builderProgram => {
|
||||
const compilerOptions = builderProgram.getCompilerOptions();
|
||||
|
||||
+70
-1
@@ -384,8 +384,77 @@ namespace fakes {
|
||||
return text;
|
||||
}
|
||||
|
||||
function compareProgramBuildInfoDiagnostic(a: ts.ProgramBuildInfoDiagnostic, b: ts.ProgramBuildInfoDiagnostic) {
|
||||
return ts.compareStringsCaseSensitive(ts.isString(a) ? a : a[0], ts.isString(b) ? b : b[0]);
|
||||
}
|
||||
|
||||
export const version = "FakeTSVersion";
|
||||
|
||||
export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost<ts.BuilderProgram> {
|
||||
createProgram = ts.createAbstractBuilder;
|
||||
createProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram;
|
||||
|
||||
readFile(path: string) {
|
||||
const value = super.readFile(path);
|
||||
if (!value || !ts.isBuildInfoFile(path)) return value;
|
||||
const buildInfo = ts.getBuildInfo(value);
|
||||
if (buildInfo.program) {
|
||||
// Fix lib signatures
|
||||
for (const path of ts.getOwnKeys(buildInfo.program.fileInfos)) {
|
||||
if (ts.startsWith(path, "/lib/")) {
|
||||
const currentValue = buildInfo.program.fileInfos[path];
|
||||
ts.Debug.assert(currentValue.signature === path);
|
||||
ts.Debug.assert(currentValue.signature === currentValue.version);
|
||||
const text = super.readFile(path)!;
|
||||
const signature = ts.generateDjb2Hash(text);
|
||||
buildInfo.program.fileInfos[path] = { version: signature, signature };
|
||||
}
|
||||
}
|
||||
}
|
||||
ts.Debug.assert(buildInfo.version === version);
|
||||
buildInfo.version = ts.version;
|
||||
return ts.getBuildInfoText(buildInfo);
|
||||
}
|
||||
|
||||
public writeFile(fileName: string, content: string, writeByteOrderMark: boolean) {
|
||||
if (!ts.isBuildInfoFile(fileName)) return super.writeFile(fileName, content, writeByteOrderMark);
|
||||
const buildInfo = ts.getBuildInfo(content);
|
||||
if (buildInfo.program) {
|
||||
// Fix lib signatures
|
||||
for (const path of ts.getOwnKeys(buildInfo.program.fileInfos)) {
|
||||
if (ts.startsWith(path, "/lib/")) {
|
||||
const currentValue = buildInfo.program.fileInfos[path];
|
||||
ts.Debug.assert(currentValue.signature === currentValue.version);
|
||||
buildInfo.program.fileInfos[path] = { version: path, signature: path };
|
||||
}
|
||||
}
|
||||
|
||||
// reference Map
|
||||
if (buildInfo.program.referencedMap) {
|
||||
const referencedMap: ts.MapLike<string[]> = {};
|
||||
for (const path of ts.getOwnKeys(buildInfo.program.referencedMap).sort()) {
|
||||
referencedMap[path] = buildInfo.program.referencedMap[path].sort();
|
||||
}
|
||||
buildInfo.program.referencedMap = referencedMap;
|
||||
}
|
||||
|
||||
// exportedModulesMap
|
||||
if (buildInfo.program.exportedModulesMap) {
|
||||
const exportedModulesMap: ts.MapLike<string[]> = {};
|
||||
for (const path of ts.getOwnKeys(buildInfo.program.exportedModulesMap).sort()) {
|
||||
exportedModulesMap[path] = buildInfo.program.exportedModulesMap[path].sort();
|
||||
}
|
||||
buildInfo.program.exportedModulesMap = exportedModulesMap;
|
||||
}
|
||||
|
||||
// semanticDiagnosticsPerFile
|
||||
if (buildInfo.program.semanticDiagnosticsPerFile) {
|
||||
buildInfo.program.semanticDiagnosticsPerFile.sort(compareProgramBuildInfoDiagnostic);
|
||||
}
|
||||
}
|
||||
buildInfo.version = version;
|
||||
super.writeFile(fileName, ts.getBuildInfoText(buildInfo), writeByteOrderMark);
|
||||
}
|
||||
|
||||
now() {
|
||||
return new Date(this.sys.vfs.time());
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@ namespace Harness.SourceMapRecorder {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export function initializeSourceMapDecoding(sourceMapData: ts.SourceMapEmitResult) {
|
||||
export function initializeSourceMapDecoding(sourceMap: ts.RawSourceMap) {
|
||||
decodingIndex = 0;
|
||||
sourceMapMappings = sourceMapData.sourceMap.mappings;
|
||||
mappings = ts.decodeMappings(sourceMapData.sourceMap.mappings);
|
||||
sourceMapMappings = sourceMap.mappings;
|
||||
mappings = ts.decodeMappings(sourceMap.mappings);
|
||||
}
|
||||
|
||||
export function decodeNextEncodedSourceMapSpan(): DecodedMapping {
|
||||
@@ -53,10 +53,10 @@ namespace Harness.SourceMapRecorder {
|
||||
let nextJsLineToWrite: number;
|
||||
let spanMarkerContinues: boolean;
|
||||
|
||||
export function initializeSourceMapSpanWriter(sourceMapRecordWriter: Compiler.WriterAggregator, sourceMapData: ts.SourceMapEmitResult, currentJsFile: documents.TextDocument) {
|
||||
export function initializeSourceMapSpanWriter(sourceMapRecordWriter: Compiler.WriterAggregator, sourceMap: ts.RawSourceMap, currentJsFile: documents.TextDocument) {
|
||||
sourceMapRecorder = sourceMapRecordWriter;
|
||||
sourceMapSources = sourceMapData.sourceMap.sources;
|
||||
sourceMapNames = sourceMapData.sourceMap.names;
|
||||
sourceMapSources = sourceMap.sources;
|
||||
sourceMapNames = sourceMap.names;
|
||||
|
||||
jsFile = currentJsFile;
|
||||
jsLineMap = jsFile.lineStarts;
|
||||
@@ -66,14 +66,14 @@ namespace Harness.SourceMapRecorder {
|
||||
nextJsLineToWrite = 0;
|
||||
spanMarkerContinues = false;
|
||||
|
||||
SourceMapDecoder.initializeSourceMapDecoding(sourceMapData);
|
||||
SourceMapDecoder.initializeSourceMapDecoding(sourceMap);
|
||||
sourceMapRecorder.WriteLine("===================================================================");
|
||||
sourceMapRecorder.WriteLine("JsFile: " + sourceMapData.sourceMap.file);
|
||||
sourceMapRecorder.WriteLine("JsFile: " + sourceMap.file);
|
||||
sourceMapRecorder.WriteLine("mapUrl: " + ts.tryGetSourceMappingURL(ts.getLineInfo(jsFile.text, jsLineMap)));
|
||||
sourceMapRecorder.WriteLine("sourceRoot: " + sourceMapData.sourceMap.sourceRoot);
|
||||
sourceMapRecorder.WriteLine("sources: " + sourceMapData.sourceMap.sources);
|
||||
if (sourceMapData.sourceMap.sourcesContent) {
|
||||
sourceMapRecorder.WriteLine("sourcesContent: " + JSON.stringify(sourceMapData.sourceMap.sourcesContent));
|
||||
sourceMapRecorder.WriteLine("sourceRoot: " + sourceMap.sourceRoot);
|
||||
sourceMapRecorder.WriteLine("sources: " + sourceMap.sources);
|
||||
if (sourceMap.sourcesContent) {
|
||||
sourceMapRecorder.WriteLine("sourcesContent: " + JSON.stringify(sourceMap.sourcesContent));
|
||||
}
|
||||
sourceMapRecorder.WriteLine("===================================================================");
|
||||
}
|
||||
@@ -299,7 +299,7 @@ namespace Harness.SourceMapRecorder {
|
||||
}
|
||||
}
|
||||
|
||||
SourceMapSpanWriter.initializeSourceMapSpanWriter(sourceMapRecorder, sourceMapData, currentFile);
|
||||
SourceMapSpanWriter.initializeSourceMapSpanWriter(sourceMapRecorder, sourceMapData.sourceMap, currentFile);
|
||||
const mapper = ts.decodeMappings(sourceMapData.sourceMap.mappings);
|
||||
for (let { value: decodedSourceMapping, done } = mapper.next(); !done; { value: decodedSourceMapping, done } = mapper.next()) {
|
||||
const currentSourceFile = ts.isSourceMapping(decodedSourceMapping)
|
||||
@@ -320,4 +320,46 @@ namespace Harness.SourceMapRecorder {
|
||||
sourceMapRecorder.Close();
|
||||
return sourceMapRecorder.lines.join("\r\n");
|
||||
}
|
||||
|
||||
export function getSourceMapRecordWithVFS(fs: vfs.FileSystem, sourceMapFile: string) {
|
||||
const sourceMapRecorder = new Compiler.WriterAggregator();
|
||||
let prevSourceFile: documents.TextDocument | undefined;
|
||||
const files = ts.createMap<documents.TextDocument>();
|
||||
const sourceMap = ts.tryParseRawSourceMap(fs.readFileSync(sourceMapFile, "utf8"));
|
||||
if (sourceMap) {
|
||||
const mapDirectory = ts.getDirectoryPath(sourceMapFile);
|
||||
const sourceRoot = sourceMap.sourceRoot ? ts.getNormalizedAbsolutePath(sourceMap.sourceRoot, mapDirectory) : mapDirectory;
|
||||
const generatedAbsoluteFilePath = ts.getNormalizedAbsolutePath(sourceMap.file, mapDirectory);
|
||||
const sourceFileAbsolutePaths = sourceMap.sources.map(source => ts.getNormalizedAbsolutePath(source, sourceRoot));
|
||||
const currentFile = getFile(generatedAbsoluteFilePath);
|
||||
|
||||
SourceMapSpanWriter.initializeSourceMapSpanWriter(sourceMapRecorder, sourceMap, currentFile);
|
||||
const mapper = ts.decodeMappings(sourceMap.mappings);
|
||||
for (let { value: decodedSourceMapping, done } = mapper.next(); !done; { value: decodedSourceMapping, done } = mapper.next()) {
|
||||
const currentSourceFile = ts.isSourceMapping(decodedSourceMapping)
|
||||
? getFile(sourceFileAbsolutePaths[decodedSourceMapping.sourceIndex])
|
||||
: undefined;
|
||||
if (currentSourceFile !== prevSourceFile) {
|
||||
if (currentSourceFile) {
|
||||
SourceMapSpanWriter.recordNewSourceFileSpan(decodedSourceMapping, currentSourceFile.text);
|
||||
}
|
||||
prevSourceFile = currentSourceFile;
|
||||
}
|
||||
else {
|
||||
SourceMapSpanWriter.recordSourceMapSpan(decodedSourceMapping);
|
||||
}
|
||||
}
|
||||
SourceMapSpanWriter.close(); // If the last spans werent emitted, emit them
|
||||
}
|
||||
sourceMapRecorder.Close();
|
||||
return sourceMapRecorder.lines.join("\r\n");
|
||||
|
||||
function getFile(path: string) {
|
||||
const existing = files.get(path);
|
||||
if (existing) return existing;
|
||||
const value = new documents.TextDocument(path, fs.readFileSync(path, "utf8"));
|
||||
files.set(path, value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,6 +335,7 @@ namespace ts.server {
|
||||
RootOfInferredProjectFalse = "Open file was set as not inferred root",
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
interface ConfigFileExistenceInfo {
|
||||
/**
|
||||
* Cached value of existence of config file
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": "../tsconfig-base",
|
||||
"compilerOptions": {
|
||||
"removeComments": true,
|
||||
"removeComments": false,
|
||||
"outFile": "../../built/local/server.js",
|
||||
"preserveConstEnums": true,
|
||||
"types": [
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace ts {
|
||||
return textChanges.getNewFileText(toStatements(valueInfo, outputKind), ScriptKind.TS, formatSettings.newLineCharacter || "\n", formatting.getFormatContext(formatSettings));
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
const enum OutputKind { ExportEquals, NamedExport, NamespaceMember, Global }
|
||||
function toNamespaceMemberStatements(info: ValueInfo): ReadonlyArray<Statement> {
|
||||
return toStatements(info, OutputKind.NamespaceMember);
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace ts.Completions.StringCompletions {
|
||||
case Extension.Jsx: return ScriptElementKindModifier.jsxModifier;
|
||||
case Extension.Ts: return ScriptElementKindModifier.tsModifier;
|
||||
case Extension.Tsx: return ScriptElementKindModifier.tsxModifier;
|
||||
case Extension.TsBuildInfo: return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported.`);
|
||||
case undefined: return ScriptElementKindModifier.none;
|
||||
default:
|
||||
return Debug.assertNever(extension);
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"runner.ts",
|
||||
|
||||
"unittests/services/extract/helpers.ts",
|
||||
"unittests/tsbuild/helpers.ts",
|
||||
"unittests/tscWatch/helpers.ts",
|
||||
"unittests/tsserver/helpers.ts",
|
||||
|
||||
@@ -60,7 +61,6 @@
|
||||
"unittests/semver.ts",
|
||||
"unittests/shimMap.ts",
|
||||
"unittests/transform.ts",
|
||||
"unittests/tsbuild.ts",
|
||||
"unittests/tsbuildWatchMode.ts",
|
||||
"unittests/config/commandLineParsing.ts",
|
||||
"unittests/config/configurationExtension.ts",
|
||||
@@ -89,6 +89,15 @@
|
||||
"unittests/services/preProcessFile.ts",
|
||||
"unittests/services/textChanges.ts",
|
||||
"unittests/services/transpile.ts",
|
||||
"unittests/tsbuild/amdModulesWithOut.ts",
|
||||
"unittests/tsbuild/emptyFiles.ts",
|
||||
"unittests/tsbuild/graphOrdering.ts",
|
||||
"unittests/tsbuild/missingExtendedFile.ts",
|
||||
"unittests/tsbuild/outFile.ts",
|
||||
"unittests/tsbuild/referencesWithRootDirInParent.ts",
|
||||
"unittests/tsbuild/resolveJsonModule.ts",
|
||||
"unittests/tsbuild/sample.ts",
|
||||
"unittests/tsbuild/transitiveReferences.ts",
|
||||
"unittests/tscWatch/consoleClearing.ts",
|
||||
"unittests/tscWatch/emit.ts",
|
||||
"unittests/tscWatch/emitAndErrorUpdates.ts",
|
||||
|
||||
@@ -1,763 +0,0 @@
|
||||
namespace ts {
|
||||
let currentTime = 100;
|
||||
|
||||
function getExpectedDiagnosticForProjectsInBuild(...projects: string[]): fakes.ExpectedDiagnostic {
|
||||
return [Diagnostics.Projects_in_this_build_Colon_0, projects.map(p => "\r\n * " + p).join("")];
|
||||
}
|
||||
|
||||
export namespace Sample1 {
|
||||
tick();
|
||||
const projFs = loadProjectFromDisk("tests/projects/sample1");
|
||||
|
||||
const allExpectedOutputs = ["/src/tests/index.js",
|
||||
"/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map",
|
||||
"/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts"];
|
||||
|
||||
describe("unittests:: tsbuild - sanity check of clean build of 'sample1' project", () => {
|
||||
it("can build the sample project 'sample1' without error", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
|
||||
|
||||
host.clearDiagnostics();
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*empty*/);
|
||||
|
||||
// Check for outputs. Not an exhaustive list
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
});
|
||||
|
||||
it("builds correctly when outDir is specified", () => {
|
||||
const fs = projFs.shadow();
|
||||
fs.writeFileSync("/src/logic/tsconfig.json", JSON.stringify({
|
||||
compilerOptions: { composite: true, declaration: true, sourceMap: true, outDir: "outDir" },
|
||||
references: [{ path: "../core" }]
|
||||
}));
|
||||
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], {});
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*empty*/);
|
||||
const expectedOutputs = allExpectedOutputs.map(f => f.replace("/logic/", "/logic/outDir/"));
|
||||
// Check for outputs. Not an exhaustive list
|
||||
for (const output of expectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
});
|
||||
|
||||
it("builds correctly when declarationDir is specified", () => {
|
||||
const fs = projFs.shadow();
|
||||
fs.writeFileSync("/src/logic/tsconfig.json", JSON.stringify({
|
||||
compilerOptions: { composite: true, declaration: true, sourceMap: true, declarationDir: "out/decls" },
|
||||
references: [{ path: "../core" }]
|
||||
}));
|
||||
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], {});
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*empty*/);
|
||||
const expectedOutputs = allExpectedOutputs.map(f => f.replace("/logic/index.d.ts", "/logic/out/decls/index.d.ts"));
|
||||
// Check for outputs. Not an exhaustive list
|
||||
for (const output of expectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: tsbuild - dry builds", () => {
|
||||
it("doesn't write any files in a dry build", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: true, force: false, verbose: false });
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
[Diagnostics.A_non_dry_build_would_build_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.A_non_dry_build_would_build_project_0, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.A_non_dry_build_would_build_project_0, "/src/tests/tsconfig.json"]
|
||||
);
|
||||
|
||||
// Check for outputs to not be written. Not an exhaustive list
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(!fs.existsSync(output), `Expect file ${output} to not exist`);
|
||||
}
|
||||
});
|
||||
|
||||
it("indicates that it would skip builds during a dry build", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
|
||||
let builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
|
||||
builder.buildAllProjects();
|
||||
tick();
|
||||
|
||||
host.clearDiagnostics();
|
||||
builder = createSolutionBuilder(host, ["/src/tests"], { dry: true, force: false, verbose: false });
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
[Diagnostics.Project_0_is_up_to_date, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_up_to_date, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_up_to_date, "/src/tests/tsconfig.json"]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: tsbuild - clean builds", () => {
|
||||
it("removes all files it built", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
|
||||
builder.buildAllProjects();
|
||||
// Verify they exist
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
builder.cleanAllProjects();
|
||||
// Verify they are gone
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(!fs.existsSync(output), `Expect file ${output} to not exist`);
|
||||
}
|
||||
// Subsequent clean shouldn't throw / etc
|
||||
builder.cleanAllProjects();
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: tsbuild - force builds", () => {
|
||||
it("always builds under --force", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: true, verbose: false });
|
||||
builder.buildAllProjects();
|
||||
let currentTime = time();
|
||||
checkOutputTimestamps(currentTime);
|
||||
|
||||
tick();
|
||||
Debug.assert(time() !== currentTime, "Time moves on");
|
||||
currentTime = time();
|
||||
builder.buildAllProjects();
|
||||
checkOutputTimestamps(currentTime);
|
||||
|
||||
function checkOutputTimestamps(expected: number) {
|
||||
// Check timestamps
|
||||
for (const output of allExpectedOutputs) {
|
||||
const actual = fs.statSync(output).mtimeMs;
|
||||
assert(actual === expected, `File ${output} has timestamp ${actual}, expected ${expected}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: tsbuild - can detect when and what to rebuild", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: true });
|
||||
|
||||
it("Builds the project", () => {
|
||||
host.clearDiagnostics();
|
||||
builder.resetBuildContext();
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"],
|
||||
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"],
|
||||
[Diagnostics.Building_project_0, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/tests/tsconfig.json", "src/tests/index.js"],
|
||||
[Diagnostics.Building_project_0, "/src/tests/tsconfig.json"]
|
||||
);
|
||||
tick();
|
||||
});
|
||||
|
||||
// All three projects are up to date
|
||||
it("Detects that all projects are up to date", () => {
|
||||
host.clearDiagnostics();
|
||||
builder.resetBuildContext();
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"],
|
||||
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/logic/tsconfig.json", "src/logic/index.ts", "src/logic/index.js"],
|
||||
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/tests/tsconfig.json", "src/tests/index.ts", "src/tests/index.js"]
|
||||
);
|
||||
tick();
|
||||
});
|
||||
|
||||
// Update a file in the leaf node (tests), only it should rebuild the last one
|
||||
it("Only builds the leaf node project", () => {
|
||||
host.clearDiagnostics();
|
||||
fs.writeFileSync("/src/tests/index.ts", "const m = 10;");
|
||||
builder.resetBuildContext();
|
||||
builder.buildAllProjects();
|
||||
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"],
|
||||
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/logic/tsconfig.json", "src/logic/index.ts", "src/logic/index.js"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/tests/tsconfig.json", "src/tests/index.js", "src/tests/index.ts"],
|
||||
[Diagnostics.Building_project_0, "/src/tests/tsconfig.json"]
|
||||
);
|
||||
tick();
|
||||
});
|
||||
|
||||
// Update a file in the parent (without affecting types), should get fast downstream builds
|
||||
it("Detects type-only changes in upstream projects", () => {
|
||||
host.clearDiagnostics();
|
||||
replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET");
|
||||
builder.resetBuildContext();
|
||||
builder.buildAllProjects();
|
||||
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/core/tsconfig.json", "src/core/anotherModule.js", "src/core/index.ts"],
|
||||
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, "src/logic/tsconfig.json"],
|
||||
[Diagnostics.Updating_output_timestamps_of_project_0, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, "src/tests/tsconfig.json"],
|
||||
[Diagnostics.Updating_output_timestamps_of_project_0, "/src/tests/tsconfig.json"]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: tsbuild - downstream-blocked compilations", () => {
|
||||
it("won't build downstream projects if upstream projects have errors", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: true });
|
||||
|
||||
// Induce an error in the middle project
|
||||
replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`);
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"],
|
||||
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"],
|
||||
[Diagnostics.Building_project_0, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.Property_0_does_not_exist_on_type_1, "muitply", `typeof import("/src/core/index")`],
|
||||
[Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, "src/tests/tsconfig.json", "src/logic"],
|
||||
[Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, "/src/tests/tsconfig.json", "/src/logic"]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: tsbuild - project invalidation", () => {
|
||||
it("invalidates projects correctly", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
|
||||
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*empty*/);
|
||||
|
||||
// Update a timestamp in the middle project
|
||||
tick();
|
||||
touch(fs, "/src/logic/index.ts");
|
||||
const originalWriteFile = fs.writeFileSync;
|
||||
const writtenFiles = createMap<true>();
|
||||
fs.writeFileSync = (path, data, encoding) => {
|
||||
writtenFiles.set(path, true);
|
||||
originalWriteFile.call(fs, path, data, encoding);
|
||||
};
|
||||
// Because we haven't reset the build context, the builder should assume there's nothing to do right now
|
||||
const status = builder.getUpToDateStatusOfFile(builder.resolveProjectName("/src/logic"));
|
||||
assert.equal(status.type, UpToDateStatusType.UpToDate, "Project should be assumed to be up-to-date");
|
||||
verifyInvalidation(/*expectedToWriteTests*/ false);
|
||||
|
||||
// Rebuild this project
|
||||
fs.writeFileSync("/src/logic/index.ts", `${fs.readFileSync("/src/logic/index.ts")}
|
||||
export class cNew {}`);
|
||||
verifyInvalidation(/*expectedToWriteTests*/ true);
|
||||
|
||||
function verifyInvalidation(expectedToWriteTests: boolean) {
|
||||
// Rebuild this project
|
||||
tick();
|
||||
builder.invalidateProject("/src/logic");
|
||||
builder.buildInvalidatedProject();
|
||||
// The file should be updated
|
||||
assert.isTrue(writtenFiles.has("/src/logic/index.js"), "JS file should have been rebuilt");
|
||||
assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt");
|
||||
assert.isFalse(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should *not* have been rebuilt");
|
||||
assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt");
|
||||
writtenFiles.clear();
|
||||
|
||||
// Build downstream projects should update 'tests', but not 'core'
|
||||
tick();
|
||||
builder.buildInvalidatedProject();
|
||||
if (expectedToWriteTests) {
|
||||
assert.isTrue(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should have been rebuilt");
|
||||
}
|
||||
else {
|
||||
assert.equal(writtenFiles.size, 0, "Should not write any new files");
|
||||
}
|
||||
assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have new timestamp");
|
||||
assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: tsbuild - with resolveJsonModule option", () => {
|
||||
const projFs = loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite");
|
||||
const allExpectedOutputs = ["/src/tests/dist/src/index.js", "/src/tests/dist/src/index.d.ts", "/src/tests/dist/src/hello.json"];
|
||||
|
||||
function verifyProjectWithResolveJsonModule(configFile: string, ...expectedDiagnosticMessages: fakes.ExpectedDiagnostic[]) {
|
||||
const fs = projFs.shadow();
|
||||
verifyProjectWithResolveJsonModuleWithFs(fs, configFile, allExpectedOutputs, ...expectedDiagnosticMessages);
|
||||
}
|
||||
|
||||
function verifyProjectWithResolveJsonModuleWithFs(fs: vfs.FileSystem, configFile: string, allExpectedOutputs: ReadonlyArray<string>, ...expectedDiagnosticMessages: fakes.ExpectedDiagnostic[]) {
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, [configFile], { dry: false, force: false, verbose: false });
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(...expectedDiagnosticMessages);
|
||||
if (!expectedDiagnosticMessages.length) {
|
||||
// Check for outputs. Not an exhaustive list
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it("with resolveJsonModule and include only", () => {
|
||||
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withInclude.json", [
|
||||
Diagnostics.File_0_is_not_in_project_file_list_Projects_must_list_all_files_or_use_an_include_pattern,
|
||||
"/src/tests/src/hello.json"
|
||||
]);
|
||||
});
|
||||
|
||||
it("with resolveJsonModule and include of *.json along with other include", () => {
|
||||
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withIncludeOfJson.json");
|
||||
});
|
||||
|
||||
it("with resolveJsonModule and include of *.json along with other include and file name matches ts file", () => {
|
||||
const fs = projFs.shadow();
|
||||
fs.rimrafSync("/src/tests/src/hello.json");
|
||||
fs.writeFileSync("/src/tests/src/index.json", JSON.stringify({ hello: "world" }));
|
||||
fs.writeFileSync("/src/tests/src/index.ts", `import hello from "./index.json"
|
||||
|
||||
export default hello.hello`);
|
||||
const allExpectedOutputs = ["/src/tests/dist/src/index.js", "/src/tests/dist/src/index.d.ts", "/src/tests/dist/src/index.json"];
|
||||
verifyProjectWithResolveJsonModuleWithFs(fs, "/src/tests/tsconfig_withIncludeOfJson.json", allExpectedOutputs);
|
||||
});
|
||||
|
||||
it("with resolveJsonModule and files containing json file", () => {
|
||||
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withFiles.json");
|
||||
});
|
||||
|
||||
it("with resolveJsonModule and include and files", () => {
|
||||
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withIncludeAndFiles.json");
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: tsbuild - lists files", () => {
|
||||
it("listFiles", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { listFiles: true });
|
||||
builder.buildAllProjects();
|
||||
assert.deepEqual(host.traces, [
|
||||
...getLibs(),
|
||||
"/src/core/anotherModule.ts",
|
||||
"/src/core/index.ts",
|
||||
"/src/core/some_decl.d.ts",
|
||||
...getLibs(),
|
||||
...getCoreOutputs(),
|
||||
"/src/logic/index.ts",
|
||||
...getLibs(),
|
||||
...getCoreOutputs(),
|
||||
"/src/logic/index.d.ts",
|
||||
"/src/tests/index.ts"
|
||||
]);
|
||||
|
||||
function getCoreOutputs() {
|
||||
return [
|
||||
"/src/core/index.d.ts",
|
||||
"/src/core/anotherModule.d.ts"
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
it("listEmittedFiles", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { listEmittedFiles: true });
|
||||
builder.buildAllProjects();
|
||||
assert.deepEqual(host.traces, [
|
||||
"TSFILE: /src/core/anotherModule.js",
|
||||
"TSFILE: /src/core/anotherModule.d.ts",
|
||||
"TSFILE: /src/core/anotherModule.d.ts.map",
|
||||
"TSFILE: /src/core/index.js",
|
||||
"TSFILE: /src/core/index.d.ts",
|
||||
"TSFILE: /src/core/index.d.ts.map",
|
||||
"TSFILE: /src/logic/index.js",
|
||||
"TSFILE: /src/logic/index.js.map",
|
||||
"TSFILE: /src/logic/index.d.ts",
|
||||
"TSFILE: /src/tests/index.js",
|
||||
"TSFILE: /src/tests/index.d.ts",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: tsbuild - with rootDir of project reference in parentDirectory", () => {
|
||||
const projFs = loadProjectFromDisk("tests/projects/projectReferenceWithRootDirInParent");
|
||||
const allExpectedOutputs = [
|
||||
"/src/dist/other/other.js", "/src/dist/other/other.d.ts",
|
||||
"/src/dist/main/a.js", "/src/dist/main/a.d.ts",
|
||||
"/src/dist/main/b.js", "/src/dist/main/b.d.ts"
|
||||
];
|
||||
it("verify that it builds correctly", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/src/main", "/src/src/other"], {});
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*empty*/);
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: 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"
|
||||
];
|
||||
const expectedFileTraces = [
|
||||
...getLibs(),
|
||||
"/src/a.ts",
|
||||
...getLibs(),
|
||||
"/src/a.d.ts",
|
||||
"/src/b.ts",
|
||||
...getLibs(),
|
||||
"/src/a.d.ts",
|
||||
"/src/b.d.ts",
|
||||
"/src/refs/a.d.ts",
|
||||
"/src/c.ts"
|
||||
];
|
||||
|
||||
function verifyBuild(modifyDiskLayout: (fs: vfs.FileSystem) => void, allExpectedOutputs: ReadonlyArray<string>, expectedFileTraces: ReadonlyArray<string>, ...expectedDiagnostics: fakes.ExpectedDiagnostic[]) {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
modifyDiskLayout(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tsconfig.c.json"], { listFiles: true });
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(...expectedDiagnostics);
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
assert.deepEqual(host.traces, expectedFileTraces);
|
||||
}
|
||||
|
||||
function modifyFsBTsToNonRelativeImport(fs: vfs.FileSystem, moduleResolution: "node" | "classic") {
|
||||
fs.writeFileSync("/src/b.ts", `import {A} from 'a';
|
||||
export const b = new A();`);
|
||||
fs.writeFileSync("/src/tsconfig.b.json", JSON.stringify({
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
moduleResolution
|
||||
},
|
||||
files: ["b.ts"],
|
||||
references: [{ path: "tsconfig.a.json" }]
|
||||
}));
|
||||
}
|
||||
|
||||
it("verify that it builds correctly", () => {
|
||||
verifyBuild(noop, allExpectedOutputs, expectedFileTraces);
|
||||
});
|
||||
|
||||
it("verify that it builds correctly when the referenced project uses different module resolution", () => {
|
||||
verifyBuild(fs => modifyFsBTsToNonRelativeImport(fs, "classic"), allExpectedOutputs, expectedFileTraces);
|
||||
});
|
||||
|
||||
it("verify that it build reports error about module not found with node resolution with external module name", () => {
|
||||
// Error in b build only a
|
||||
const allExpectedOutputs = ["/src/a.js", "/src/a.d.ts"];
|
||||
const expectedFileTraces = [
|
||||
...getLibs(),
|
||||
"/src/a.ts",
|
||||
];
|
||||
verifyBuild(fs => modifyFsBTsToNonRelativeImport(fs, "node"),
|
||||
allExpectedOutputs,
|
||||
expectedFileTraces,
|
||||
[Diagnostics.Cannot_find_module_0, "a"],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("unittests:: tsbuild - when tsconfig extends the missing file", () => {
|
||||
const projFs = loadProjectFromDisk("tests/projects/missingExtendedConfig");
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tsconfig.json"], {});
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
[Diagnostics.The_specified_path_does_not_exist_Colon_0, "/src/foobar.json"],
|
||||
[Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, "/src/tsconfig.first.json", "[\"**/*\"]", "[]"],
|
||||
[Diagnostics.The_specified_path_does_not_exist_Colon_0, "/src/foobar.json"],
|
||||
[Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, "/src/tsconfig.second.json", "[\"**/*\"]", "[]"]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export namespace OutFile {
|
||||
const outFileFs = loadProjectFromDisk("tests/projects/outfile-concat");
|
||||
|
||||
describe("unittests:: tsbuild - baseline sectioned sourcemaps", () => {
|
||||
let fs: vfs.FileSystem | undefined;
|
||||
const actualReadFileMap = createMap<number>();
|
||||
before(() => {
|
||||
fs = outFileFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/third"], { dry: false, force: false, verbose: false });
|
||||
host.clearDiagnostics();
|
||||
const originalReadFile = host.readFile;
|
||||
host.readFile = path => {
|
||||
// Dont record libs
|
||||
if (path.startsWith("/src/")) {
|
||||
actualReadFileMap.set(path, (actualReadFileMap.get(path) || 0) + 1);
|
||||
}
|
||||
return originalReadFile.call(host, path);
|
||||
};
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*none*/);
|
||||
});
|
||||
after(() => {
|
||||
fs = undefined;
|
||||
});
|
||||
it(`Generates files matching the baseline`, () => {
|
||||
const patch = fs!.diff();
|
||||
// tslint:disable-next-line:no-null-keyword
|
||||
Harness.Baseline.runBaseline("outfile-concat.js", patch ? vfs.formatPatch(patch) : null);
|
||||
});
|
||||
it("verify readFile calls", () => {
|
||||
const expected = [
|
||||
// Configs
|
||||
"/src/third/tsconfig.json",
|
||||
"/src/second/tsconfig.json",
|
||||
"/src/first/tsconfig.json",
|
||||
|
||||
// Source files
|
||||
"/src/third/third_part1.ts",
|
||||
"/src/second/second_part1.ts",
|
||||
"/src/second/second_part2.ts",
|
||||
"/src/first/first_PART1.ts",
|
||||
"/src/first/first_part2.ts",
|
||||
"/src/first/first_part3.ts",
|
||||
|
||||
// outputs
|
||||
"/src/first/bin/first-output.js",
|
||||
"/src/first/bin/first-output.js.map",
|
||||
"/src/first/bin/first-output.d.ts",
|
||||
"/src/first/bin/first-output.d.ts.map",
|
||||
"/src/2/second-output.js",
|
||||
"/src/2/second-output.js.map",
|
||||
"/src/2/second-output.d.ts",
|
||||
"/src/2/second-output.d.ts.map"
|
||||
];
|
||||
|
||||
assert.equal(actualReadFileMap.size, expected.length, `Expected: ${JSON.stringify(expected)} \nActual: ${JSON.stringify(arrayFrom(actualReadFileMap.entries()))}`);
|
||||
expected.forEach(expectedValue => {
|
||||
const actual = actualReadFileMap.get(expectedValue);
|
||||
assert.equal(actual, 1, `Mismatch in read file call number for: ${expectedValue}\nExpected: ${JSON.stringify(expected)} \nActual: ${JSON.stringify(arrayFrom(actualReadFileMap.entries()))}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: tsbuild - downstream prepend projects always get rebuilt", () => {
|
||||
it("", () => {
|
||||
const fs = outFileFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/third"], { dry: false, force: false, verbose: false });
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*none*/);
|
||||
assert.equal(fs.statSync("src/third/thirdjs/output/third-output.js").mtimeMs, time(), "First build timestamp is correct");
|
||||
tick();
|
||||
replaceText(fs, "src/first/first_PART1.ts", "Hello", "Hola");
|
||||
tick();
|
||||
builder.resetBuildContext();
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*none*/);
|
||||
assert.equal(fs.statSync("src/third/thirdjs/output/third-output.js").mtimeMs, time(), "Second build timestamp is correct");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export namespace EmptyFiles {
|
||||
const projFs = loadProjectFromDisk("tests/projects/empty-files");
|
||||
|
||||
const allExpectedOutputs = [
|
||||
"/src/core/index.js",
|
||||
"/src/core/index.d.ts",
|
||||
"/src/core/index.d.ts.map",
|
||||
];
|
||||
|
||||
describe("unittests:: tsbuild - empty files option in tsconfig", () => {
|
||||
it("has empty files diagnostic when files is empty and no references are provided", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/no-references"], { dry: false, force: false, verbose: false });
|
||||
|
||||
host.clearDiagnostics();
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages([Diagnostics.The_files_list_in_config_file_0_is_empty, "/src/no-references/tsconfig.json"]);
|
||||
|
||||
// Check for outputs to not be written.
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(!fs.existsSync(output), `Expect file ${output} to not exist`);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not have empty files diagnostic when files is empty and references are provided", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/with-references"], { dry: false, force: false, verbose: false });
|
||||
|
||||
host.clearDiagnostics();
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*empty*/);
|
||||
|
||||
// Check for outputs to be written.
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("unittests:: tsbuild - graph-ordering", () => {
|
||||
let host: fakes.SolutionBuilderHost | undefined;
|
||||
const deps: [string, string][] = [
|
||||
["A", "B"],
|
||||
["B", "C"],
|
||||
["A", "C"],
|
||||
["B", "D"],
|
||||
["C", "D"],
|
||||
["C", "E"],
|
||||
["F", "E"]
|
||||
];
|
||||
|
||||
before(() => {
|
||||
const fs = new vfs.FileSystem(false);
|
||||
host = new fakes.SolutionBuilderHost(fs);
|
||||
writeProjects(fs, ["A", "B", "C", "D", "E", "F", "G"], deps);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
host = undefined;
|
||||
});
|
||||
|
||||
it("orders the graph correctly - specify two roots", () => {
|
||||
checkGraphOrdering(["A", "G"], ["A", "B", "C", "D", "E", "G"]);
|
||||
});
|
||||
|
||||
it("orders the graph correctly - multiple parts of the same graph in various orders", () => {
|
||||
checkGraphOrdering(["A"], ["A", "B", "C", "D", "E"]);
|
||||
checkGraphOrdering(["A", "C", "D"], ["A", "B", "C", "D", "E"]);
|
||||
checkGraphOrdering(["D", "C", "A"], ["A", "B", "C", "D", "E"]);
|
||||
});
|
||||
|
||||
it("orders the graph correctly - other orderings", () => {
|
||||
checkGraphOrdering(["F"], ["F", "E"]);
|
||||
checkGraphOrdering(["E"], ["E"]);
|
||||
checkGraphOrdering(["F", "C", "A"], ["A", "B", "C", "D", "E", "F"]);
|
||||
});
|
||||
|
||||
function checkGraphOrdering(rootNames: string[], expectedBuildSet: string[]) {
|
||||
const builder = createSolutionBuilder(host!, rootNames, { dry: true, force: false, verbose: false });
|
||||
|
||||
const projFileNames = rootNames.map(getProjectFileName);
|
||||
const graph = builder.getBuildGraph(projFileNames);
|
||||
|
||||
assert.sameMembers(graph.buildQueue, expectedBuildSet.map(getProjectFileName));
|
||||
|
||||
for (const dep of deps) {
|
||||
const child = getProjectFileName(dep[0]);
|
||||
if (graph.buildQueue.indexOf(child) < 0) continue;
|
||||
const parent = getProjectFileName(dep[1]);
|
||||
assert.isAbove(graph.buildQueue.indexOf(child), graph.buildQueue.indexOf(parent), `Expecting child ${child} to be built after parent ${parent}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getProjectFileName(proj: string) {
|
||||
return `/project/${proj}/tsconfig.json` as ResolvedConfigFileName;
|
||||
}
|
||||
|
||||
function writeProjects(fileSystem: vfs.FileSystem, projectNames: string[], deps: [string, string][]): string[] {
|
||||
const projFileNames: string[] = [];
|
||||
for (const dep of deps) {
|
||||
if (projectNames.indexOf(dep[0]) < 0) throw new Error(`Invalid dependency - project ${dep[0]} does not exist`);
|
||||
if (projectNames.indexOf(dep[1]) < 0) throw new Error(`Invalid dependency - project ${dep[1]} does not exist`);
|
||||
}
|
||||
for (const proj of projectNames) {
|
||||
fileSystem.mkdirpSync(`/project/${proj}`);
|
||||
fileSystem.writeFileSync(`/project/${proj}/${proj}.ts`, "export {}");
|
||||
const configFileName = getProjectFileName(proj);
|
||||
const configContent = JSON.stringify({
|
||||
compilerOptions: { composite: true },
|
||||
files: [`./${proj}.ts`],
|
||||
references: deps.filter(d => d[0] === proj).map(d => ({ path: `../${d[1]}` }))
|
||||
}, undefined, 2);
|
||||
fileSystem.writeFileSync(configFileName, configContent);
|
||||
projFileNames.push(configFileName);
|
||||
}
|
||||
return projFileNames;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function replaceText(fs: vfs.FileSystem, path: string, oldText: string, newText: string) {
|
||||
if (!fs.statSync(path).isFile()) {
|
||||
throw new Error(`File ${path} does not exist`);
|
||||
}
|
||||
const old = fs.readFileSync(path, "utf-8");
|
||||
if (old.indexOf(oldText) < 0) {
|
||||
throw new Error(`Text "${oldText}" does not exist in file ${path}`);
|
||||
}
|
||||
const newContent = old.replace(oldText, newText);
|
||||
fs.writeFileSync(path, newContent, "utf-8");
|
||||
}
|
||||
|
||||
function tick() {
|
||||
currentTime += 60_000;
|
||||
}
|
||||
|
||||
function time() {
|
||||
return currentTime;
|
||||
}
|
||||
|
||||
function touch(fs: vfs.FileSystem, path: string) {
|
||||
if (!fs.statSync(path).isFile()) {
|
||||
throw new Error(`File ${path} does not exist`);
|
||||
}
|
||||
fs.utimesSync(path, new Date(time()), new Date(time()));
|
||||
}
|
||||
|
||||
function loadProjectFromDisk(root: string): vfs.FileSystem {
|
||||
const resolver = vfs.createResolver(Harness.IO);
|
||||
const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
|
||||
files: {
|
||||
["/lib"]: new vfs.Mount(vpath.resolve(Harness.IO.getWorkspaceRoot(), "built/local"), resolver),
|
||||
["/src"]: new vfs.Mount(vpath.resolve(Harness.IO.getWorkspaceRoot(), root), resolver)
|
||||
},
|
||||
cwd: "/",
|
||||
meta: { defaultLibLocation: "/lib" },
|
||||
time
|
||||
});
|
||||
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"
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
namespace ts {
|
||||
describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => {
|
||||
let outFileFs: vfs.FileSystem;
|
||||
const { time, tick } = getTime();
|
||||
const enum ext { js, jsmap, dts, dtsmap, buildinfo }
|
||||
const enum project { lib, app }
|
||||
type OutputFile = [string, string, string, string, string];
|
||||
function relName(path: string) { return path.slice(1); }
|
||||
const outputFiles: [OutputFile, OutputFile] = [
|
||||
[
|
||||
"/src/lib/module.js",
|
||||
"/src/lib/module.js.map",
|
||||
"/src/lib/module.d.ts",
|
||||
"/src/lib/module.d.ts.map",
|
||||
"/src/lib/module.tsbuildinfo"
|
||||
],
|
||||
[
|
||||
"/src/app/module.js",
|
||||
"/src/app/module.js.map",
|
||||
"/src/app/module.d.ts",
|
||||
"/src/app/module.d.ts.map",
|
||||
"/src/app/module.tsbuildinfo"
|
||||
]
|
||||
];
|
||||
type Sources = [string, ReadonlyArray<string>];
|
||||
const enum source { config, ts }
|
||||
const sources: [Sources, Sources] = [
|
||||
[
|
||||
"/src/lib/tsconfig.json",
|
||||
[
|
||||
"/src/lib/file0.ts",
|
||||
"/src/lib/file1.ts",
|
||||
"/src/lib/file2.ts",
|
||||
"/src/lib/global.ts",
|
||||
]
|
||||
],
|
||||
[
|
||||
"/src/app/tsconfig.json",
|
||||
[
|
||||
"/src/app/file3.ts",
|
||||
"/src/app/file4.ts"
|
||||
]
|
||||
]
|
||||
];
|
||||
before(() => {
|
||||
outFileFs = loadProjectFromDisk("tests/projects/amdModulesWithOut", time);
|
||||
});
|
||||
after(() => {
|
||||
outFileFs = undefined!;
|
||||
});
|
||||
|
||||
interface VerifyOutFileScenarioInput {
|
||||
scenario: string;
|
||||
modifyFs: (fs: vfs.FileSystem) => void;
|
||||
modifyAgainFs?: (fs: vfs.FileSystem) => void;
|
||||
}
|
||||
|
||||
function verifyOutFileScenario({
|
||||
scenario,
|
||||
modifyFs,
|
||||
modifyAgainFs
|
||||
}: VerifyOutFileScenarioInput) {
|
||||
verifyTsbuildOutput({
|
||||
scenario,
|
||||
projFs: () => outFileFs,
|
||||
time,
|
||||
tick,
|
||||
proj: "amdModulesWithOut",
|
||||
rootNames: ["/src/app"],
|
||||
expectedMapFileNames: [
|
||||
outputFiles[project.lib][ext.jsmap],
|
||||
outputFiles[project.lib][ext.dtsmap],
|
||||
outputFiles[project.app][ext.jsmap],
|
||||
outputFiles[project.app][ext.dtsmap],
|
||||
],
|
||||
expectedBuildInfoFilesForSectionBaselines: [
|
||||
[outputFiles[project.lib][ext.buildinfo], outputFiles[project.lib][ext.js], outputFiles[project.lib][ext.dts]],
|
||||
[outputFiles[project.app][ext.buildinfo], outputFiles[project.app][ext.js], outputFiles[project.app][ext.dts]]
|
||||
],
|
||||
lastProjectOutputJs: outputFiles[project.app][ext.js],
|
||||
initialBuild: {
|
||||
modifyFs
|
||||
},
|
||||
incrementalDtsUnchangedBuild: {
|
||||
modifyFs: fs => appendText(fs, relName(sources[project.lib][source.ts][1]), "console.log(x);")
|
||||
},
|
||||
incrementalHeaderChangedBuild: modifyAgainFs ? {
|
||||
modifyFs: modifyAgainFs
|
||||
} : undefined,
|
||||
outputFiles: [
|
||||
...outputFiles[project.lib],
|
||||
...outputFiles[project.app]
|
||||
],
|
||||
baselineOnly: true
|
||||
});
|
||||
}
|
||||
|
||||
describe("Prepend output with .tsbuildinfo", () => {
|
||||
verifyOutFileScenario({
|
||||
scenario: "modules and globals mixed in amd",
|
||||
modifyFs: noop
|
||||
});
|
||||
|
||||
// Prologues
|
||||
describe("Prologues", () => {
|
||||
verifyOutFileScenario({
|
||||
scenario: "multiple prologues in all projects",
|
||||
modifyFs: fs => {
|
||||
enableStrict(fs, sources[project.lib][source.config]);
|
||||
addTestPrologue(fs, sources[project.lib][source.ts][0], `"myPrologue"`);
|
||||
addTestPrologue(fs, sources[project.lib][source.ts][2], `"myPrologueFile"`);
|
||||
addTestPrologue(fs, sources[project.lib][source.ts][3], `"myPrologue3"`);
|
||||
enableStrict(fs, sources[project.app][source.config]);
|
||||
addTestPrologue(fs, sources[project.app][source.ts][0], `"myPrologue"`);
|
||||
addTestPrologue(fs, sources[project.app][source.ts][1], `"myPrologue2";`);
|
||||
},
|
||||
modifyAgainFs: fs => addTestPrologue(fs, relName(sources[project.lib][source.ts][1]), `"myPrologue5"`)
|
||||
});
|
||||
});
|
||||
|
||||
// Shebang
|
||||
describe("Shebang", () => {
|
||||
// changes declaration because its emitted in .d.ts file
|
||||
verifyOutFileScenario({
|
||||
scenario: "shebang in all projects",
|
||||
modifyFs: fs => {
|
||||
addShebang(fs, "lib", "file0");
|
||||
addShebang(fs, "lib", "file1");
|
||||
addShebang(fs, "app", "file3");
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// emitHelpers
|
||||
describe("emitHelpers", () => {
|
||||
verifyOutFileScenario({
|
||||
scenario: "multiple emitHelpers in all projects",
|
||||
modifyFs: fs => {
|
||||
addSpread(fs, "lib", "file0");
|
||||
addRest(fs, "lib", "file1");
|
||||
addRest(fs, "app", "file3");
|
||||
addSpread(fs, "app", "file4");
|
||||
},
|
||||
modifyAgainFs: fs => removeRest(fs, "lib", "file1")
|
||||
});
|
||||
});
|
||||
|
||||
// triple slash refs
|
||||
describe("triple slash refs", () => {
|
||||
// changes declaration because its emitted in .d.ts file
|
||||
verifyOutFileScenario({
|
||||
scenario: "triple slash refs in all projects",
|
||||
modifyFs: fs => {
|
||||
addTripleSlashRef(fs, "lib", "file0");
|
||||
addTripleSlashRef(fs, "app", "file4");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("stripInternal", () => {
|
||||
function stripInternalScenario(fs: vfs.FileSystem) {
|
||||
const internal = "/*@internal*/";
|
||||
replaceText(fs, sources[project.app][source.config], `"composite": true,`, `"composite": true,
|
||||
"stripInternal": true,`);
|
||||
replaceText(fs, sources[project.lib][source.ts][0], "const", `${internal} const`);
|
||||
appendText(fs, sources[project.lib][source.ts][1], `
|
||||
export class normalC {
|
||||
${internal} constructor() { }
|
||||
${internal} prop: string;
|
||||
${internal} method() { }
|
||||
${internal} get c() { return 10; }
|
||||
${internal} set c(val: number) { }
|
||||
}
|
||||
export namespace normalN {
|
||||
${internal} export class C { }
|
||||
${internal} export function foo() {}
|
||||
${internal} export namespace someNamespace { export class C {} }
|
||||
${internal} export namespace someOther.something { export class someClass {} }
|
||||
${internal} export import someImport = someNamespace.C;
|
||||
${internal} export type internalType = internalC;
|
||||
${internal} export const internalConst = 10;
|
||||
${internal} export enum internalEnum { a, b, c }
|
||||
}
|
||||
${internal} export class internalC {}
|
||||
${internal} export function internalfoo() {}
|
||||
${internal} export namespace internalNamespace { export class someClass {} }
|
||||
${internal} export namespace internalOther.something { export class someClass {} }
|
||||
${internal} export import internalImport = internalNamespace.someClass;
|
||||
${internal} export type internalType = internalC;
|
||||
${internal} export const internalConst = 10;
|
||||
${internal} export enum internalEnum { a, b, c }`);
|
||||
}
|
||||
|
||||
// Verify initial + incremental edits
|
||||
verifyOutFileScenario({
|
||||
scenario: "stripInternal",
|
||||
modifyFs: stripInternalScenario,
|
||||
modifyAgainFs: fs => replaceText(fs, sources[project.lib][source.ts][1], `export const`, `/*@internal*/ export const`),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
namespace ts {
|
||||
const projFs = loadProjectFromDisk("tests/projects/empty-files");
|
||||
|
||||
const allExpectedOutputs = [
|
||||
"/src/core/index.js",
|
||||
"/src/core/index.d.ts",
|
||||
"/src/core/index.d.ts.map",
|
||||
];
|
||||
|
||||
describe("unittests:: tsbuild - empty files option in tsconfig", () => {
|
||||
it("has empty files diagnostic when files is empty and no references are provided", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/no-references"], { dry: false, force: false, verbose: false });
|
||||
|
||||
host.clearDiagnostics();
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages([Diagnostics.The_files_list_in_config_file_0_is_empty, "/src/no-references/tsconfig.json"]);
|
||||
|
||||
// Check for outputs to not be written.
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(!fs.existsSync(output), `Expect file ${output} to not exist`);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not have empty files diagnostic when files is empty and references are provided", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/with-references"], { dry: false, force: false, verbose: false });
|
||||
|
||||
host.clearDiagnostics();
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*empty*/);
|
||||
|
||||
// Check for outputs to be written.
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
namespace ts {
|
||||
describe("unittests:: tsbuild - graph-ordering", () => {
|
||||
let host: fakes.SolutionBuilderHost | undefined;
|
||||
const deps: [string, string][] = [
|
||||
["A", "B"],
|
||||
["B", "C"],
|
||||
["A", "C"],
|
||||
["B", "D"],
|
||||
["C", "D"],
|
||||
["C", "E"],
|
||||
["F", "E"]
|
||||
];
|
||||
|
||||
before(() => {
|
||||
const fs = new vfs.FileSystem(false);
|
||||
host = new fakes.SolutionBuilderHost(fs);
|
||||
writeProjects(fs, ["A", "B", "C", "D", "E", "F", "G"], deps);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
host = undefined;
|
||||
});
|
||||
|
||||
it("orders the graph correctly - specify two roots", () => {
|
||||
checkGraphOrdering(["A", "G"], ["A", "B", "C", "D", "E", "G"]);
|
||||
});
|
||||
|
||||
it("orders the graph correctly - multiple parts of the same graph in various orders", () => {
|
||||
checkGraphOrdering(["A"], ["A", "B", "C", "D", "E"]);
|
||||
checkGraphOrdering(["A", "C", "D"], ["A", "B", "C", "D", "E"]);
|
||||
checkGraphOrdering(["D", "C", "A"], ["A", "B", "C", "D", "E"]);
|
||||
});
|
||||
|
||||
it("orders the graph correctly - other orderings", () => {
|
||||
checkGraphOrdering(["F"], ["F", "E"]);
|
||||
checkGraphOrdering(["E"], ["E"]);
|
||||
checkGraphOrdering(["F", "C", "A"], ["A", "B", "C", "D", "E", "F"]);
|
||||
});
|
||||
|
||||
function checkGraphOrdering(rootNames: string[], expectedBuildSet: string[]) {
|
||||
const builder = createSolutionBuilder(host!, rootNames, { dry: true, force: false, verbose: false });
|
||||
|
||||
const projFileNames = rootNames.map(getProjectFileName);
|
||||
const graph = builder.getBuildGraph(projFileNames);
|
||||
|
||||
assert.sameMembers(graph.buildQueue, expectedBuildSet.map(getProjectFileName));
|
||||
|
||||
for (const dep of deps) {
|
||||
const child = getProjectFileName(dep[0]);
|
||||
if (graph.buildQueue.indexOf(child) < 0) continue;
|
||||
const parent = getProjectFileName(dep[1]);
|
||||
assert.isAbove(graph.buildQueue.indexOf(child), graph.buildQueue.indexOf(parent), `Expecting child ${child} to be built after parent ${parent}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getProjectFileName(proj: string) {
|
||||
return `/project/${proj}/tsconfig.json` as ResolvedConfigFileName;
|
||||
}
|
||||
|
||||
function writeProjects(fileSystem: vfs.FileSystem, projectNames: string[], deps: [string, string][]): string[] {
|
||||
const projFileNames: string[] = [];
|
||||
for (const dep of deps) {
|
||||
if (projectNames.indexOf(dep[0]) < 0) throw new Error(`Invalid dependency - project ${dep[0]} does not exist`);
|
||||
if (projectNames.indexOf(dep[1]) < 0) throw new Error(`Invalid dependency - project ${dep[1]} does not exist`);
|
||||
}
|
||||
for (const proj of projectNames) {
|
||||
fileSystem.mkdirpSync(`/project/${proj}`);
|
||||
fileSystem.writeFileSync(`/project/${proj}/${proj}.ts`, "export {}");
|
||||
const configFileName = getProjectFileName(proj);
|
||||
const configContent = JSON.stringify({
|
||||
compilerOptions: { composite: true },
|
||||
files: [`./${proj}.ts`],
|
||||
references: deps.filter(d => d[0] === proj).map(d => ({ path: `../${d[1]}` }))
|
||||
}, undefined, 2);
|
||||
fileSystem.writeFileSync(configFileName, configContent);
|
||||
projFileNames.push(configFileName);
|
||||
}
|
||||
return projFileNames;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
namespace ts {
|
||||
export function getExpectedDiagnosticForProjectsInBuild(...projects: string[]): fakes.ExpectedDiagnostic {
|
||||
return [Diagnostics.Projects_in_this_build_Colon_0, projects.map(p => "\r\n * " + p).join("")];
|
||||
}
|
||||
|
||||
export function changeCompilerVersion(host: fakes.SolutionBuilderHost) {
|
||||
const originalReadFile = host.readFile;
|
||||
host.readFile = path => {
|
||||
const value = originalReadFile.call(host, path);
|
||||
if (!value || !isBuildInfoFile(path)) return value;
|
||||
const buildInfo = getBuildInfo(value);
|
||||
buildInfo.version = fakes.version;
|
||||
return getBuildInfoText(buildInfo);
|
||||
};
|
||||
}
|
||||
|
||||
export function replaceText(fs: vfs.FileSystem, path: string, oldText: string, newText: string) {
|
||||
if (!fs.statSync(path).isFile()) {
|
||||
throw new Error(`File ${path} does not exist`);
|
||||
}
|
||||
const old = fs.readFileSync(path, "utf-8");
|
||||
if (old.indexOf(oldText) < 0) {
|
||||
throw new Error(`Text "${oldText}" does not exist in file ${path}`);
|
||||
}
|
||||
const newContent = old.replace(oldText, newText);
|
||||
fs.writeFileSync(path, newContent, "utf-8");
|
||||
}
|
||||
|
||||
export function prependText(fs: vfs.FileSystem, path: string, additionalContent: string) {
|
||||
if (!fs.statSync(path).isFile()) {
|
||||
throw new Error(`File ${path} does not exist`);
|
||||
}
|
||||
const old = fs.readFileSync(path, "utf-8");
|
||||
fs.writeFileSync(path, `${additionalContent}${old}`, "utf-8");
|
||||
}
|
||||
|
||||
export function appendText(fs: vfs.FileSystem, path: string, additionalContent: string) {
|
||||
if (!fs.statSync(path).isFile()) {
|
||||
throw new Error(`File ${path} does not exist`);
|
||||
}
|
||||
const old = fs.readFileSync(path, "utf-8");
|
||||
fs.writeFileSync(path, `${old}${additionalContent}`);
|
||||
}
|
||||
|
||||
export function getTime() {
|
||||
let currentTime = 100;
|
||||
return { tick, time, touch };
|
||||
|
||||
function tick() {
|
||||
currentTime += 60_000;
|
||||
}
|
||||
|
||||
function time() {
|
||||
return currentTime;
|
||||
}
|
||||
|
||||
function touch(fs: vfs.FileSystem, path: string) {
|
||||
if (!fs.statSync(path).isFile()) {
|
||||
throw new Error(`File ${path} does not exist`);
|
||||
}
|
||||
fs.utimesSync(path, new Date(time()), new Date(time()));
|
||||
}
|
||||
}
|
||||
|
||||
export function loadProjectFromDisk(root: string, time?: vfs.FileSystemOptions["time"]): vfs.FileSystem {
|
||||
const resolver = vfs.createResolver(Harness.IO);
|
||||
const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
|
||||
files: {
|
||||
["/lib"]: new vfs.Mount(vpath.resolve(Harness.IO.getWorkspaceRoot(), "built/local"), resolver),
|
||||
["/src"]: new vfs.Mount(vpath.resolve(Harness.IO.getWorkspaceRoot(), root), resolver)
|
||||
},
|
||||
cwd: "/",
|
||||
meta: { defaultLibLocation: "/lib" },
|
||||
time
|
||||
});
|
||||
fs.makeReadonly();
|
||||
return fs;
|
||||
}
|
||||
|
||||
export 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 generateSourceMapBaselineFiles(fs: vfs.FileSystem, mapFileNames: ReadonlyArray<string>) {
|
||||
for (const mapFile of mapFileNames) {
|
||||
if (!fs.existsSync(mapFile)) continue;
|
||||
const text = Harness.SourceMapRecorder.getSourceMapRecordWithVFS(fs, mapFile);
|
||||
fs.writeFileSync(`${mapFile}.baseline.txt`, text);
|
||||
}
|
||||
}
|
||||
|
||||
// [tsbuildinfo, js, dts]
|
||||
export type BuildInfoSectionBaselineFiles = [string, string | undefined, string | undefined];
|
||||
function generateBuildInfoSectionBaselineFiles(fs: vfs.FileSystem, buildInfoFileNames: ReadonlyArray<BuildInfoSectionBaselineFiles>) {
|
||||
for (const [file, jsFile, dtsFile] of buildInfoFileNames) {
|
||||
if (!fs.existsSync(file)) continue;
|
||||
|
||||
const buildInfo = getBuildInfo(fs.readFileSync(file, "utf8"));
|
||||
const bundle = buildInfo.bundle;
|
||||
if (!bundle || (!length(bundle.js && bundle.js.sections) && !length(bundle.dts && bundle.dts.sections))) continue;
|
||||
|
||||
// Write the baselines:
|
||||
const baselineRecorder = new Harness.Compiler.WriterAggregator();
|
||||
generateBundleFileSectionInfo(fs, baselineRecorder, bundle.js, jsFile);
|
||||
generateBundleFileSectionInfo(fs, baselineRecorder, bundle.dts, dtsFile);
|
||||
baselineRecorder.Close();
|
||||
|
||||
const text = baselineRecorder.lines.join("\r\n");
|
||||
fs.writeFileSync(`${file}.baseline.txt`, text, "utf8");
|
||||
}
|
||||
}
|
||||
|
||||
function generateBundleFileSectionInfo(fs: vfs.FileSystem, baselineRecorder: Harness.Compiler.WriterAggregator, bundleFileInfo: BundleFileInfo | undefined, outFile: string | undefined) {
|
||||
if (!length(bundleFileInfo && bundleFileInfo.sections) && !outFile) return; // Nothing to baseline
|
||||
|
||||
const content = outFile && fs.existsSync(outFile) ? fs.readFileSync(outFile, "utf8") : "";
|
||||
baselineRecorder.WriteLine("======================================================================");
|
||||
baselineRecorder.WriteLine(`File:: ${outFile}`);
|
||||
for (const section of bundleFileInfo ? bundleFileInfo.sections : emptyArray) {
|
||||
baselineRecorder.WriteLine("----------------------------------------------------------------------");
|
||||
writeSectionHeader(section);
|
||||
if (section.kind !== BundleFileSectionKind.Prepend) {
|
||||
writeTextOfSection(section.pos, section.end);
|
||||
}
|
||||
else {
|
||||
Debug.assert(section.pos === first(section.texts).pos);
|
||||
Debug.assert(section.end === last(section.texts).end);
|
||||
for (const text of section.texts) {
|
||||
baselineRecorder.WriteLine(">>--------------------------------------------------------------------");
|
||||
writeSectionHeader(text);
|
||||
writeTextOfSection(text.pos, text.end);
|
||||
}
|
||||
}
|
||||
}
|
||||
baselineRecorder.WriteLine("======================================================================");
|
||||
|
||||
function writeTextOfSection(pos: number, end: number) {
|
||||
const textLines = content.substring(pos, end).split(/\r?\n/);
|
||||
for (const line of textLines) {
|
||||
baselineRecorder.WriteLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
function writeSectionHeader(section: BundleFileSection) {
|
||||
baselineRecorder.WriteLine(`${section.kind}: (${section.pos}-${section.end})${section.data ? ":: " + section.data : ""}${section.kind === BundleFileSectionKind.Prepend ? " texts:: " + section.texts.length : ""}`);
|
||||
}
|
||||
}
|
||||
|
||||
interface BuildInput {
|
||||
fs: vfs.FileSystem;
|
||||
tick: () => void;
|
||||
rootNames: ReadonlyArray<string>;
|
||||
expectedMapFileNames: ReadonlyArray<string>;
|
||||
expectedBuildInfoFilesForSectionBaselines?: ReadonlyArray<BuildInfoSectionBaselineFiles>;
|
||||
modifyFs: (fs: vfs.FileSystem) => void;
|
||||
}
|
||||
|
||||
function build({ fs, tick, rootNames, expectedMapFileNames, expectedBuildInfoFilesForSectionBaselines, modifyFs }: BuildInput) {
|
||||
const actualReadFileMap = createMap<number>();
|
||||
modifyFs(fs);
|
||||
tick();
|
||||
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, rootNames, { dry: false, force: false, verbose: true });
|
||||
host.clearDiagnostics();
|
||||
const originalReadFile = host.readFile;
|
||||
host.readFile = path => {
|
||||
// Dont record libs
|
||||
if (path.startsWith("/src/")) {
|
||||
actualReadFileMap.set(path, (actualReadFileMap.get(path) || 0) + 1);
|
||||
}
|
||||
return originalReadFile.call(host, path);
|
||||
};
|
||||
builder.buildAllProjects();
|
||||
generateSourceMapBaselineFiles(fs, expectedMapFileNames);
|
||||
generateBuildInfoSectionBaselineFiles(fs, expectedBuildInfoFilesForSectionBaselines || emptyArray);
|
||||
fs.makeReadonly();
|
||||
return { fs, actualReadFileMap, host, builder };
|
||||
}
|
||||
|
||||
function generateBaseline(fs: vfs.FileSystem, proj: string, scenario: string, subScenario: string, baseFs: vfs.FileSystem) {
|
||||
const patch = fs.diff(baseFs);
|
||||
// tslint:disable-next-line:no-null-keyword
|
||||
Harness.Baseline.runBaseline(`tsbuild/${proj}/${subScenario.split(" ").join("-")}/${scenario.split(" ").join("-")}.js`, patch ? vfs.formatPatch(patch) : null);
|
||||
}
|
||||
|
||||
function verifyReadFileCalls(actualReadFileMap: Map<number>, expectedReadFiles: ReadonlyMap<number>) {
|
||||
TestFSWithWatch.verifyMapSize("readFileCalls", actualReadFileMap, arrayFrom(expectedReadFiles.keys()));
|
||||
expectedReadFiles.forEach((expected, expectedFile) => {
|
||||
const actual = actualReadFileMap.get(expectedFile);
|
||||
assert.equal(actual, expected, `Mismatch in read file call number for: ${expectedFile}
|
||||
Not in Actual: ${JSON.stringify(arrayFrom(mapDefinedIterator(expectedReadFiles.keys(), f => actualReadFileMap.has(f) ? undefined : f)))}
|
||||
Mismatch Actual(path, actual, expected): ${JSON.stringify(arrayFrom(mapDefinedIterator(actualReadFileMap.entries(),
|
||||
([p, v]) => expectedReadFiles.get(p) !== v ? [p, v, expectedReadFiles.get(p) || 0] : undefined)))}`);
|
||||
});
|
||||
}
|
||||
|
||||
export function getReadFilesMap(filesReadOnce: ReadonlyArray<string>, ...filesWithTwoReadCalls: string[]) {
|
||||
const map = arrayToMap(filesReadOnce, identity, () => 1);
|
||||
for (const fileWithTwoReadCalls of filesWithTwoReadCalls) {
|
||||
map.set(fileWithTwoReadCalls, 2);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export interface ExpectedBuildOutput {
|
||||
expectedDiagnostics?: ReadonlyArray<fakes.ExpectedDiagnostic>;
|
||||
expectedReadFiles?: ReadonlyMap<number>;
|
||||
}
|
||||
|
||||
export interface BuildState extends ExpectedBuildOutput {
|
||||
modifyFs: (fs: vfs.FileSystem) => void;
|
||||
}
|
||||
|
||||
export interface VerifyTsBuildInput {
|
||||
scenario: string;
|
||||
projFs: () => vfs.FileSystem;
|
||||
time: () => number;
|
||||
tick: () => void;
|
||||
proj: string;
|
||||
rootNames: ReadonlyArray<string>;
|
||||
expectedMapFileNames: ReadonlyArray<string>;
|
||||
expectedBuildInfoFilesForSectionBaselines?: ReadonlyArray<BuildInfoSectionBaselineFiles>;
|
||||
lastProjectOutputJs: string;
|
||||
initialBuild: BuildState;
|
||||
outputFiles?: ReadonlyArray<string>;
|
||||
incrementalDtsChangedBuild?: BuildState;
|
||||
incrementalDtsUnchangedBuild?: BuildState;
|
||||
incrementalHeaderChangedBuild?: BuildState;
|
||||
baselineOnly?: true;
|
||||
}
|
||||
|
||||
export function verifyTsbuildOutput({
|
||||
scenario, projFs, time, tick, proj, rootNames, outputFiles, baselineOnly,
|
||||
expectedMapFileNames, expectedBuildInfoFilesForSectionBaselines, lastProjectOutputJs,
|
||||
initialBuild, incrementalDtsChangedBuild, incrementalDtsUnchangedBuild, incrementalHeaderChangedBuild
|
||||
}: VerifyTsBuildInput) {
|
||||
describe(`tsc --b ${proj}:: ${scenario}`, () => {
|
||||
let fs: vfs.FileSystem;
|
||||
let actualReadFileMap: Map<number>;
|
||||
let firstBuildTime: number;
|
||||
let host: fakes.SolutionBuilderHost;
|
||||
before(() => {
|
||||
const result = build({
|
||||
fs: projFs().shadow(),
|
||||
tick,
|
||||
rootNames,
|
||||
expectedMapFileNames,
|
||||
expectedBuildInfoFilesForSectionBaselines,
|
||||
modifyFs: initialBuild.modifyFs,
|
||||
});
|
||||
({ fs, actualReadFileMap, host } = result);
|
||||
firstBuildTime = time();
|
||||
});
|
||||
after(() => {
|
||||
fs = undefined!;
|
||||
actualReadFileMap = undefined!;
|
||||
host = undefined!;
|
||||
});
|
||||
describe("initialBuild", () => {
|
||||
if (!baselineOnly) {
|
||||
it(`verify diagnostics`, () => {
|
||||
host.assertDiagnosticMessages(...(initialBuild.expectedDiagnostics || emptyArray));
|
||||
});
|
||||
}
|
||||
it(`Generates files matching the baseline`, () => {
|
||||
generateBaseline(fs, proj, scenario, "initial Build", projFs());
|
||||
});
|
||||
if (!baselineOnly) {
|
||||
it("verify readFile calls", () => {
|
||||
verifyReadFileCalls(actualReadFileMap, Debug.assertDefined(initialBuild.expectedReadFiles));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function incrementalBuild(subScenario: string, incrementalModifyFs: (fs: vfs.FileSystem) => void, incrementalExpectedDiagnostics: ReadonlyArray<fakes.ExpectedDiagnostic> | undefined, incrementalExpectedReadFiles: ReadonlyMap<number> | undefined) {
|
||||
describe(subScenario, () => {
|
||||
let newFs: vfs.FileSystem;
|
||||
let actualReadFileMap: Map<number>;
|
||||
let host: fakes.SolutionBuilderHost;
|
||||
before(() => {
|
||||
assert.equal(fs.statSync(lastProjectOutputJs).mtimeMs, firstBuildTime, "First build timestamp is correct");
|
||||
tick();
|
||||
newFs = fs.shadow();
|
||||
tick();
|
||||
({ actualReadFileMap, host } = build({
|
||||
fs: newFs,
|
||||
tick,
|
||||
rootNames,
|
||||
expectedMapFileNames,
|
||||
expectedBuildInfoFilesForSectionBaselines,
|
||||
modifyFs: incrementalModifyFs,
|
||||
}));
|
||||
assert.equal(newFs.statSync(lastProjectOutputJs).mtimeMs, time(), "Second build timestamp is correct");
|
||||
});
|
||||
after(() => {
|
||||
newFs = undefined!;
|
||||
actualReadFileMap = undefined!;
|
||||
host = undefined!;
|
||||
});
|
||||
if (!baselineOnly) {
|
||||
it(`verify diagnostics`, () => {
|
||||
host.assertDiagnosticMessages(...(incrementalExpectedDiagnostics || emptyArray));
|
||||
});
|
||||
}
|
||||
it(`Generates files matching the baseline`, () => {
|
||||
generateBaseline(newFs, proj, scenario, subScenario, fs);
|
||||
});
|
||||
if (!baselineOnly) {
|
||||
it("verify readFile calls", () => {
|
||||
verifyReadFileCalls(actualReadFileMap, Debug.assertDefined(incrementalExpectedReadFiles));
|
||||
});
|
||||
}
|
||||
it(`Verify emit output file text is same when built clean`, () => {
|
||||
const expectedOutputFiles = Debug.assertDefined(outputFiles);
|
||||
const { fs } = build({
|
||||
fs: newFs.shadow(),
|
||||
tick,
|
||||
rootNames,
|
||||
expectedMapFileNames: emptyArray,
|
||||
modifyFs: fs => {
|
||||
// Delete output files
|
||||
for (const outputFile of expectedOutputFiles) {
|
||||
if (fs.existsSync(outputFile)) {
|
||||
fs.rimrafSync(outputFile);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
for (const outputFile of expectedOutputFiles) {
|
||||
const expectedText = fs.existsSync(outputFile) ? fs.readFileSync(outputFile, "utf8") : undefined;
|
||||
const actualText = newFs.existsSync(outputFile) ? newFs.readFileSync(outputFile, "utf8") : undefined;
|
||||
assert.equal(actualText, expectedText, `File: ${outputFile}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
if (incrementalDtsChangedBuild) {
|
||||
incrementalBuild(
|
||||
"incremental declaration changes",
|
||||
incrementalDtsChangedBuild.modifyFs,
|
||||
incrementalDtsChangedBuild.expectedDiagnostics,
|
||||
incrementalDtsChangedBuild.expectedReadFiles,
|
||||
);
|
||||
}
|
||||
|
||||
if (incrementalDtsUnchangedBuild) {
|
||||
incrementalBuild(
|
||||
"incremental declaration doesnt change",
|
||||
incrementalDtsUnchangedBuild.modifyFs,
|
||||
incrementalDtsUnchangedBuild.expectedDiagnostics,
|
||||
incrementalDtsUnchangedBuild.expectedReadFiles
|
||||
);
|
||||
}
|
||||
|
||||
if (incrementalHeaderChangedBuild) {
|
||||
incrementalBuild(
|
||||
"incremental headers change without dts changes",
|
||||
incrementalHeaderChangedBuild.modifyFs,
|
||||
incrementalHeaderChangedBuild.expectedDiagnostics,
|
||||
incrementalHeaderChangedBuild.expectedReadFiles
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function enableStrict(fs: vfs.FileSystem, path: string) {
|
||||
replaceText(fs, path, `"strict": false`, `"strict": true`);
|
||||
}
|
||||
|
||||
export function addTestPrologue(fs: vfs.FileSystem, path: string, prologue: string) {
|
||||
prependText(fs, path, `${prologue}
|
||||
`);
|
||||
}
|
||||
|
||||
export function addShebang(fs: vfs.FileSystem, project: string, file: string) {
|
||||
prependText(fs, `src/${project}/${file}.ts`, `#!someshebang ${project} ${file}
|
||||
`);
|
||||
}
|
||||
|
||||
export function restContent(project: string, file: string) {
|
||||
return `function for${project}${file}Rest() {
|
||||
const { b, ...rest } = { a: 10, b: 30, yy: 30 };
|
||||
}`;
|
||||
}
|
||||
|
||||
function nonrestContent(project: string, file: string) {
|
||||
return `function for${project}${file}Rest() { }`;
|
||||
}
|
||||
|
||||
export function addRest(fs: vfs.FileSystem, project: string, file: string) {
|
||||
appendText(fs, `src/${project}/${file}.ts`, restContent(project, file));
|
||||
}
|
||||
|
||||
export function removeRest(fs: vfs.FileSystem, project: string, file: string) {
|
||||
replaceText(fs, `src/${project}/${file}.ts`, restContent(project, file), nonrestContent(project, file));
|
||||
}
|
||||
|
||||
export function addStubFoo(fs: vfs.FileSystem, project: string, file: string) {
|
||||
appendText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file));
|
||||
}
|
||||
|
||||
export function changeStubToRest(fs: vfs.FileSystem, project: string, file: string) {
|
||||
replaceText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file), restContent(project, file));
|
||||
}
|
||||
|
||||
export function addSpread(fs: vfs.FileSystem, project: string, file: string) {
|
||||
const path = `src/${project}/${file}.ts`;
|
||||
const content = fs.readFileSync(path, "utf8");
|
||||
fs.writeFileSync(path, `${content}
|
||||
function ${project}${file}Spread(...b: number[]) { }
|
||||
${project}${file}Spread(...[10, 20, 30]);`);
|
||||
|
||||
replaceText(fs, `src/${project}/tsconfig.json`, `"strict": false,`, `"strict": false,
|
||||
"downlevelIteration": true,`);
|
||||
}
|
||||
|
||||
export function getTripleSlashRef(project: string) {
|
||||
return `/src/${project}/tripleRef.d.ts`;
|
||||
}
|
||||
|
||||
export function addTripleSlashRef(fs: vfs.FileSystem, project: string, file: string) {
|
||||
fs.writeFileSync(getTripleSlashRef(project), `declare class ${project}${file} { }`);
|
||||
prependText(fs, `src/${project}/${file}.ts`, `///<reference path="./tripleRef.d.ts"/>
|
||||
const ${file}Const = new ${project}${file}();
|
||||
`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace ts {
|
||||
describe("unittests:: tsbuild:: when tsconfig extends the missing file", () => {
|
||||
it("unittests:: tsbuild - when tsconfig extends the missing file", () => {
|
||||
const projFs = loadProjectFromDisk("tests/projects/missingExtendedConfig");
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tsconfig.json"], {});
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
[Diagnostics.The_specified_path_does_not_exist_Colon_0, "/src/foobar.json"],
|
||||
[Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, "/src/tsconfig.first.json", "[\"**/*\"]", "[]"],
|
||||
[Diagnostics.The_specified_path_does_not_exist_Colon_0, "/src/foobar.json"],
|
||||
[Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, "/src/tsconfig.second.json", "[\"**/*\"]", "[]"]
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,860 @@
|
||||
namespace ts {
|
||||
describe("unittests:: tsbuild:: outFile::", () => {
|
||||
let outFileFs: vfs.FileSystem;
|
||||
const enum ext { js, jsmap, dts, dtsmap, buildinfo }
|
||||
const enum project { first, second, third }
|
||||
type OutputFile = [string, string, string, string, string];
|
||||
function relName(path: string) { return path.slice(1); }
|
||||
const outputFiles: [OutputFile, OutputFile, OutputFile] = [
|
||||
[
|
||||
"/src/first/bin/first-output.js",
|
||||
"/src/first/bin/first-output.js.map",
|
||||
"/src/first/bin/first-output.d.ts",
|
||||
"/src/first/bin/first-output.d.ts.map",
|
||||
"/src/first/bin/first-output.tsbuildinfo"
|
||||
],
|
||||
[
|
||||
"/src/2/second-output.js",
|
||||
"/src/2/second-output.js.map",
|
||||
"/src/2/second-output.d.ts",
|
||||
"/src/2/second-output.d.ts.map",
|
||||
"/src/2/second-output.tsbuildinfo"
|
||||
],
|
||||
[
|
||||
"/src/third/thirdjs/output/third-output.js",
|
||||
"/src/third/thirdjs/output/third-output.js.map",
|
||||
"/src/third/thirdjs/output/third-output.d.ts",
|
||||
"/src/third/thirdjs/output/third-output.d.ts.map",
|
||||
"/src/third/thirdjs/output/third-output.tsbuildinfo"
|
||||
]
|
||||
];
|
||||
const relOutputFiles = outputFiles.map(v => v.map(relName)) as [OutputFile, OutputFile, OutputFile];
|
||||
type Sources = [string, ReadonlyArray<string>];
|
||||
const enum source { config, ts }
|
||||
const enum part { one, two, three }
|
||||
const sources: [Sources, Sources, Sources] = [
|
||||
[
|
||||
"/src/first/tsconfig.json",
|
||||
[
|
||||
"/src/first/first_PART1.ts",
|
||||
"/src/first/first_part2.ts",
|
||||
"/src/first/first_part3.ts"
|
||||
]
|
||||
],
|
||||
[
|
||||
"/src/second/tsconfig.json",
|
||||
[
|
||||
"/src/second/second_part1.ts",
|
||||
"/src/second/second_part2.ts"
|
||||
]
|
||||
],
|
||||
[
|
||||
"/src/third/tsconfig.json",
|
||||
[
|
||||
"/src/third/third_part1.ts"
|
||||
]
|
||||
]
|
||||
];
|
||||
const expectedMapFileNames = [
|
||||
outputFiles[project.first][ext.jsmap],
|
||||
outputFiles[project.first][ext.dtsmap],
|
||||
outputFiles[project.second][ext.jsmap],
|
||||
outputFiles[project.second][ext.dtsmap],
|
||||
outputFiles[project.third][ext.jsmap],
|
||||
outputFiles[project.third][ext.dtsmap]
|
||||
];
|
||||
const expectedTsbuildInfoFileNames: ReadonlyArray<BuildInfoSectionBaselineFiles> = [
|
||||
[outputFiles[project.first][ext.buildinfo], outputFiles[project.first][ext.js], outputFiles[project.first][ext.dts]],
|
||||
[outputFiles[project.second][ext.buildinfo], outputFiles[project.second][ext.js], outputFiles[project.second][ext.dts]],
|
||||
[outputFiles[project.third][ext.buildinfo], outputFiles[project.third][ext.js], outputFiles[project.third][ext.dts]]
|
||||
];
|
||||
const relSources = sources.map(([config, sources]) => [relName(config), sources.map(relName)]) as any as [Sources, Sources, Sources];
|
||||
const { time, tick } = getTime();
|
||||
let expectedOutputFiles = [
|
||||
...outputFiles[project.first],
|
||||
...outputFiles[project.second],
|
||||
...outputFiles[project.third]
|
||||
];
|
||||
let initialExpectedDiagnostics: ReadonlyArray<fakes.ExpectedDiagnostic> = [
|
||||
getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[project.first][source.config], relOutputFiles[project.first][ext.js]],
|
||||
[Diagnostics.Building_project_0, sources[project.first][source.config]],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[project.second][source.config], relOutputFiles[project.second][ext.js]],
|
||||
[Diagnostics.Building_project_0, sources[project.second][source.config]],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[project.third][source.config], relOutputFiles[project.third][ext.js]],
|
||||
[Diagnostics.Building_project_0, sources[project.third][source.config]]
|
||||
];
|
||||
let initialExpectedReadFiles: ReadonlyMap<number> = getReadFilesMap(
|
||||
[
|
||||
// Configs
|
||||
sources[project.first][source.config],
|
||||
sources[project.second][source.config],
|
||||
sources[project.third][source.config],
|
||||
|
||||
// Source files
|
||||
...sources[project.first][source.ts],
|
||||
...sources[project.second][source.ts],
|
||||
...sources[project.third][source.ts],
|
||||
|
||||
// outputs
|
||||
...outputFiles[project.first],
|
||||
...outputFiles[project.second],
|
||||
|
||||
// build info
|
||||
outputFiles[project.third][ext.buildinfo],
|
||||
],
|
||||
// These are first not present and later read new contents to generate third output
|
||||
outputFiles[project.first][ext.buildinfo],
|
||||
outputFiles[project.second][ext.buildinfo]
|
||||
);
|
||||
|
||||
let dtsChangedExpectedDiagnostics: ReadonlyArray<fakes.ExpectedDiagnostic> = [
|
||||
getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[project.first][source.config], relOutputFiles[project.first][ext.js], relSources[project.first][source.ts][part.one]],
|
||||
[Diagnostics.Building_project_0, sources[project.first][source.config]],
|
||||
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, relSources[project.second][source.config], relSources[project.second][source.ts][part.one], relOutputFiles[project.second][ext.js]],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[project.third][source.config], relOutputFiles[project.third][ext.js], "src/first"],
|
||||
[Diagnostics.Building_project_0, sources[project.third][source.config]]
|
||||
];
|
||||
let dtsChangedExpectedReadFiles: ReadonlyMap<number> = getReadFilesMap(
|
||||
[
|
||||
// Configs
|
||||
sources[project.first][source.config],
|
||||
sources[project.second][source.config],
|
||||
sources[project.third][source.config],
|
||||
|
||||
// Source files
|
||||
...sources[project.first][source.ts],
|
||||
...sources[project.third][source.ts],
|
||||
|
||||
// outputs
|
||||
...outputFiles[project.first],
|
||||
...outputFiles[project.second],
|
||||
outputFiles[project.third][ext.dts],
|
||||
|
||||
// build info
|
||||
outputFiles[project.third][ext.buildinfo],
|
||||
],
|
||||
outputFiles[project.first][ext.dts], // dts changes so once read old content, and once new (to emit third)
|
||||
outputFiles[project.first][ext.buildinfo], // since first build info changes
|
||||
);
|
||||
|
||||
let dtsChangedExpectedDiagnosticsDependOrdered: ReadonlyArray<fakes.ExpectedDiagnostic> = [
|
||||
getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[project.first][source.config], relOutputFiles[project.first][ext.js], relSources[project.first][source.ts][part.one]],
|
||||
[Diagnostics.Building_project_0, sources[project.first][source.config]],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[project.second][source.config], relOutputFiles[project.second][ext.js], "src/first"],
|
||||
[Diagnostics.Building_project_0, sources[project.second][source.config]],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[project.third][source.config], relOutputFiles[project.third][ext.js], "src/second"],
|
||||
[Diagnostics.Building_project_0, sources[project.third][source.config]]
|
||||
];
|
||||
let dtsChangedExpectedReadFilesDependOrdered: ReadonlyMap<number> = getDtsChangedReadFilesDependOrdered();
|
||||
|
||||
let dtsUnchangedExpectedDiagnostics: ReadonlyArray<fakes.ExpectedDiagnostic> = [
|
||||
getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[project.first][source.config], relOutputFiles[project.first][ext.js], relSources[project.first][source.ts][part.one]],
|
||||
[Diagnostics.Building_project_0, sources[project.first][source.config]],
|
||||
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, relSources[project.second][source.config], relSources[project.second][source.ts][part.one], relOutputFiles[project.second][ext.js]],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed, relSources[project.third][source.config], "src/first"],
|
||||
[Diagnostics.Updating_output_of_project_0, sources[project.third][source.config]],
|
||||
[Diagnostics.Updating_unchanged_output_timestamps_of_project_0, sources[project.third][source.config]],
|
||||
];
|
||||
let dtsUnchangedExpectedReadFiles: ReadonlyMap<number> = getReadFilesMap(
|
||||
[
|
||||
// Configs
|
||||
sources[project.first][source.config],
|
||||
sources[project.second][source.config],
|
||||
sources[project.third][source.config],
|
||||
|
||||
// Source files
|
||||
...sources[project.first][source.ts],
|
||||
|
||||
// outputs to prepend
|
||||
...outputFiles[project.first],
|
||||
...outputFiles[project.second],
|
||||
...outputFiles[project.third],
|
||||
],
|
||||
outputFiles[project.first][ext.buildinfo], // since first build info changes
|
||||
);
|
||||
|
||||
let dtsUnchangedExpectedDiagnosticsDependOrdered: ReadonlyArray<fakes.ExpectedDiagnostic> = [
|
||||
getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, relSources[project.first][source.config], relOutputFiles[project.first][ext.js], relSources[project.first][source.ts][part.one]],
|
||||
[Diagnostics.Building_project_0, sources[project.first][source.config]],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed, relSources[project.second][source.config], "src/first"],
|
||||
[Diagnostics.Updating_output_of_project_0, sources[project.second][source.config]],
|
||||
[Diagnostics.Updating_unchanged_output_timestamps_of_project_0, sources[project.second][source.config]],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed, relSources[project.third][source.config], "src/second"],
|
||||
[Diagnostics.Updating_output_of_project_0, sources[project.third][source.config]],
|
||||
[Diagnostics.Updating_unchanged_output_timestamps_of_project_0, sources[project.third][source.config]],
|
||||
];
|
||||
let dtsUnchangedExpectedReadFilesDependOrdered: ReadonlyMap<number> = getDtsUnchangedExpectedReadFilesDependOrdered();
|
||||
|
||||
before(() => {
|
||||
outFileFs = loadProjectFromDisk("tests/projects/outfile-concat", time);
|
||||
});
|
||||
after(() => {
|
||||
outFileFs = undefined!;
|
||||
expectedOutputFiles = undefined!;
|
||||
initialExpectedDiagnostics = undefined!;
|
||||
initialExpectedReadFiles = undefined!;
|
||||
dtsChangedExpectedDiagnostics = undefined!;
|
||||
dtsChangedExpectedReadFiles = undefined!;
|
||||
dtsChangedExpectedDiagnosticsDependOrdered = undefined!;
|
||||
dtsChangedExpectedReadFilesDependOrdered = undefined!;
|
||||
dtsUnchangedExpectedDiagnostics = undefined!;
|
||||
dtsUnchangedExpectedReadFiles = undefined!;
|
||||
dtsUnchangedExpectedDiagnosticsDependOrdered = undefined!;
|
||||
dtsUnchangedExpectedReadFilesDependOrdered = undefined!;
|
||||
});
|
||||
|
||||
function createSolutionBuilder(host: fakes.SolutionBuilderHost) {
|
||||
return ts.createSolutionBuilder(host, ["/src/third"], { dry: false, force: false, verbose: true });
|
||||
}
|
||||
|
||||
function getInitialExpectedReadFiles(additionalSourceFiles?: ReadonlyArray<string>) {
|
||||
if (!additionalSourceFiles) return initialExpectedReadFiles;
|
||||
const expectedReadFiles = cloneMap(initialExpectedReadFiles);
|
||||
for (const path of additionalSourceFiles) {
|
||||
expectedReadFiles.set(path, 1);
|
||||
}
|
||||
return expectedReadFiles;
|
||||
}
|
||||
|
||||
function getDtsChangedReadFilesDependOrdered() {
|
||||
const value = cloneMap(dtsChangedExpectedReadFiles);
|
||||
for (const path of sources[project.second][source.ts]) {
|
||||
value.set(path, 1);
|
||||
}
|
||||
value.set(outputFiles[project.second][ext.dts], 2); // dts changes so once read old content, and once new (to emit third)
|
||||
value.set(outputFiles[project.second][ext.buildinfo], 2); // since first build info changes
|
||||
return value;
|
||||
}
|
||||
|
||||
function getDtsChangedReadFiles(dependOrdered?: boolean, additionalSourceFiles?: ReadonlyArray<string>) {
|
||||
const value = dependOrdered ? dtsChangedExpectedReadFilesDependOrdered : dtsChangedExpectedReadFiles;
|
||||
if (!additionalSourceFiles) return value;
|
||||
const expectedReadFiles = cloneMap(value);
|
||||
for (const path of additionalSourceFiles) {
|
||||
expectedReadFiles.set(path, 1);
|
||||
}
|
||||
return expectedReadFiles;
|
||||
}
|
||||
|
||||
function getDtsUnchangedExpectedReadFilesDependOrdered() {
|
||||
const value = cloneMap(dtsUnchangedExpectedReadFiles);
|
||||
// Since this changes too
|
||||
for (const path of outputFiles[project.second]) {
|
||||
value.set(path, 2);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function getDtsUnchangedReadFiles(dependOrdered?: boolean, additionalSourceFiles?: ReadonlyArray<string>) {
|
||||
const value = dependOrdered ? dtsUnchangedExpectedReadFilesDependOrdered : dtsUnchangedExpectedReadFiles;
|
||||
if (!additionalSourceFiles || additionalSourceFiles.length !== 3) return value;
|
||||
const expectedReadFiles = cloneMap(value);
|
||||
// Additional source Files
|
||||
expectedReadFiles.set(additionalSourceFiles[project.first], 1);
|
||||
return expectedReadFiles;
|
||||
}
|
||||
|
||||
interface VerifyOutFileScenarioInput {
|
||||
scenario: string;
|
||||
modifyFs: (fs: vfs.FileSystem) => void;
|
||||
modifyAgainFs?: (fs: vfs.FileSystem) => void;
|
||||
additionalSourceFiles?: ReadonlyArray<string>;
|
||||
expectedBuildInfoFilesForSectionBaselines?: ReadonlyArray<BuildInfoSectionBaselineFiles>;
|
||||
dependOrdered?: true;
|
||||
ignoreDtsChanged?: true;
|
||||
ignoreDtsUnchanged?: true;
|
||||
baselineOnly?: true;
|
||||
}
|
||||
|
||||
function verifyOutFileScenario({
|
||||
scenario,
|
||||
modifyFs,
|
||||
modifyAgainFs,
|
||||
additionalSourceFiles,
|
||||
expectedBuildInfoFilesForSectionBaselines,
|
||||
dependOrdered,
|
||||
ignoreDtsChanged,
|
||||
ignoreDtsUnchanged,
|
||||
baselineOnly
|
||||
}: VerifyOutFileScenarioInput) {
|
||||
const initialExpectedReadFiles = !baselineOnly ? getInitialExpectedReadFiles(additionalSourceFiles) : undefined;
|
||||
const dtsChangedReadFiles = !baselineOnly && !ignoreDtsChanged ? getDtsChangedReadFiles(dependOrdered, additionalSourceFiles) : undefined;
|
||||
const dtsUnchanged: ExpectedBuildOutput | undefined = !baselineOnly && (!ignoreDtsUnchanged || !modifyAgainFs) ? {
|
||||
expectedDiagnostics: dependOrdered ?
|
||||
dtsUnchangedExpectedDiagnosticsDependOrdered :
|
||||
dtsUnchangedExpectedDiagnostics,
|
||||
expectedReadFiles: getDtsUnchangedReadFiles(dependOrdered, additionalSourceFiles)
|
||||
} : undefined;
|
||||
|
||||
verifyTsbuildOutput({
|
||||
scenario,
|
||||
projFs: () => outFileFs,
|
||||
time,
|
||||
tick,
|
||||
proj: "outfile-concat",
|
||||
rootNames: ["/src/third"],
|
||||
expectedMapFileNames,
|
||||
expectedBuildInfoFilesForSectionBaselines: expectedBuildInfoFilesForSectionBaselines || expectedTsbuildInfoFileNames,
|
||||
lastProjectOutputJs: outputFiles[project.third][ext.js],
|
||||
initialBuild: {
|
||||
modifyFs,
|
||||
expectedDiagnostics: initialExpectedDiagnostics,
|
||||
expectedReadFiles: initialExpectedReadFiles
|
||||
},
|
||||
incrementalDtsChangedBuild: !ignoreDtsChanged ? {
|
||||
modifyFs: fs => replaceText(fs, relSources[project.first][source.ts][part.one], "Hello", "Hola"),
|
||||
expectedDiagnostics: dependOrdered ?
|
||||
dtsChangedExpectedDiagnosticsDependOrdered :
|
||||
dtsChangedExpectedDiagnostics,
|
||||
expectedReadFiles: dtsChangedReadFiles
|
||||
} : undefined,
|
||||
incrementalDtsUnchangedBuild: !ignoreDtsUnchanged ? {
|
||||
modifyFs: fs => appendText(fs, relSources[project.first][source.ts][part.one], "console.log(s);"),
|
||||
expectedDiagnostics: dtsUnchanged && dtsUnchanged.expectedDiagnostics,
|
||||
expectedReadFiles: dtsUnchanged && dtsUnchanged.expectedReadFiles
|
||||
} : undefined,
|
||||
incrementalHeaderChangedBuild: modifyAgainFs ? {
|
||||
modifyFs: modifyAgainFs,
|
||||
expectedDiagnostics: dtsUnchanged && dtsUnchanged.expectedDiagnostics,
|
||||
expectedReadFiles: dtsUnchanged && dtsUnchanged.expectedReadFiles
|
||||
} : undefined,
|
||||
outputFiles: expectedOutputFiles,
|
||||
baselineOnly
|
||||
});
|
||||
}
|
||||
|
||||
// Verify initial + incremental edits
|
||||
verifyOutFileScenario({
|
||||
scenario: "baseline sectioned sourcemaps",
|
||||
modifyFs: noop
|
||||
});
|
||||
|
||||
// Verify baseline with build info
|
||||
verifyOutFileScenario({
|
||||
scenario: "when final project is not composite but uses project references",
|
||||
modifyFs: fs => replaceText(fs, sources[project.third][source.config], `"composite": true,`, ""),
|
||||
ignoreDtsChanged: true,
|
||||
ignoreDtsUnchanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
|
||||
// Verify baseline with build info
|
||||
verifyOutFileScenario({
|
||||
scenario: "when final project is not composite but incremental",
|
||||
modifyFs: fs => replaceText(fs, sources[project.third][source.config], `"composite": true,`, `"incremental": true,`),
|
||||
ignoreDtsChanged: true,
|
||||
ignoreDtsUnchanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
|
||||
// Verify baseline with build info
|
||||
verifyOutFileScenario({
|
||||
scenario: "when final project specifies tsBuildInfoFile",
|
||||
modifyFs: fs => replaceText(fs, sources[project.third][source.config], `"composite": true,`, `"composite": true,
|
||||
"tsBuildInfoFile": "./thirdjs/output/third.tsbuildinfo",`),
|
||||
expectedBuildInfoFilesForSectionBaselines: [
|
||||
expectedTsbuildInfoFileNames[0],
|
||||
expectedTsbuildInfoFileNames[1],
|
||||
["/src/third/thirdjs/output/third.tsbuildinfo", expectedTsbuildInfoFileNames[2][1], expectedTsbuildInfoFileNames[2][2]]
|
||||
],
|
||||
ignoreDtsChanged: true,
|
||||
ignoreDtsUnchanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
|
||||
it("clean projects", () => {
|
||||
const fs = outFileFs.shadow();
|
||||
const expectedOutputs = [
|
||||
...outputFiles[project.first],
|
||||
...outputFiles[project.second],
|
||||
...outputFiles[project.third]
|
||||
];
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host);
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(...initialExpectedDiagnostics);
|
||||
// Verify they exist
|
||||
for (const output of expectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
host.clearDiagnostics();
|
||||
builder.cleanAllProjects();
|
||||
host.assertDiagnosticMessages(/*none*/);
|
||||
// Verify they are gone
|
||||
for (const output of expectedOutputs) {
|
||||
assert(!fs.existsSync(output), `Expect file ${output} to not exist`);
|
||||
}
|
||||
// Subsequent clean shouldn't throw / etc
|
||||
builder.cleanAllProjects();
|
||||
});
|
||||
|
||||
it("verify buildInfo absence results in new build", () => {
|
||||
const fs = outFileFs.shadow();
|
||||
const expectedOutputs = [
|
||||
...outputFiles[project.first],
|
||||
...outputFiles[project.second],
|
||||
...outputFiles[project.third]
|
||||
];
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host);
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(...initialExpectedDiagnostics);
|
||||
// Verify they exist
|
||||
for (const output of expectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
// Delete bundle info
|
||||
host.clearDiagnostics();
|
||||
host.deleteFile(outputFiles[project.first][ext.buildinfo]);
|
||||
builder.resetBuildContext();
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, relSources[project.first][source.config], relOutputFiles[project.first][ext.buildinfo]],
|
||||
[Diagnostics.Building_project_0, sources[project.first][source.config]],
|
||||
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, relSources[project.second][source.config], relSources[project.second][source.ts][part.one], relOutputFiles[project.second][ext.js]],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed, relSources[project.third][source.config], "src/first"],
|
||||
[Diagnostics.Updating_output_of_project_0, sources[project.third][source.config]],
|
||||
[Diagnostics.Updating_unchanged_output_timestamps_of_project_0, sources[project.third][source.config]],
|
||||
);
|
||||
});
|
||||
|
||||
it("verify that if incremental is set to false, tsbuildinfo is not generated", () => {
|
||||
const fs = outFileFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
replaceText(fs, sources[project.third][source.config], `"composite": true,`, "");
|
||||
const builder = createSolutionBuilder(host);
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(...initialExpectedDiagnostics);
|
||||
// Verify they exist - without tsbuildinfo for third project
|
||||
for (const output of expectedOutputFiles.slice(0, expectedOutputFiles.length - 2)) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
assert.isFalse(fs.existsSync(outputFiles[project.third][ext.buildinfo]), `Expect file ${outputFiles[project.third][ext.buildinfo]} to not exist`);
|
||||
});
|
||||
|
||||
it("rebuilds completely when version in tsbuildinfo doesnt match ts version", () => {
|
||||
const fs = outFileFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host);
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(...initialExpectedDiagnostics);
|
||||
host.clearDiagnostics();
|
||||
builder.resetBuildContext();
|
||||
changeCompilerVersion(host);
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
// TODO:: This should build all instead
|
||||
getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[project.first][source.config], fakes.version, version],
|
||||
[Diagnostics.Building_project_0, sources[project.first][source.config]],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[project.second][source.config], fakes.version, version],
|
||||
[Diagnostics.Building_project_0, sources[project.second][source.config]],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, relSources[project.third][source.config], fakes.version, version],
|
||||
[Diagnostics.Building_project_0, sources[project.third][source.config]],
|
||||
);
|
||||
});
|
||||
|
||||
describe("Prepend output with .tsbuildinfo", () => {
|
||||
// Prologues
|
||||
describe("Prologues", () => {
|
||||
// Verify initial + incremental edits
|
||||
verifyOutFileScenario({
|
||||
scenario: "strict in all projects",
|
||||
modifyFs: fs => {
|
||||
enableStrict(fs, sources[project.first][source.config]);
|
||||
enableStrict(fs, sources[project.second][source.config]);
|
||||
enableStrict(fs, sources[project.third][source.config]);
|
||||
},
|
||||
modifyAgainFs: fs => addTestPrologue(fs, relSources[project.first][source.ts][part.one], `"myPrologue"`)
|
||||
});
|
||||
|
||||
// Verify ignore dtsChanged
|
||||
verifyOutFileScenario({
|
||||
scenario: "strict in one dependency",
|
||||
modifyFs: fs => enableStrict(fs, sources[project.second][source.config]),
|
||||
modifyAgainFs: fs => addTestPrologue(fs, "src/first/first_PART1.ts", `"myPrologue"`),
|
||||
ignoreDtsChanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
|
||||
// Verify initial + incremental edits - sourcemap verification
|
||||
verifyOutFileScenario({
|
||||
scenario: "multiple prologues in all projects",
|
||||
modifyFs: fs => {
|
||||
enableStrict(fs, sources[project.first][source.config]);
|
||||
addTestPrologue(fs, sources[project.first][source.ts][part.one], `"myPrologue"`);
|
||||
enableStrict(fs, sources[project.second][source.config]);
|
||||
addTestPrologue(fs, sources[project.second][source.ts][part.one], `"myPrologue"`);
|
||||
addTestPrologue(fs, sources[project.second][source.ts][part.two], `"myPrologue2";`);
|
||||
enableStrict(fs, sources[project.third][source.config]);
|
||||
addTestPrologue(fs, sources[project.third][source.ts][part.one], `"myPrologue";`);
|
||||
addTestPrologue(fs, sources[project.third][source.ts][part.one], `"myPrologue3";`);
|
||||
},
|
||||
modifyAgainFs: fs => addTestPrologue(fs, relSources[project.first][source.ts][part.one], `"myPrologue5"`)
|
||||
});
|
||||
|
||||
// Verify ignore dtsChanged
|
||||
verifyOutFileScenario({
|
||||
scenario: "multiple prologues in different projects",
|
||||
modifyFs: fs => {
|
||||
enableStrict(fs, sources[project.first][source.config]);
|
||||
addTestPrologue(fs, sources[project.second][source.ts][part.one], `"myPrologue"`);
|
||||
addTestPrologue(fs, sources[project.second][source.ts][part.two], `"myPrologue2";`);
|
||||
enableStrict(fs, sources[project.third][source.config]);
|
||||
},
|
||||
modifyAgainFs: fs => addTestPrologue(fs, sources[project.first][source.ts][part.one], `"myPrologue5"`),
|
||||
ignoreDtsChanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
});
|
||||
|
||||
// Shebang
|
||||
describe("Shebang", () => {
|
||||
// changes declaration because its emitted in .d.ts file
|
||||
// Verify initial + incremental edits
|
||||
verifyOutFileScenario({
|
||||
scenario: "shebang in all projects",
|
||||
modifyFs: fs => {
|
||||
addShebang(fs, "first", "first_PART1");
|
||||
addShebang(fs, "first", "first_part2");
|
||||
addShebang(fs, "second", "second_part1");
|
||||
addShebang(fs, "third", "third_part1");
|
||||
},
|
||||
});
|
||||
|
||||
// Verify ignore dtsChanged
|
||||
verifyOutFileScenario({
|
||||
scenario: "shebang in only one dependency project",
|
||||
modifyFs: fs => addShebang(fs, "second", "second_part1"),
|
||||
ignoreDtsChanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
});
|
||||
|
||||
// emitHelpers
|
||||
describe("emitHelpers", () => {
|
||||
// Verify initial + incremental edits
|
||||
verifyOutFileScenario({
|
||||
scenario: "emitHelpers in all projects",
|
||||
modifyFs: fs => {
|
||||
addRest(fs, "first", "first_PART1");
|
||||
addRest(fs, "second", "second_part1");
|
||||
addRest(fs, "third", "third_part1");
|
||||
},
|
||||
modifyAgainFs: fs => removeRest(fs, "first", "first_PART1")
|
||||
});
|
||||
|
||||
// Verify ignore dtsChanged
|
||||
verifyOutFileScenario({
|
||||
scenario: "emitHelpers in only one dependency project",
|
||||
modifyFs: fs => {
|
||||
addStubFoo(fs, "first", "first_PART1");
|
||||
addRest(fs, "second", "second_part1");
|
||||
},
|
||||
modifyAgainFs: fs => changeStubToRest(fs, "first", "first_PART1"),
|
||||
ignoreDtsChanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
|
||||
// Verify ignore dtsChanged
|
||||
verifyOutFileScenario({
|
||||
scenario: "multiple emitHelpers in all projects",
|
||||
modifyFs: fs => {
|
||||
addRest(fs, "first", "first_PART1");
|
||||
addSpread(fs, "first", "first_part3");
|
||||
addRest(fs, "second", "second_part1");
|
||||
addSpread(fs, "second", "second_part2");
|
||||
addRest(fs, "third", "third_part1");
|
||||
addSpread(fs, "third", "third_part1");
|
||||
},
|
||||
modifyAgainFs: fs => removeRest(fs, "first", "first_PART1"),
|
||||
ignoreDtsChanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
|
||||
// Verify ignore dtsChanged
|
||||
verifyOutFileScenario({
|
||||
scenario: "multiple emitHelpers in different projects",
|
||||
modifyFs: fs => {
|
||||
addRest(fs, "first", "first_PART1");
|
||||
addSpread(fs, "second", "second_part1");
|
||||
addRest(fs, "third", "third_part1");
|
||||
},
|
||||
modifyAgainFs: fs => removeRest(fs, "first", "first_PART1"),
|
||||
ignoreDtsChanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
});
|
||||
|
||||
// triple slash refs
|
||||
describe("triple slash refs", () => {
|
||||
// changes declaration because its emitted in .d.ts file
|
||||
// Verify initial + incremental edits
|
||||
verifyOutFileScenario({
|
||||
scenario: "triple slash refs in all projects",
|
||||
modifyFs: fs => {
|
||||
addTripleSlashRef(fs, "first", "first_part2");
|
||||
addTripleSlashRef(fs, "second", "second_part1");
|
||||
addTripleSlashRef(fs, "third", "third_part1");
|
||||
},
|
||||
additionalSourceFiles: [
|
||||
getTripleSlashRef("first"), getTripleSlashRef("second"), getTripleSlashRef("third")
|
||||
]
|
||||
});
|
||||
|
||||
// Verify ignore dtsChanged
|
||||
verifyOutFileScenario({
|
||||
scenario: "triple slash refs in one project",
|
||||
modifyFs: fs => addTripleSlashRef(fs, "second", "second_part1"),
|
||||
additionalSourceFiles: [
|
||||
getTripleSlashRef("second")
|
||||
],
|
||||
ignoreDtsChanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
});
|
||||
|
||||
describe("stripInternal", () => {
|
||||
function disableRemoveComments(fs: vfs.FileSystem, file: string) {
|
||||
replaceText(fs, file, `"removeComments": true`, `"removeComments": false`);
|
||||
}
|
||||
|
||||
function diableRemoveCommentsInAll(fs: vfs.FileSystem) {
|
||||
disableRemoveComments(fs, sources[project.first][source.config]);
|
||||
disableRemoveComments(fs, sources[project.second][source.config]);
|
||||
disableRemoveComments(fs, sources[project.third][source.config]);
|
||||
}
|
||||
|
||||
function stripInternalOfThird(fs: vfs.FileSystem) {
|
||||
replaceText(fs, sources[project.third][source.config], `"declaration": true,`, `"declaration": true,
|
||||
"stripInternal": true`);
|
||||
}
|
||||
|
||||
function stripInternalScenario(fs: vfs.FileSystem, removeCommentsDisabled?: boolean, jsDocStyle?: boolean) {
|
||||
const internal = jsDocStyle ? `/**@internal*/` : `/*@internal*/`;
|
||||
if (removeCommentsDisabled) {
|
||||
diableRemoveCommentsInAll(fs);
|
||||
}
|
||||
stripInternalOfThird(fs);
|
||||
replaceText(fs, sources[project.first][source.ts][part.one], "interface", `${internal} interface`);
|
||||
appendText(fs, sources[project.second][source.ts][part.one], `
|
||||
class normalC {
|
||||
${internal} constructor() { }
|
||||
${internal} prop: string;
|
||||
${internal} method() { }
|
||||
${internal} get c() { return 10; }
|
||||
${internal} set c(val: number) { }
|
||||
}
|
||||
namespace normalN {
|
||||
${internal} export class C { }
|
||||
${internal} export function foo() {}
|
||||
${internal} export namespace someNamespace { export class C {} }
|
||||
${internal} export namespace someOther.something { export class someClass {} }
|
||||
${internal} export import someImport = someNamespace.C;
|
||||
${internal} export type internalType = internalC;
|
||||
${internal} export const internalConst = 10;
|
||||
${internal} export enum internalEnum { a, b, c }
|
||||
}
|
||||
${internal} class internalC {}
|
||||
${internal} function internalfoo() {}
|
||||
${internal} namespace internalNamespace { export class someClass {} }
|
||||
${internal} namespace internalOther.something { export class someClass {} }
|
||||
${internal} import internalImport = internalNamespace.someClass;
|
||||
${internal} type internalType = internalC;
|
||||
${internal} const internalConst = 10;
|
||||
${internal} enum internalEnum { a, b, c }`);
|
||||
}
|
||||
|
||||
// Verify initial + incremental edits
|
||||
verifyOutFileScenario({
|
||||
scenario: "stripInternal",
|
||||
modifyFs: stripInternalScenario,
|
||||
modifyAgainFs: fs => replaceText(fs, sources[project.first][source.ts][part.one], `/*@internal*/ interface`, "interface"),
|
||||
});
|
||||
|
||||
// Verify ignore dtsChanged
|
||||
verifyOutFileScenario({
|
||||
scenario: "stripInternal with comments emit enabled",
|
||||
modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ true),
|
||||
modifyAgainFs: fs => replaceText(fs, sources[project.first][source.ts][part.one], `/*@internal*/ interface`, "interface"),
|
||||
ignoreDtsChanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
|
||||
// Verify ignore dtsChanged
|
||||
verifyOutFileScenario({
|
||||
scenario: "stripInternal jsdoc style comment",
|
||||
modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ false, /*jsDocStyle*/ true),
|
||||
modifyAgainFs: fs => replaceText(fs, sources[project.first][source.ts][part.one], `/**@internal*/ interface`, "interface"),
|
||||
ignoreDtsChanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
|
||||
// Verify ignore dtsChanged
|
||||
verifyOutFileScenario({
|
||||
scenario: "stripInternal jsdoc style with comments emit enabled",
|
||||
modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ true, /*jsDocStyle*/ true),
|
||||
ignoreDtsChanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
|
||||
describe("with three levels of project dependency", () => {
|
||||
function makeOneTwoThreeDependOrder(fs: vfs.FileSystem) {
|
||||
replaceText(fs, sources[project.second][source.config], "[", `[
|
||||
{ "path": "../first", "prepend": true }`);
|
||||
replaceText(fs, sources[project.third][source.config], `{ "path": "../first", "prepend": true },`, "");
|
||||
}
|
||||
|
||||
function stripInternalWithDependentOrder(fs: vfs.FileSystem, removeCommentsDisabled?: boolean, jsDocStyle?: boolean) {
|
||||
stripInternalScenario(fs, removeCommentsDisabled, jsDocStyle);
|
||||
makeOneTwoThreeDependOrder(fs);
|
||||
}
|
||||
|
||||
// Verify initial + incremental edits
|
||||
verifyOutFileScenario({
|
||||
scenario: "stripInternal when one-two-three are prepended in order",
|
||||
modifyFs: stripInternalWithDependentOrder,
|
||||
modifyAgainFs: fs => replaceText(fs, sources[project.first][source.ts][part.one], `/*@internal*/ interface`, "interface"),
|
||||
dependOrdered: true,
|
||||
});
|
||||
|
||||
// Verify ignore dtsChanged
|
||||
verifyOutFileScenario({
|
||||
scenario: "stripInternal with comments emit enabled when one-two-three are prepended in order",
|
||||
modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ true),
|
||||
modifyAgainFs: fs => replaceText(fs, sources[project.first][source.ts][part.one], `/*@internal*/ interface`, "interface"),
|
||||
dependOrdered: true,
|
||||
ignoreDtsChanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
|
||||
// Verify ignore dtsChanged
|
||||
verifyOutFileScenario({
|
||||
scenario: "stripInternal jsdoc style comment when one-two-three are prepended in order",
|
||||
modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ false, /*jsDocStyle*/ true),
|
||||
modifyAgainFs: fs => replaceText(fs, sources[project.first][source.ts][part.one], `/**@internal*/ interface`, "interface"),
|
||||
dependOrdered: true,
|
||||
ignoreDtsChanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
|
||||
// Verify ignore dtsChanged
|
||||
verifyOutFileScenario({
|
||||
scenario: "stripInternal jsdoc style with comments emit enabled when one-two-three are prepended in order",
|
||||
modifyFs: fs => stripInternalWithDependentOrder(fs, /*removeCommentsDisabled*/ true, /*jsDocStyle*/ true),
|
||||
dependOrdered: true,
|
||||
ignoreDtsChanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
});
|
||||
|
||||
// only baseline
|
||||
verifyOutFileScenario({
|
||||
scenario: "stripInternal baseline when internal is inside another internal",
|
||||
modifyFs: fs => {
|
||||
stripInternalOfThird(fs);
|
||||
prependText(fs, sources[project.first][source.ts][part.one], `namespace ts {
|
||||
/* @internal */
|
||||
/**
|
||||
* Subset of properties from SourceFile that are used in multiple utility functions
|
||||
*/
|
||||
export interface SourceFileLike {
|
||||
readonly text: string;
|
||||
lineMap?: ReadonlyArray<number>;
|
||||
/* @internal */
|
||||
getPositionOfLineAndCharacter?(line: number, character: number, allowEdits?: true): number;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export interface RedirectInfo {
|
||||
/** Source file this redirects to. */
|
||||
readonly redirectTarget: SourceFile;
|
||||
/**
|
||||
* Source file for the duplicate package. This will not be used by the Program,
|
||||
* but we need to keep this around so we can watch for changes in underlying.
|
||||
*/
|
||||
readonly unredirected: SourceFile;
|
||||
}
|
||||
|
||||
// Source files are declarations when they are external modules.
|
||||
export interface SourceFile {
|
||||
someProp: string;
|
||||
}
|
||||
}`);
|
||||
},
|
||||
ignoreDtsChanged: true,
|
||||
ignoreDtsUnchanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
|
||||
// only baseline
|
||||
verifyOutFileScenario({
|
||||
scenario: "stripInternal when few members of enum are internal",
|
||||
modifyFs: fs => {
|
||||
stripInternalOfThird(fs);
|
||||
prependText(fs, sources[project.first][source.ts][part.one], `enum TokenFlags {
|
||||
None = 0,
|
||||
/* @internal */
|
||||
PrecedingLineBreak = 1 << 0,
|
||||
/* @internal */
|
||||
PrecedingJSDocComment = 1 << 1,
|
||||
/* @internal */
|
||||
Unterminated = 1 << 2,
|
||||
/* @internal */
|
||||
ExtendedUnicodeEscape = 1 << 3,
|
||||
Scientific = 1 << 4,
|
||||
Octal = 1 << 5,
|
||||
HexSpecifier = 1 << 6,
|
||||
BinarySpecifier = 1 << 7,
|
||||
OctalSpecifier = 1 << 8,
|
||||
/* @internal */
|
||||
ContainsSeparator = 1 << 9,
|
||||
/* @internal */
|
||||
BinaryOrOctalSpecifier = BinarySpecifier | OctalSpecifier,
|
||||
/* @internal */
|
||||
NumericLiteralFlags = Scientific | Octal | HexSpecifier | BinaryOrOctalSpecifier | ContainsSeparator
|
||||
}
|
||||
`);
|
||||
},
|
||||
ignoreDtsChanged: true,
|
||||
ignoreDtsUnchanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
});
|
||||
|
||||
describe("empty source files", () => {
|
||||
function makeThirdEmptySourceFile(fs: vfs.FileSystem) {
|
||||
fs.writeFileSync(sources[project.third][source.ts][part.one], "", "utf8");
|
||||
}
|
||||
|
||||
// Verify ignore dtsChanged
|
||||
verifyOutFileScenario({
|
||||
scenario: "when source files are empty in the own file",
|
||||
modifyFs: makeThirdEmptySourceFile,
|
||||
ignoreDtsChanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
|
||||
// only baseline
|
||||
verifyOutFileScenario({
|
||||
scenario: "declarationMap and sourceMap disabled",
|
||||
modifyFs: fs => {
|
||||
makeThirdEmptySourceFile(fs);
|
||||
replaceText(fs, sources[project.third][source.config], `"composite": true,`, "");
|
||||
replaceText(fs, sources[project.third][source.config], `"sourceMap": true,`, "");
|
||||
replaceText(fs, sources[project.third][source.config], `"declarationMap": true,`, "");
|
||||
},
|
||||
ignoreDtsChanged: true,
|
||||
ignoreDtsUnchanged: true,
|
||||
baselineOnly: true
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
namespace ts {
|
||||
describe("unittests:: tsbuild:: with rootDir of project reference in parentDirectory", () => {
|
||||
let projFs: vfs.FileSystem;
|
||||
before(() => {
|
||||
projFs = loadProjectFromDisk("tests/projects/projectReferenceWithRootDirInParent");
|
||||
});
|
||||
|
||||
after(() => {
|
||||
projFs = undefined!; // Release the contents
|
||||
});
|
||||
|
||||
it("verify that it builds correctly", () => {
|
||||
const projFs = loadProjectFromDisk("tests/projects/projectReferenceWithRootDirInParent");
|
||||
const allExpectedOutputs = [
|
||||
"/src/dist/other/other.js", "/src/dist/other/other.d.ts",
|
||||
"/src/dist/main/a.js", "/src/dist/main/a.d.ts",
|
||||
"/src/dist/main/b.js", "/src/dist/main/b.d.ts"
|
||||
];
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/src/main", "/src/src/other"], {});
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*empty*/);
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
});
|
||||
|
||||
it("verify that it reports error for same .tsbuildinfo file because no rootDir in the base", () => {
|
||||
const allExpectedOutputs = [
|
||||
"/src/dist/other.js", "/src/dist/other.d.ts",
|
||||
"/src/dist/tsconfig.tsbuildinfo"
|
||||
];
|
||||
const missingOutputs = [
|
||||
"/src/dist/a.js", "/src/dist/a.d.ts",
|
||||
"/src/dist/b.js", "/src/dist/b.d.ts"
|
||||
];
|
||||
const fs = projFs.shadow();
|
||||
replaceText(fs, "/src/tsconfig.base.json", `"rootDir": "./src/",`, "");
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/src/main"], { verbose: true });
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/src/other/tsconfig.json", "src/src/main/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/src/other/tsconfig.json", "src/dist/other.js"],
|
||||
[Diagnostics.Building_project_0, "/src/src/other/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/src/main/tsconfig.json", "src/dist/a.js"],
|
||||
[Diagnostics.Building_project_0, "/src/src/main/tsconfig.json"],
|
||||
[Diagnostics.Cannot_write_file_0_because_it_will_overwrite_tsbuildinfo_file_generated_by_referenced_project_1, "/src/dist/tsconfig.tsbuildinfo", "/src/src/other"]
|
||||
);
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
for (const output of missingOutputs) {
|
||||
assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`);
|
||||
}
|
||||
});
|
||||
|
||||
it("verify that it reports error for same .tsbuildinfo file", () => {
|
||||
const allExpectedOutputs = [
|
||||
"/src/dist/other.js", "/src/dist/other.d.ts",
|
||||
"/src/dist/tsconfig.tsbuildinfo"
|
||||
];
|
||||
const missingOutputs = [
|
||||
"/src/dist/a.js", "/src/dist/a.d.ts",
|
||||
"/src/dist/b.js", "/src/dist/b.d.ts"
|
||||
];
|
||||
const fs = projFs.shadow();
|
||||
fs.writeFileSync("/src/src/main/tsconfig.json", JSON.stringify({
|
||||
compilerOptions: { composite: true, outDir: "../../dist/" },
|
||||
references: [{ path: "../other" }]
|
||||
}));
|
||||
fs.writeFileSync("/src/src/other/tsconfig.json", JSON.stringify({
|
||||
compilerOptions: { composite: true, outDir: "../../dist/" },
|
||||
}));
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/src/main"], { verbose: true });
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/src/other/tsconfig.json", "src/src/main/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/src/other/tsconfig.json", "src/dist/other.js"],
|
||||
[Diagnostics.Building_project_0, "/src/src/other/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/src/main/tsconfig.json", "src/dist/a.js"],
|
||||
[Diagnostics.Building_project_0, "/src/src/main/tsconfig.json"],
|
||||
[Diagnostics.Cannot_write_file_0_because_it_will_overwrite_tsbuildinfo_file_generated_by_referenced_project_1, "/src/dist/tsconfig.tsbuildinfo", "/src/src/other"]
|
||||
);
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
for (const output of missingOutputs) {
|
||||
assert.isFalse(fs.existsSync(output), `Expect file ${output} to not exist`);
|
||||
}
|
||||
});
|
||||
|
||||
it("verify that it reports no error when .tsbuildinfo differ", () => {
|
||||
const allExpectedOutputs = [
|
||||
"/src/dist/other.js", "/src/dist/other.d.ts",
|
||||
"/src/dist/tsconfig.main.tsbuildinfo",
|
||||
"/src/dist/a.js", "/src/dist/a.d.ts",
|
||||
"/src/dist/b.js", "/src/dist/b.d.ts",
|
||||
"/src/dist/tsconfig.other.tsbuildinfo"
|
||||
];
|
||||
const fs = projFs.shadow();
|
||||
fs.renameSync("/src/src/main/tsconfig.json", "/src/src/main/tsconfig.main.json");
|
||||
fs.renameSync("/src/src/other/tsconfig.json", "/src/src/other/tsconfig.other.json");
|
||||
fs.writeFileSync("/src/src/main/tsconfig.main.json", JSON.stringify({
|
||||
compilerOptions: { composite: true, outDir: "../../dist/" },
|
||||
references: [{ path: "../other/tsconfig.other.json" }]
|
||||
}));
|
||||
fs.writeFileSync("/src/src/other/tsconfig.other.json", JSON.stringify({
|
||||
compilerOptions: { composite: true, outDir: "../../dist/" },
|
||||
}));
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/src/main/tsconfig.main.json"], { verbose: true });
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/src/other/tsconfig.other.json", "src/src/main/tsconfig.main.json"),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/src/other/tsconfig.other.json", "src/dist/other.js"],
|
||||
[Diagnostics.Building_project_0, "/src/src/other/tsconfig.other.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/src/main/tsconfig.main.json", "src/dist/a.js"],
|
||||
[Diagnostics.Building_project_0, "/src/src/main/tsconfig.main.json"]
|
||||
);
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
namespace ts {
|
||||
describe("unittests:: tsbuild:: with resolveJsonModule option", () => {
|
||||
let projFs: vfs.FileSystem;
|
||||
const allExpectedOutputs = ["/src/tests/dist/src/index.js", "/src/tests/dist/src/index.d.ts", "/src/tests/dist/src/hello.json"];
|
||||
before(() => {
|
||||
projFs = loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite");
|
||||
});
|
||||
|
||||
after(() => {
|
||||
projFs = undefined!; // Release the contents
|
||||
});
|
||||
|
||||
function verifyProjectWithResolveJsonModule(configFile: string, ...expectedDiagnosticMessages: fakes.ExpectedDiagnostic[]) {
|
||||
const fs = projFs.shadow();
|
||||
verifyProjectWithResolveJsonModuleWithFs(fs, configFile, allExpectedOutputs, ...expectedDiagnosticMessages);
|
||||
}
|
||||
|
||||
function verifyProjectWithResolveJsonModuleWithFs(fs: vfs.FileSystem, configFile: string, allExpectedOutputs: ReadonlyArray<string>, ...expectedDiagnosticMessages: fakes.ExpectedDiagnostic[]) {
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, [configFile], { dry: false, force: false, verbose: false });
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(...expectedDiagnosticMessages);
|
||||
if (!expectedDiagnosticMessages.length) {
|
||||
// Check for outputs. Not an exhaustive list
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it("with resolveJsonModule and include only", () => {
|
||||
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withInclude.json", [
|
||||
Diagnostics.File_0_is_not_in_project_file_list_Projects_must_list_all_files_or_use_an_include_pattern,
|
||||
"/src/tests/src/hello.json"
|
||||
]);
|
||||
});
|
||||
|
||||
it("with resolveJsonModule and include of *.json along with other include", () => {
|
||||
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withIncludeOfJson.json");
|
||||
});
|
||||
|
||||
it("with resolveJsonModule and include of *.json along with other include and file name matches ts file", () => {
|
||||
const fs = projFs.shadow();
|
||||
fs.rimrafSync("/src/tests/src/hello.json");
|
||||
fs.writeFileSync("/src/tests/src/index.json", JSON.stringify({ hello: "world" }));
|
||||
fs.writeFileSync("/src/tests/src/index.ts", `import hello from "./index.json"
|
||||
|
||||
export default hello.hello`);
|
||||
const allExpectedOutputs = ["/src/tests/dist/src/index.js", "/src/tests/dist/src/index.d.ts", "/src/tests/dist/src/index.json"];
|
||||
verifyProjectWithResolveJsonModuleWithFs(fs, "/src/tests/tsconfig_withIncludeOfJson.json", allExpectedOutputs);
|
||||
});
|
||||
|
||||
it("with resolveJsonModule and files containing json file", () => {
|
||||
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withFiles.json");
|
||||
});
|
||||
|
||||
it("with resolveJsonModule and include and files", () => {
|
||||
verifyProjectWithResolveJsonModule("/src/tests/tsconfig_withIncludeAndFiles.json");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,666 @@
|
||||
namespace ts {
|
||||
describe("unittests:: tsbuild:: on 'sample1' project", () => {
|
||||
let projFs: vfs.FileSystem;
|
||||
const { time, tick } = getTime();
|
||||
const allExpectedOutputs = ["/src/tests/index.js",
|
||||
"/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map",
|
||||
"/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts"];
|
||||
|
||||
before(() => {
|
||||
projFs = loadProjectFromDisk("tests/projects/sample1", time);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
projFs = undefined!; // Release the contents
|
||||
});
|
||||
|
||||
describe("sanity check of clean build of 'sample1' project", () => {
|
||||
it("can build the sample project 'sample1' without error", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
|
||||
|
||||
host.clearDiagnostics();
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*empty*/);
|
||||
|
||||
// Check for outputs. Not an exhaustive list
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
});
|
||||
|
||||
it("builds correctly when outDir is specified", () => {
|
||||
const fs = projFs.shadow();
|
||||
fs.writeFileSync("/src/logic/tsconfig.json", JSON.stringify({
|
||||
compilerOptions: { composite: true, declaration: true, sourceMap: true, outDir: "outDir" },
|
||||
references: [{ path: "../core" }]
|
||||
}));
|
||||
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], {});
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*empty*/);
|
||||
const expectedOutputs = allExpectedOutputs.map(f => f.replace("/logic/", "/logic/outDir/"));
|
||||
// Check for outputs. Not an exhaustive list
|
||||
for (const output of expectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
});
|
||||
|
||||
it("builds correctly when declarationDir is specified", () => {
|
||||
const fs = projFs.shadow();
|
||||
fs.writeFileSync("/src/logic/tsconfig.json", JSON.stringify({
|
||||
compilerOptions: { composite: true, declaration: true, sourceMap: true, declarationDir: "out/decls" },
|
||||
references: [{ path: "../core" }]
|
||||
}));
|
||||
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], {});
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*empty*/);
|
||||
const expectedOutputs = allExpectedOutputs.map(f => f.replace("/logic/index.d.ts", "/logic/out/decls/index.d.ts"));
|
||||
// Check for outputs. Not an exhaustive list
|
||||
for (const output of expectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
});
|
||||
|
||||
it("builds correctly when project is not composite or doesnt have any references", () => {
|
||||
const fs = projFs.shadow();
|
||||
replaceText(fs, "/src/core/tsconfig.json", `"composite": true,`, "");
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/core"], { verbose: true });
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"],
|
||||
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"]
|
||||
);
|
||||
for (const output of ["/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map"]) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("dry builds", () => {
|
||||
it("doesn't write any files in a dry build", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: true, force: false, verbose: false });
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
[Diagnostics.A_non_dry_build_would_build_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.A_non_dry_build_would_build_project_0, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.A_non_dry_build_would_build_project_0, "/src/tests/tsconfig.json"]
|
||||
);
|
||||
|
||||
// Check for outputs to not be written. Not an exhaustive list
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(!fs.existsSync(output), `Expect file ${output} to not exist`);
|
||||
}
|
||||
});
|
||||
|
||||
it("indicates that it would skip builds during a dry build", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
|
||||
let builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
|
||||
builder.buildAllProjects();
|
||||
tick();
|
||||
|
||||
host.clearDiagnostics();
|
||||
builder = createSolutionBuilder(host, ["/src/tests"], { dry: true, force: false, verbose: false });
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
[Diagnostics.Project_0_is_up_to_date, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_up_to_date, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_up_to_date, "/src/tests/tsconfig.json"]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("clean builds", () => {
|
||||
it("removes all files it built", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
|
||||
builder.buildAllProjects();
|
||||
// Verify they exist
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
builder.cleanAllProjects();
|
||||
// Verify they are gone
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(!fs.existsSync(output), `Expect file ${output} to not exist`);
|
||||
}
|
||||
// Subsequent clean shouldn't throw / etc
|
||||
builder.cleanAllProjects();
|
||||
});
|
||||
});
|
||||
|
||||
describe("force builds", () => {
|
||||
it("always builds under --force", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: true, verbose: false });
|
||||
builder.buildAllProjects();
|
||||
let currentTime = time();
|
||||
checkOutputTimestamps(currentTime);
|
||||
|
||||
tick();
|
||||
Debug.assert(time() !== currentTime, "Time moves on");
|
||||
currentTime = time();
|
||||
builder.buildAllProjects();
|
||||
checkOutputTimestamps(currentTime);
|
||||
|
||||
function checkOutputTimestamps(expected: number) {
|
||||
// Check timestamps
|
||||
for (const output of allExpectedOutputs) {
|
||||
const actual = fs.statSync(output).mtimeMs;
|
||||
assert(actual === expected, `File ${output} has timestamp ${actual}, expected ${expected}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("can detect when and what to rebuild", () => {
|
||||
function initializeWithBuild() {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true });
|
||||
builder.buildAllProjects();
|
||||
host.clearDiagnostics();
|
||||
tick();
|
||||
builder.resetBuildContext();
|
||||
return { fs, host, builder };
|
||||
}
|
||||
|
||||
it("Builds the project", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true });
|
||||
builder.resetBuildContext();
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"],
|
||||
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"],
|
||||
[Diagnostics.Building_project_0, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/tests/tsconfig.json", "src/tests/index.js"],
|
||||
[Diagnostics.Building_project_0, "/src/tests/tsconfig.json"]
|
||||
);
|
||||
});
|
||||
|
||||
// All three projects are up to date
|
||||
it("Detects that all projects are up to date", () => {
|
||||
const { host, builder } = initializeWithBuild();
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"],
|
||||
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/logic/tsconfig.json", "src/logic/index.ts", "src/logic/index.js"],
|
||||
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/tests/tsconfig.json", "src/tests/index.ts", "src/tests/index.js"]
|
||||
);
|
||||
});
|
||||
|
||||
// Update a file in the leaf node (tests), only it should rebuild the last one
|
||||
it("Only builds the leaf node project", () => {
|
||||
const { fs, host, builder } = initializeWithBuild();
|
||||
fs.writeFileSync("/src/tests/index.ts", "const m = 10;");
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"],
|
||||
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/logic/tsconfig.json", "src/logic/index.ts", "src/logic/index.js"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/tests/tsconfig.json", "src/tests/index.js", "src/tests/index.ts"],
|
||||
[Diagnostics.Building_project_0, "/src/tests/tsconfig.json"]
|
||||
);
|
||||
});
|
||||
|
||||
// Update a file in the parent (without affecting types), should get fast downstream builds
|
||||
it("Detects type-only changes in upstream projects", () => {
|
||||
const { fs, host, builder } = initializeWithBuild();
|
||||
replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET");
|
||||
builder.buildAllProjects();
|
||||
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/core/tsconfig.json", "src/core/anotherModule.js", "src/core/index.ts"],
|
||||
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Updating_unchanged_output_timestamps_of_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, "src/logic/tsconfig.json"],
|
||||
[Diagnostics.Updating_output_timestamps_of_project_0, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, "src/tests/tsconfig.json"],
|
||||
[Diagnostics.Updating_output_timestamps_of_project_0, "/src/tests/tsconfig.json"]
|
||||
);
|
||||
});
|
||||
|
||||
it("rebuilds completely when version in tsbuildinfo doesnt match ts version", () => {
|
||||
const { host, builder } = initializeWithBuild();
|
||||
changeCompilerVersion(host);
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/core/tsconfig.json", fakes.version, version],
|
||||
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/logic/tsconfig.json", fakes.version, version],
|
||||
[Diagnostics.Building_project_0, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, "src/tests/tsconfig.json", fakes.version, version],
|
||||
[Diagnostics.Building_project_0, "/src/tests/tsconfig.json"],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("downstream-blocked compilations", () => {
|
||||
it("won't build downstream projects if upstream projects have errors", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: true });
|
||||
|
||||
// Induce an error in the middle project
|
||||
replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`);
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"],
|
||||
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"],
|
||||
[Diagnostics.Building_project_0, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.Property_0_does_not_exist_on_type_1, "muitply", `typeof import("/src/core/index")`],
|
||||
[Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, "src/tests/tsconfig.json", "src/logic"],
|
||||
[Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, "/src/tests/tsconfig.json", "/src/logic"]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("project invalidation", () => {
|
||||
it("invalidates projects correctly", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
|
||||
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(/*empty*/);
|
||||
|
||||
// Update a timestamp in the middle project
|
||||
tick();
|
||||
appendText(fs, "/src/logic/index.ts", "function foo() {}");
|
||||
const originalWriteFile = fs.writeFileSync;
|
||||
const writtenFiles = createMap<true>();
|
||||
fs.writeFileSync = (path, data, encoding) => {
|
||||
writtenFiles.set(path, true);
|
||||
originalWriteFile.call(fs, path, data, encoding);
|
||||
};
|
||||
// Because we haven't reset the build context, the builder should assume there's nothing to do right now
|
||||
const status = builder.getUpToDateStatusOfFile(builder.resolveProjectName("/src/logic"));
|
||||
assert.equal(status.type, UpToDateStatusType.UpToDate, "Project should be assumed to be up-to-date");
|
||||
verifyInvalidation(/*expectedToWriteTests*/ false);
|
||||
|
||||
// Rebuild this project
|
||||
fs.writeFileSync("/src/logic/index.ts", `${fs.readFileSync("/src/logic/index.ts")}
|
||||
export class cNew {}`);
|
||||
verifyInvalidation(/*expectedToWriteTests*/ true);
|
||||
|
||||
function verifyInvalidation(expectedToWriteTests: boolean) {
|
||||
// Rebuild this project
|
||||
tick();
|
||||
builder.invalidateProject("/src/logic");
|
||||
builder.buildInvalidatedProject();
|
||||
// The file should be updated
|
||||
assert.isTrue(writtenFiles.has("/src/logic/index.js"), "JS file should have been rebuilt");
|
||||
assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt");
|
||||
assert.isFalse(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should *not* have been rebuilt");
|
||||
assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt");
|
||||
writtenFiles.clear();
|
||||
|
||||
// Build downstream projects should update 'tests', but not 'core'
|
||||
tick();
|
||||
builder.buildInvalidatedProject();
|
||||
if (expectedToWriteTests) {
|
||||
assert.isTrue(writtenFiles.has("/src/tests/index.js"), "Downstream JS file should have been rebuilt");
|
||||
}
|
||||
else {
|
||||
assert.equal(writtenFiles.size, 0, "Should not write any new files");
|
||||
}
|
||||
assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have new timestamp");
|
||||
assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("lists files", () => {
|
||||
it("listFiles", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { listFiles: true });
|
||||
builder.buildAllProjects();
|
||||
assert.deepEqual(host.traces, [
|
||||
...getLibs(),
|
||||
"/src/core/anotherModule.ts",
|
||||
"/src/core/index.ts",
|
||||
"/src/core/some_decl.d.ts",
|
||||
...getLibs(),
|
||||
...getCoreOutputs(),
|
||||
"/src/logic/index.ts",
|
||||
...getLibs(),
|
||||
...getCoreOutputs(),
|
||||
"/src/logic/index.d.ts",
|
||||
"/src/tests/index.ts"
|
||||
]);
|
||||
|
||||
function getCoreOutputs() {
|
||||
return [
|
||||
"/src/core/index.d.ts",
|
||||
"/src/core/anotherModule.d.ts"
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
it("listEmittedFiles", () => {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tests"], { listEmittedFiles: true });
|
||||
builder.buildAllProjects();
|
||||
assert.deepEqual(host.traces, [
|
||||
"TSFILE: /src/core/anotherModule.js",
|
||||
"TSFILE: /src/core/anotherModule.d.ts",
|
||||
"TSFILE: /src/core/anotherModule.d.ts.map",
|
||||
"TSFILE: /src/core/index.js",
|
||||
"TSFILE: /src/core/index.d.ts",
|
||||
"TSFILE: /src/core/index.d.ts.map",
|
||||
"TSFILE: /src/core/tsconfig.tsbuildinfo",
|
||||
"TSFILE: /src/logic/index.js",
|
||||
"TSFILE: /src/logic/index.js.map",
|
||||
"TSFILE: /src/logic/index.d.ts",
|
||||
"TSFILE: /src/logic/tsconfig.tsbuildinfo",
|
||||
"TSFILE: /src/tests/index.js",
|
||||
"TSFILE: /src/tests/index.d.ts",
|
||||
"TSFILE: /src/tests/tsconfig.tsbuildinfo",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("emit output", () => {
|
||||
const initialBuild: BuildState = {
|
||||
modifyFs: noop,
|
||||
expectedDiagnostics: [
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/core/tsconfig.json", "src/core/anotherModule.js"],
|
||||
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/index.js"],
|
||||
[Diagnostics.Building_project_0, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/tests/tsconfig.json", "src/tests/index.js"],
|
||||
[Diagnostics.Building_project_0, "/src/tests/tsconfig.json"]
|
||||
],
|
||||
expectedReadFiles: getReadFilesMap(
|
||||
[
|
||||
// Configs
|
||||
"/src/core/tsconfig.json",
|
||||
"/src/logic/tsconfig.json",
|
||||
"/src/tests/tsconfig.json",
|
||||
|
||||
// Source files
|
||||
"/src/core/anotherModule.ts",
|
||||
"/src/core/index.ts",
|
||||
"/src/core/some_decl.d.ts",
|
||||
"/src/logic/index.ts",
|
||||
"/src/tests/index.ts",
|
||||
|
||||
// Modules of generated files
|
||||
"/src/core/anotherModule.d.ts",
|
||||
"/src/core/index.d.ts",
|
||||
"/src/logic/index.d.ts",
|
||||
|
||||
// build info
|
||||
"/src/core/tsconfig.tsbuildinfo",
|
||||
"/src/logic/tsconfig.tsbuildinfo",
|
||||
"/src/tests/tsconfig.tsbuildinfo"
|
||||
]
|
||||
)
|
||||
};
|
||||
verifyTsbuildOutput({
|
||||
scenario: "sample",
|
||||
projFs: () => projFs,
|
||||
time,
|
||||
tick,
|
||||
proj: "sample1",
|
||||
rootNames: ["/src/tests"],
|
||||
expectedMapFileNames: [
|
||||
"/src/core/anotherModule.d.ts.map",
|
||||
"/src/core/index.d.ts.map",
|
||||
"/src/logic/index.js.map"
|
||||
],
|
||||
lastProjectOutputJs: "/src/tests/index.js",
|
||||
initialBuild,
|
||||
incrementalDtsChangedBuild: {
|
||||
modifyFs: fs => appendText(fs, "/src/core/index.ts", `
|
||||
export class someClass { }`),
|
||||
expectedDiagnostics: [
|
||||
// Emits only partial core instead of all outputs
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/core/tsconfig.json", "src/core/anotherModule.js", "src/core/index.ts"],
|
||||
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Updating_unchanged_output_timestamps_of_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/logic/tsconfig.json", "src/logic/index.js", "src/core"],
|
||||
[Diagnostics.Building_project_0, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/tests/tsconfig.json", "src/tests/index.js", "src/core"],
|
||||
[Diagnostics.Building_project_0, "/src/tests/tsconfig.json"],
|
||||
],
|
||||
expectedReadFiles: getReadFilesMap(
|
||||
[
|
||||
// Configs
|
||||
"/src/core/tsconfig.json",
|
||||
"/src/logic/tsconfig.json",
|
||||
"/src/tests/tsconfig.json",
|
||||
|
||||
// Source files
|
||||
"/src/core/anotherModule.ts",
|
||||
"/src/core/index.ts",
|
||||
"/src/core/some_decl.d.ts",
|
||||
"/src/logic/index.ts",
|
||||
"/src/tests/index.ts",
|
||||
|
||||
// Modules of generated files
|
||||
"/src/core/anotherModule.d.ts",
|
||||
"/src/core/index.d.ts",
|
||||
"/src/logic/index.d.ts",
|
||||
|
||||
// build info
|
||||
"/src/core/tsconfig.tsbuildinfo",
|
||||
"/src/logic/tsconfig.tsbuildinfo",
|
||||
"/src/tests/tsconfig.tsbuildinfo",
|
||||
|
||||
"/src/tests/index.d.ts", // to check if d.ts has changed
|
||||
],
|
||||
"/src/core/index.d.ts", // to check if changed, and to build other projects after change
|
||||
),
|
||||
},
|
||||
incrementalDtsUnchangedBuild: {
|
||||
modifyFs: fs => appendText(fs, "/src/core/index.ts", `
|
||||
class someClass { }`),
|
||||
expectedDiagnostics: [
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/core/tsconfig.json", "src/core/anotherModule.js", "src/core/index.ts"],
|
||||
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Updating_unchanged_output_timestamps_of_project_0, "/src/core/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, "src/logic/tsconfig.json"],
|
||||
[Diagnostics.Updating_output_timestamps_of_project_0, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, "src/tests/tsconfig.json"],
|
||||
[Diagnostics.Updating_output_timestamps_of_project_0, "/src/tests/tsconfig.json"]
|
||||
],
|
||||
expectedReadFiles: getReadFilesMap(
|
||||
[
|
||||
// Configs
|
||||
"/src/core/tsconfig.json",
|
||||
"/src/logic/tsconfig.json",
|
||||
"/src/tests/tsconfig.json",
|
||||
|
||||
// Source files
|
||||
"/src/core/anotherModule.ts",
|
||||
"/src/core/index.ts",
|
||||
"/src/core/some_decl.d.ts",
|
||||
|
||||
// to check if changed
|
||||
"/src/core/index.d.ts",
|
||||
|
||||
// build info
|
||||
"/src/core/tsconfig.tsbuildinfo",
|
||||
"/src/logic/tsconfig.tsbuildinfo",
|
||||
"/src/tests/tsconfig.tsbuildinfo",
|
||||
],
|
||||
)
|
||||
},
|
||||
outputFiles: [
|
||||
"/src/core/anotherModule.js",
|
||||
"/src/core/anotherModule.d.ts",
|
||||
"/src/core/anotherModule.d.ts.map",
|
||||
"/src/core/index.js",
|
||||
"/src/core/index.d.ts",
|
||||
"/src/core/index.d.ts.map",
|
||||
"/src/core/tsconfig.tsbuildinfo",
|
||||
"/src/logic/index.js",
|
||||
"/src/logic/index.js.map",
|
||||
"/src/logic/index.d.ts",
|
||||
"/src/logic/tsconfig.tsbuildinfo",
|
||||
"/src/tests/index.js",
|
||||
"/src/tests/index.d.ts",
|
||||
"/src/tests/tsconfig.tsbuildinfo",
|
||||
]
|
||||
});
|
||||
|
||||
verifyTsbuildOutput({
|
||||
scenario: "when logic config changes declaration dir",
|
||||
projFs: () => projFs,
|
||||
time,
|
||||
tick,
|
||||
proj: "sample1",
|
||||
rootNames: ["/src/tests"],
|
||||
expectedMapFileNames: [
|
||||
"/src/core/anotherModule.d.ts.map",
|
||||
"/src/core/index.d.ts.map",
|
||||
"/src/logic/index.js.map"
|
||||
],
|
||||
lastProjectOutputJs: "/src/tests/index.js",
|
||||
initialBuild,
|
||||
incrementalDtsChangedBuild: {
|
||||
modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"declaration": true,`, `"declaration": true,
|
||||
"declarationDir": "decls",`),
|
||||
expectedDiagnostics: [
|
||||
getExpectedDiagnosticForProjectsInBuild("src/core/tsconfig.json", "src/logic/tsconfig.json", "src/tests/tsconfig.json"),
|
||||
[Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, "src/core/tsconfig.json", "src/core/anotherModule.ts", "src/core/anotherModule.js"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/logic/tsconfig.json", "src/logic/decls/index.d.ts"],
|
||||
[Diagnostics.Building_project_0, "/src/logic/tsconfig.json"],
|
||||
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/tests/tsconfig.json", "src/tests/index.js", "src/logic"],
|
||||
[Diagnostics.Building_project_0, "/src/tests/tsconfig.json"],
|
||||
],
|
||||
expectedReadFiles: getReadFilesMap(
|
||||
[
|
||||
// Configs
|
||||
"/src/core/tsconfig.json",
|
||||
"/src/logic/tsconfig.json",
|
||||
"/src/tests/tsconfig.json",
|
||||
|
||||
// Source files
|
||||
"/src/logic/index.ts",
|
||||
"/src/tests/index.ts",
|
||||
|
||||
// Modules of generated files
|
||||
"/src/core/anotherModule.d.ts",
|
||||
"/src/core/index.d.ts",
|
||||
"/src/logic/decls/index.d.ts",
|
||||
|
||||
// build info
|
||||
"/src/core/tsconfig.tsbuildinfo",
|
||||
"/src/logic/tsconfig.tsbuildinfo",
|
||||
"/src/tests/tsconfig.tsbuildinfo",
|
||||
|
||||
"/src/tests/index.d.ts", // to check if d.ts has changed
|
||||
]
|
||||
)
|
||||
},
|
||||
outputFiles: [
|
||||
"/src/core/anotherModule.js",
|
||||
"/src/core/anotherModule.d.ts",
|
||||
"/src/core/anotherModule.d.ts.map",
|
||||
"/src/core/index.js",
|
||||
"/src/core/index.d.ts",
|
||||
"/src/core/index.d.ts.map",
|
||||
"/src/core/tsconfig.tsbuildinfo",
|
||||
"/src/logic/index.js",
|
||||
"/src/logic/index.js.map",
|
||||
"/src/logic/decls/index.d.ts",
|
||||
"/src/logic/tsconfig.tsbuildinfo",
|
||||
"/src/tests/index.js",
|
||||
"/src/tests/index.d.ts",
|
||||
"/src/tests/tsconfig.tsbuildinfo",
|
||||
],
|
||||
});
|
||||
|
||||
verifyTsbuildOutput({
|
||||
scenario: "when logic specifies tsBuildInfoFile",
|
||||
projFs: () => projFs,
|
||||
time,
|
||||
tick,
|
||||
proj: "sample1",
|
||||
rootNames: ["/src/tests"],
|
||||
expectedMapFileNames: [
|
||||
"/src/core/anotherModule.d.ts.map",
|
||||
"/src/core/index.d.ts.map",
|
||||
"/src/logic/index.js.map"
|
||||
],
|
||||
lastProjectOutputJs: "/src/tests/index.js",
|
||||
initialBuild: {
|
||||
modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"composite": true,`, `"composite": true,
|
||||
"tsBuildInfoFile": "ownFile.tsbuildinfo",`),
|
||||
expectedDiagnostics: initialBuild.expectedDiagnostics,
|
||||
expectedReadFiles: getReadFilesMap(
|
||||
[
|
||||
// Configs
|
||||
"/src/core/tsconfig.json",
|
||||
"/src/logic/tsconfig.json",
|
||||
"/src/tests/tsconfig.json",
|
||||
|
||||
// Source files
|
||||
"/src/core/anotherModule.ts",
|
||||
"/src/core/index.ts",
|
||||
"/src/core/some_decl.d.ts",
|
||||
"/src/logic/index.ts",
|
||||
"/src/tests/index.ts",
|
||||
|
||||
// Modules of generated files
|
||||
"/src/core/anotherModule.d.ts",
|
||||
"/src/core/index.d.ts",
|
||||
"/src/logic/index.d.ts",
|
||||
|
||||
// build info
|
||||
"/src/core/tsconfig.tsbuildinfo",
|
||||
"/src/logic/ownFile.tsbuildinfo",
|
||||
"/src/tests/tsconfig.tsbuildinfo"
|
||||
]
|
||||
)
|
||||
},
|
||||
outputFiles: [
|
||||
"/src/core/anotherModule.js",
|
||||
"/src/core/anotherModule.d.ts",
|
||||
"/src/core/anotherModule.d.ts.map",
|
||||
"/src/core/index.js",
|
||||
"/src/core/index.d.ts",
|
||||
"/src/core/index.d.ts.map",
|
||||
"/src/core/tsconfig.tsbuildinfo",
|
||||
"/src/logic/index.js",
|
||||
"/src/logic/index.js.map",
|
||||
"/src/logic/index.d.ts",
|
||||
"/src/logic/ownFile.tsbuildinfo",
|
||||
"/src/tests/index.js",
|
||||
"/src/tests/index.d.ts",
|
||||
"/src/tests/tsconfig.tsbuildinfo",
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
namespace ts {
|
||||
describe("unittests:: tsbuild:: when project reference is referenced transitively", () => {
|
||||
let projFs: vfs.FileSystem;
|
||||
const allExpectedOutputs = [
|
||||
"/src/a.js", "/src/a.d.ts",
|
||||
"/src/b.js", "/src/b.d.ts",
|
||||
"/src/c.js"
|
||||
];
|
||||
const expectedFileTraces = [
|
||||
...getLibs(),
|
||||
"/src/a.ts",
|
||||
...getLibs(),
|
||||
"/src/a.d.ts",
|
||||
"/src/b.ts",
|
||||
...getLibs(),
|
||||
"/src/a.d.ts",
|
||||
"/src/b.d.ts",
|
||||
"/src/refs/a.d.ts",
|
||||
"/src/c.ts"
|
||||
];
|
||||
before(() => {
|
||||
projFs = loadProjectFromDisk("tests/projects/transitiveReferences");
|
||||
});
|
||||
after(() => {
|
||||
projFs = undefined!; // Release the contents
|
||||
});
|
||||
|
||||
function verifyBuild(modifyDiskLayout: (fs: vfs.FileSystem) => void, allExpectedOutputs: ReadonlyArray<string>, expectedFileTraces: ReadonlyArray<string>, ...expectedDiagnostics: fakes.ExpectedDiagnostic[]) {
|
||||
const fs = projFs.shadow();
|
||||
const host = new fakes.SolutionBuilderHost(fs);
|
||||
modifyDiskLayout(fs);
|
||||
const builder = createSolutionBuilder(host, ["/src/tsconfig.c.json"], { listFiles: true });
|
||||
builder.buildAllProjects();
|
||||
host.assertDiagnosticMessages(...expectedDiagnostics);
|
||||
for (const output of allExpectedOutputs) {
|
||||
assert(fs.existsSync(output), `Expect file ${output} to exist`);
|
||||
}
|
||||
assert.deepEqual(host.traces, expectedFileTraces);
|
||||
}
|
||||
|
||||
function modifyFsBTsToNonRelativeImport(fs: vfs.FileSystem, moduleResolution: "node" | "classic") {
|
||||
fs.writeFileSync("/src/b.ts", `import {A} from 'a';
|
||||
export const b = new A();`);
|
||||
fs.writeFileSync("/src/tsconfig.b.json", JSON.stringify({
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
moduleResolution
|
||||
},
|
||||
files: ["b.ts"],
|
||||
references: [{ path: "tsconfig.a.json" }]
|
||||
}));
|
||||
}
|
||||
|
||||
it("verify that it builds correctly", () => {
|
||||
verifyBuild(noop, allExpectedOutputs, expectedFileTraces);
|
||||
});
|
||||
|
||||
it("verify that it builds correctly when the referenced project uses different module resolution", () => {
|
||||
verifyBuild(fs => modifyFsBTsToNonRelativeImport(fs, "classic"), allExpectedOutputs, expectedFileTraces);
|
||||
});
|
||||
|
||||
it("verify that it build reports error about module not found with node resolution with external module name", () => {
|
||||
// Error in b build only a
|
||||
const allExpectedOutputs = ["/src/a.js", "/src/a.d.ts"];
|
||||
const expectedFileTraces = [
|
||||
...getLibs(),
|
||||
"/src/a.ts",
|
||||
];
|
||||
verifyBuild(fs => modifyFsBTsToNonRelativeImport(fs, "node"),
|
||||
allExpectedOutputs,
|
||||
expectedFileTraces,
|
||||
[Diagnostics.Cannot_find_module_0, "a"],
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -348,9 +348,9 @@ function myFunc() { return 10; }`);
|
||||
// TODO:: local change does not build logic.js because builder doesnt find any changes in input files to generate output
|
||||
// Make local change to function bar
|
||||
verifyChangeInCore(`${coreIndex.content}
|
||||
function myFunc() { return 100; }`);
|
||||
function myFunc() { return 100; }`, /*isLocal*/ true);
|
||||
|
||||
function verifyChangeInCore(content: string) {
|
||||
function verifyChangeInCore(content: string, isLocal?: boolean) {
|
||||
const outputFileStamps = getOutputFileStamps();
|
||||
host.writeFile(coreIndex.path, content);
|
||||
|
||||
@@ -363,12 +363,14 @@ function myFunc() { return 100; }`);
|
||||
emptyArray
|
||||
);
|
||||
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
|
||||
const changedLogicOutput = getOutputFileNames(SubProject.logic, "index");
|
||||
const changedLogic = getOutputFileStamps();
|
||||
verifyChangedFiles(
|
||||
changedLogic,
|
||||
changedCore,
|
||||
getOutputFileNames(SubProject.logic, "index"),
|
||||
emptyArray
|
||||
// Only js file is written and d.ts is modified timestamp if its local change
|
||||
isLocal ? [changedLogicOutput[0]] : getOutputFileNames(SubProject.logic, "index"),
|
||||
isLocal ? [changedLogicOutput[1]] : emptyArray
|
||||
);
|
||||
host.checkTimeoutQueueLength(0);
|
||||
checkOutputErrorsIncremental(host, emptyArray);
|
||||
@@ -510,7 +512,8 @@ let x: string = 10;`);
|
||||
changeExtension(fileWithError.path, Extension.Js),
|
||||
changeExtension(fileWithError.path, Extension.Dts),
|
||||
changeExtension(fileWithoutError.path, Extension.Js),
|
||||
changeExtension(fileWithoutError.path, Extension.Dts)
|
||||
changeExtension(fileWithoutError.path, Extension.Dts),
|
||||
`${subProjectLocation}/tsconfig${Extension.TsBuildInfo}`
|
||||
];
|
||||
|
||||
function verifyDtsErrors(host: TsBuildWatchSystem, isIncremental: boolean, expectedErrors: ReadonlyArray<string>) {
|
||||
@@ -570,9 +573,10 @@ let x: string = 10;`);
|
||||
it("when fixing errors only changed file is emitted", () => {
|
||||
const host = createSolutionWithIncrementalError();
|
||||
fixError(host);
|
||||
assert.equal(host.writtenFiles.size, 2, `Expected to write only changed files: ${arrayFrom(host.writtenFiles.keys())}`);
|
||||
assert.equal(host.writtenFiles.size, 3, `Expected to write only changed files: ${arrayFrom(host.writtenFiles.keys())}`);
|
||||
verifyWrittenFile(host, outputs[0]);
|
||||
verifyWrittenFile(host, outputs[1]);
|
||||
verifyWrittenFile(host, outputs[4]);
|
||||
});
|
||||
|
||||
it("when file with no error changes, declaration errors are reported", () => {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "./tsconfig-base",
|
||||
"compilerOptions": {
|
||||
"declarationMap": false,
|
||||
"sourceMap": false,
|
||||
"composite": false,
|
||||
"incremental": true,
|
||||
"declaration": true,
|
||||
"stripInternal": true
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
"compilerOptions": {
|
||||
"declaration": false,
|
||||
"declarationMap": false,
|
||||
"composite": false
|
||||
"composite": false,
|
||||
"incremental": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "../tsconfig-library-base",
|
||||
"compilerOptions": {
|
||||
"outFile": "../../built/local/tsserverlibrary.out.js"
|
||||
},
|
||||
"files": [
|
||||
"tsserverlibrary.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../compiler", "prepend": true },
|
||||
{ "path": "../jsTyping", "prepend": true },
|
||||
{ "path": "../services", "prepend": true },
|
||||
{ "path": "../server", "prepend": true }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../tsconfig-library-base",
|
||||
"compilerOptions": {
|
||||
"outFile": "../../built/local/typescriptServices.out.js"
|
||||
},
|
||||
"files": [
|
||||
"typescriptServices.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../compiler", "prepend": true },
|
||||
{ "path": "../jsTyping", "prepend": true },
|
||||
{ "path": "../services", "prepend": true }
|
||||
]
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
"extends": "../tsconfig-noncomposite-base",
|
||||
"compilerOptions": {
|
||||
"removeComments": true,
|
||||
"incremental": false,
|
||||
"outFile": "../../built/local/watchGuard.js",
|
||||
"types": [
|
||||
"node"
|
||||
|
||||
Reference in New Issue
Block a user