Merge master

This commit is contained in:
Orta Therox
2019-09-25 14:46:36 -04:00
462 changed files with 20003 additions and 19496 deletions
+40 -16
View File
@@ -564,7 +564,7 @@ namespace ts {
if (!isIIFE) {
currentFlow = { flags: FlowFlags.Start };
if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethod)) {
currentFlow.container = <FunctionExpression | ArrowFunction | MethodDeclaration>node;
currentFlow.node = <FunctionExpression | ArrowFunction | MethodDeclaration>node;
}
}
// We create a return control flow graph for IIFEs and constructors. For constructors
@@ -581,6 +581,7 @@ namespace ts {
if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((<FunctionLikeDeclaration>node).body)) {
node.flags |= NodeFlags.HasImplicitReturn;
if (hasExplicitReturn) node.flags |= NodeFlags.HasExplicitReturn;
(<FunctionLikeDeclaration>node).endFlowNode = currentFlow;
}
if (node.kind === SyntaxKind.SourceFile) {
node.flags |= emitFlags;
@@ -671,6 +672,9 @@ namespace ts {
bindJSDoc(node);
return;
}
if (node.kind >= SyntaxKind.FirstStatement && node.kind <= SyntaxKind.LastStatement && !options.allowUnreachableCode) {
node.flowNode = currentFlow;
}
switch (node.kind) {
case SyntaxKind.WhileStatement:
bindWhileStatement(<WhileStatement>node);
@@ -708,6 +712,9 @@ namespace ts {
case SyntaxKind.CaseClause:
bindCaseClause(<CaseClause>node);
break;
case SyntaxKind.ExpressionStatement:
bindExpressionStatement(<ExpressionStatement>node);
break;
case SyntaxKind.LabeledStatement:
bindLabeledStatement(<LabeledStatement>node);
break;
@@ -845,17 +852,11 @@ namespace ts {
}
function createBranchLabel(): FlowLabel {
return {
flags: FlowFlags.BranchLabel,
antecedents: undefined
};
return { flags: FlowFlags.BranchLabel, antecedents: undefined };
}
function createLoopLabel(): FlowLabel {
return {
flags: FlowFlags.LoopLabel,
antecedents: undefined
};
return { flags: FlowFlags.LoopLabel, antecedents: undefined };
}
function setFlowNodeReferenced(flow: FlowNode) {
@@ -885,7 +886,7 @@ namespace ts {
return antecedent;
}
setFlowNodeReferenced(antecedent);
return flowNodeCreated({ flags, expression, antecedent });
return flowNodeCreated({ flags, antecedent, node: expression });
}
function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode {
@@ -893,7 +894,7 @@ namespace ts {
return antecedent;
}
setFlowNodeReferenced(antecedent);
return flowNodeCreated({ flags: FlowFlags.SwitchClause, switchStatement, clauseStart, clauseEnd, antecedent });
return flowNodeCreated({ flags: FlowFlags.SwitchClause, antecedent, switchStatement, clauseStart, clauseEnd });
}
function createFlowAssignment(antecedent: FlowNode, node: Expression | VariableDeclaration | BindingElement): FlowNode {
@@ -901,10 +902,14 @@ namespace ts {
return flowNodeCreated({ flags: FlowFlags.Assignment, antecedent, node });
}
function createFlowCall(antecedent: FlowNode, node: CallExpression): FlowNode {
setFlowNodeReferenced(antecedent);
return flowNodeCreated({ flags: FlowFlags.Call, antecedent, node });
}
function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode {
setFlowNodeReferenced(antecedent);
const res: FlowArrayMutation = flowNodeCreated({ flags: FlowFlags.ArrayMutation, antecedent, node });
return res;
return flowNodeCreated({ flags: FlowFlags.ArrayMutation, antecedent, node });
}
function finishFlowLabel(flow: FlowLabel): FlowNode {
@@ -1030,12 +1035,12 @@ namespace ts {
function bindForInOrForOfStatement(node: ForInOrOfStatement): void {
const preLoopLabel = createLoopLabel();
const postLoopLabel = createBranchLabel();
bind(node.expression);
addAntecedent(preLoopLabel, currentFlow);
currentFlow = preLoopLabel;
if (node.kind === SyntaxKind.ForOfStatement) {
bind(node.awaitModifier);
}
bind(node.expression);
addAntecedent(postLoopLabel, currentFlow);
bind(node.initializer);
if (node.initializer.kind !== SyntaxKind.VariableDeclarationList) {
@@ -1222,7 +1227,8 @@ namespace ts {
addAntecedent(postSwitchLabel, currentFlow);
const hasDefault = forEach(node.caseBlock.clauses, c => c.kind === SyntaxKind.DefaultClause);
// We mark a switch statement as possibly exhaustive if it has no default clause and if all
// case clauses have unreachable end points (e.g. they all return).
// case clauses have unreachable end points (e.g. they all return). Note, we no longer need
// this property in control flow analysis, it's there only for backwards compatibility.
node.possiblyExhaustive = !hasDefault && !postSwitchLabel.antecedents;
if (!hasDefault) {
addAntecedent(postSwitchLabel, createFlowSwitchClause(preSwitchCaseFlow, node, 0, 0));
@@ -1281,6 +1287,24 @@ namespace ts {
activeLabels!.pop();
}
function isDottedName(node: Expression): boolean {
return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.ThisKeyword ||
node.kind === SyntaxKind.PropertyAccessExpression && isDottedName((<PropertyAccessExpression>node).expression) ||
node.kind === SyntaxKind.ParenthesizedExpression && isDottedName((<ParenthesizedExpression>node).expression);
}
function bindExpressionStatement(node: ExpressionStatement): void {
bind(node.expression);
// A top level call expression with a dotted function name and at least one argument
// is potentially an assertion and is therefore included in the control flow.
if (node.expression.kind === SyntaxKind.CallExpression) {
const call = <CallExpression>node.expression;
if (isDottedName(call.expression)) {
currentFlow = createFlowCall(currentFlow, call);
}
}
}
function bindLabeledStatement(node: LabeledStatement): void {
const preStatementLabel = createLoopLabel();
const postStatementLabel = createBranchLabel();
@@ -2859,7 +2883,7 @@ namespace ts {
// If this is a property-parameter, then also declare the property symbol into the
// containing class.
if (isParameterPropertyDeclaration(node)) {
if (isParameterPropertyDeclaration(node, node.parent)) {
const classDeclaration = <ClassLikeDeclaration>node.parent.parent;
declareSymbol(classDeclaration.symbol.members!, classDeclaration.symbol, node, SymbolFlags.Property | (node.questionToken ? SymbolFlags.Optional : SymbolFlags.None), SymbolFlags.PropertyExcludes);
}
+106 -31
View File
@@ -60,6 +60,10 @@ namespace ts {
* Files pending to be emitted
*/
affectedFilesPendingEmit?: readonly Path[] | undefined;
/**
* Files pending to be emitted kind.
*/
affectedFilesPendingEmitKind?: ReadonlyMap<BuilderFileEmit> | undefined;
/**
* Current index to retrieve pending affected file
*/
@@ -70,6 +74,11 @@ namespace ts {
hasReusableDiagnostic?: true;
}
export const enum BuilderFileEmit {
DtsOnly,
Full
}
/**
* State to store the changed files, affected files and cache semantic diagnostics
*/
@@ -127,7 +136,11 @@ namespace ts {
/**
* Files pending to be emitted
*/
affectedFilesPendingEmit: readonly Path[] | undefined;
affectedFilesPendingEmit: Path[] | undefined;
/**
* Files pending to be emitted kind.
*/
affectedFilesPendingEmitKind: Map<BuilderFileEmit> | undefined;
/**
* Current index to retrieve pending affected file
*/
@@ -139,7 +152,7 @@ namespace ts {
/**
* Already seen emitted files
*/
seenEmittedFiles: Map<true> | undefined;
seenEmittedFiles: Map<BuilderFileEmit> | undefined;
/**
* true if program has been emitted
*/
@@ -160,8 +173,7 @@ namespace ts {
const compilerOptions = newProgram.getCompilerOptions();
state.compilerOptions = compilerOptions;
// With --out or --outFile, any change affects all semantic diagnostics so no need to cache them
// With --isolatedModules, emitting changed file doesnt emit dependent files so we cant know of dependent files to retrieve errors so dont cache the errors
if (!compilerOptions.outFile && !compilerOptions.out && !compilerOptions.isolatedModules) {
if (!compilerOptions.outFile && !compilerOptions.out) {
state.semanticDiagnosticsPerFile = createMap<readonly Diagnostic[]>();
}
state.changedFilesSet = createMap<true>();
@@ -186,7 +198,8 @@ namespace ts {
copyEntries(changedFilesSet, state.changedFilesSet);
}
if (!compilerOptions.outFile && !compilerOptions.out && oldState!.affectedFilesPendingEmit) {
state.affectedFilesPendingEmit = oldState!.affectedFilesPendingEmit;
state.affectedFilesPendingEmit = oldState!.affectedFilesPendingEmit.slice();
state.affectedFilesPendingEmitKind = cloneMapOrUndefined(oldState!.affectedFilesPendingEmitKind);
state.affectedFilesPendingEmitIndex = oldState!.affectedFilesPendingEmitIndex;
}
}
@@ -233,7 +246,7 @@ namespace ts {
if (oldCompilerOptions && compilerOptionsAffectEmit(compilerOptions, oldCompilerOptions)) {
// Add all files to affectedFilesPendingEmit since emit changed
addToAffectedFilesPendingEmit(state, newProgram.getSourceFiles().map(f => f.path));
newProgram.getSourceFiles().forEach(f => addToAffectedFilesPendingEmit(state, f.path, BuilderFileEmit.Full));
Debug.assert(state.seenAffectedFiles === undefined);
state.seenAffectedFiles = createMap<true>();
}
@@ -243,7 +256,7 @@ namespace ts {
function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newProgram: Program, getCanonicalFileName: GetCanonicalFileName): readonly Diagnostic[] {
if (!diagnostics.length) return emptyArray;
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getOutputPathForBuildInfo(newProgram.getCompilerOptions())!, newProgram.getCurrentDirectory()));
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(newProgram.getCompilerOptions())!, newProgram.getCurrentDirectory()));
return diagnostics.map(diagnostic => {
const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram, toPath);
result.reportsUnnecessary = diagnostic.reportsUnnecessary;
@@ -295,7 +308,8 @@ namespace ts {
newState.semanticDiagnosticsFromOldState = cloneMapOrUndefined(state.semanticDiagnosticsFromOldState);
newState.program = state.program;
newState.compilerOptions = state.compilerOptions;
newState.affectedFilesPendingEmit = state.affectedFilesPendingEmit;
newState.affectedFilesPendingEmit = state.affectedFilesPendingEmit && state.affectedFilesPendingEmit.slice();
newState.affectedFilesPendingEmitKind = cloneMapOrUndefined(state.affectedFilesPendingEmitKind);
newState.affectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex;
newState.seenEmittedFiles = cloneMapOrUndefined(state.seenEmittedFiles);
newState.programEmitComplete = state.programEmitComplete;
@@ -373,19 +387,24 @@ namespace ts {
/**
* Returns next file to be emitted from files that retrieved semantic diagnostics but did not emit yet
*/
function getNextAffectedFilePendingEmit(state: BuilderProgramState): SourceFile | undefined {
function getNextAffectedFilePendingEmit(state: BuilderProgramState) {
const { affectedFilesPendingEmit } = state;
if (affectedFilesPendingEmit) {
const seenEmittedFiles = state.seenEmittedFiles || (state.seenEmittedFiles = createMap());
for (let i = state.affectedFilesPendingEmitIndex!; i < affectedFilesPendingEmit.length; i++) {
const affectedFile = Debug.assertDefined(state.program).getSourceFileByPath(affectedFilesPendingEmit[i]);
if (affectedFile && !seenEmittedFiles.has(affectedFile.path)) {
// emit this file
state.affectedFilesPendingEmitIndex = i;
return affectedFile;
if (affectedFile) {
const seenKind = seenEmittedFiles.get(affectedFile.path);
const emitKind = Debug.assertDefined(Debug.assertDefined(state.affectedFilesPendingEmitKind).get(affectedFile.path));
if (seenKind === undefined || seenKind < emitKind) {
// emit this file
state.affectedFilesPendingEmitIndex = i;
return { affectedFile, emitKind };
}
}
}
state.affectedFilesPendingEmit = undefined;
state.affectedFilesPendingEmitKind = undefined;
state.affectedFilesPendingEmitIndex = undefined;
}
return undefined;
@@ -406,7 +425,7 @@ namespace ts {
const options = program.getCompilerOptions();
forEach(program.getSourceFiles(), f =>
program.isSourceFileDefaultLibrary(f) &&
!skipTypeChecking(f, options) &&
!skipTypeChecking(f, options, program) &&
removeSemanticDiagnosticsOf(state, f.path)
);
}
@@ -442,7 +461,7 @@ namespace ts {
);
// If not dts emit, nothing more to do
if (getEmitDeclarations(state.compilerOptions)) {
addToAffectedFilesPendingEmit(state, [path]);
addToAffectedFilesPendingEmit(state, path, BuilderFileEmit.DtsOnly);
}
}
}
@@ -463,16 +482,43 @@ namespace ts {
return !state.semanticDiagnosticsFromOldState.size;
}
function isChangedSignagure(state: BuilderProgramState, path: Path) {
const newSignature = Debug.assertDefined(state.currentAffectedFilesSignatures).get(path);
const oldSignagure = Debug.assertDefined(state.fileInfos.get(path)).signature;
return newSignature !== oldSignagure;
}
/**
* Iterate on referencing modules that export entities from affected file
*/
function forEachReferencingModulesOfExportOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, fn: (state: BuilderProgramState, filePath: Path) => boolean) {
// If there was change in signature (dts output) for the changed file,
// then only we need to handle pending file emit
if (!state.exportedModulesMap || state.affectedFiles!.length === 1 || !state.changedFilesSet.has(affectedFile.path)) {
if (!state.exportedModulesMap || !state.changedFilesSet.has(affectedFile.path)) {
return;
}
if (!isChangedSignagure(state, affectedFile.path)) return;
// Since isolated modules dont change js files, files affected by change in signature is itself
// But we need to cleanup semantic diagnostics and queue dts emit for affected files
if (state.compilerOptions.isolatedModules) {
const seenFileNamesMap = createMap<true>();
seenFileNamesMap.set(affectedFile.path, true);
const queue = BuilderState.getReferencedByPaths(state, affectedFile.resolvedPath);
while (queue.length > 0) {
const currentPath = queue.pop()!;
if (!seenFileNamesMap.has(currentPath)) {
seenFileNamesMap.set(currentPath, true);
const result = fn(state, currentPath);
if (result && isChangedSignagure(state, currentPath)) {
const currentSourceFile = Debug.assertDefined(state.program).getSourceFileByPath(currentPath)!;
queue.push(...BuilderState.getReferencedByPaths(state, currentSourceFile.resolvedPath));
}
}
}
}
Debug.assert(!!state.currentAffectedFilesExportedModulesMap);
const seenFileAndExportsOfFile = createMap<true>();
// Go through exported modules from cache first
@@ -548,7 +594,13 @@ 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, isBuildInfoEmit?: boolean, isEmitResult?: boolean) {
function doneWithAffectedFile(
state: BuilderProgramState,
affected: SourceFile | Program,
emitKind?: BuilderFileEmit,
isPendingEmit?: boolean,
isBuildInfoEmit?: boolean
) {
if (isBuildInfoEmit) {
state.emittedBuildInfo = true;
}
@@ -558,8 +610,8 @@ namespace ts {
}
else {
state.seenAffectedFiles!.set((affected as SourceFile).path, true);
if (isEmitResult) {
(state.seenEmittedFiles || (state.seenEmittedFiles = createMap())).set((affected as SourceFile).path, true);
if (emitKind !== undefined) {
(state.seenEmittedFiles || (state.seenEmittedFiles = createMap())).set((affected as SourceFile).path, emitKind);
}
if (isPendingEmit) {
state.affectedFilesPendingEmitIndex!++;
@@ -573,16 +625,23 @@ namespace ts {
/**
* Returns the result with affected file
*/
function toAffectedFileResult<T>(state: BuilderProgramState, result: T, affected: SourceFile | Program, isPendingEmit?: boolean, isBuildInfoEmit?: boolean): AffectedFileResult<T> {
doneWithAffectedFile(state, affected, isPendingEmit, isBuildInfoEmit);
function toAffectedFileResult<T>(state: BuilderProgramState, result: T, affected: SourceFile | Program): AffectedFileResult<T> {
doneWithAffectedFile(state, affected);
return { result, affected };
}
/**
* Returns the result with affected file
*/
function toAffectedFileEmitResult(state: BuilderProgramState, result: EmitResult, affected: SourceFile | Program, isPendingEmit?: boolean, isBuildInfoEmit?: boolean): AffectedFileResult<EmitResult> {
doneWithAffectedFile(state, affected, isPendingEmit, isBuildInfoEmit, /*isEmitResult*/ true);
function toAffectedFileEmitResult(
state: BuilderProgramState,
result: EmitResult,
affected: SourceFile | Program,
emitKind: BuilderFileEmit,
isPendingEmit?: boolean,
isBuildInfoEmit?: boolean
): AffectedFileResult<EmitResult> {
doneWithAffectedFile(state, affected, emitKind, isPendingEmit, isBuildInfoEmit);
return { result, affected };
}
@@ -623,7 +682,7 @@ namespace ts {
function getProgramBuildInfo(state: Readonly<ReusableBuilderProgramState>, getCanonicalFileName: GetCanonicalFileName): ProgramBuildInfo | undefined {
if (state.compilerOptions.outFile || state.compilerOptions.out) return undefined;
const currentDirectory = Debug.assertDefined(state.program).getCurrentDirectory();
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getOutputPathForBuildInfo(state.compilerOptions)!, currentDirectory));
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory));
const fileInfos: MapLike<BuilderState.FileInfo> = {};
state.fileInfos.forEach((value, key) => {
const signature = state.currentAffectedFilesSignatures && state.currentAffectedFilesSignatures.get(key);
@@ -849,11 +908,12 @@ namespace ts {
*/
function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult<EmitResult> {
let affected = getNextAffectedFile(state, cancellationToken, computeHash);
let emitKind = BuilderFileEmit.Full;
let isPendingEmitFile = false;
if (!affected) {
if (!state.compilerOptions.out && !state.compilerOptions.outFile) {
affected = getNextAffectedFilePendingEmit(state);
if (!affected) {
const pendingAffectedFile = getNextAffectedFilePendingEmit(state);
if (!pendingAffectedFile) {
if (state.emittedBuildInfo) {
return undefined;
}
@@ -865,10 +925,12 @@ namespace ts {
// Otherwise just affected file
affected.emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken),
affected,
/*emitKind*/ BuilderFileEmit.Full,
/*isPendingEmitFile*/ false,
/*isBuildInfoEmit*/ true
);
}
({ affectedFile: affected, emitKind } = pendingAffectedFile);
isPendingEmitFile = true;
}
else {
@@ -886,10 +948,17 @@ namespace ts {
state,
// When whole program is affected, do emit only once (eg when --out or --outFile is specified)
// Otherwise just affected file
Debug.assertDefined(state.program).emit(affected === state.program ? undefined : affected as SourceFile, writeFile || maybeBind(host, host.writeFile), cancellationToken, emitOnlyDtsFiles, customTransformers),
Debug.assertDefined(state.program).emit(
affected === state.program ? undefined : affected as SourceFile,
writeFile || maybeBind(host, host.writeFile),
cancellationToken,
emitOnlyDtsFiles || emitKind === BuilderFileEmit.DtsOnly,
customTransformers
),
affected,
emitKind,
isPendingEmitFile,
);
);
}
/**
@@ -953,7 +1022,7 @@ namespace ts {
// Add file to affected file pending emit to handle for later emit time
if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
addToAffectedFilesPendingEmit(state, [(affected as SourceFile).path]);
addToAffectedFilesPendingEmit(state, (affected as SourceFile).path, BuilderFileEmit.Full);
}
// Get diagnostics for the affected file if its not ignored
@@ -1006,8 +1075,14 @@ namespace ts {
}
}
function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilesPendingEmit: readonly Path[]) {
state.affectedFilesPendingEmit = concatenate(state.affectedFilesPendingEmit, affectedFilesPendingEmit);
function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilePendingEmit: Path, kind: BuilderFileEmit) {
if (!state.affectedFilesPendingEmit) state.affectedFilesPendingEmit = [];
if (!state.affectedFilesPendingEmitKind) state.affectedFilesPendingEmitKind = createMap();
const existingKind = state.affectedFilesPendingEmitKind.get(affectedFilePendingEmit);
state.affectedFilesPendingEmit.push(affectedFilePendingEmit);
state.affectedFilesPendingEmitKind.set(affectedFilePendingEmit, existingKind || kind);
// affectedFilesPendingEmitIndex === undefined
// - means the emit state.affectedFilesPendingEmit was undefined before adding current affected files
// so start from 0 as array would be affectedFilesPendingEmit
+11 -4
View File
@@ -15,9 +15,9 @@ namespace ts {
/*@internal*/
namespace ts {
export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput {
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitOutput {
const outputFiles: OutputFile[] = [];
const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, forceDtsEmit);
return { outputFiles, emitSkipped: emitResult.emitSkipped, exportedModulesFromDeclarationEmit: emitResult.exportedModulesFromDeclarationEmit };
function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) {
@@ -344,7 +344,14 @@ namespace ts.BuilderState {
}
}
else {
const emitOutput = getFileEmitOutput(programOfThisState, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken);
const emitOutput = getFileEmitOutput(
programOfThisState,
sourceFile,
/*emitOnlyDtsFiles*/ true,
cancellationToken,
/*customTransformers*/ undefined,
/*forceDtsEmit*/ true
);
const firstDts = emitOutput.outputFiles &&
programOfThisState.getCompilerOptions().declarationMap ?
emitOutput.outputFiles.length > 1 ? emitOutput.outputFiles[1] : undefined :
@@ -459,7 +466,7 @@ namespace ts.BuilderState {
/**
* Gets the files referenced by the the file path
*/
function getReferencedByPaths(state: Readonly<BuilderState>, referencedFilePath: Path) {
export function getReferencedByPaths(state: Readonly<BuilderState>, referencedFilePath: Path) {
return arrayFrom(mapDefinedIterator(state.referencedMap!.entries(), ([filePath, referencesInFile]) =>
referencesInFile.has(referencedFilePath) ? filePath as Path : undefined
));
+673 -253
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -772,6 +772,12 @@ namespace ts {
category: Diagnostics.Advanced_Options,
description: Diagnostics.Disable_size_limitations_on_JavaScript_projects
},
{
name: "disableSourceOfProjectReferenceRedirect",
type: "boolean",
category: Diagnostics.Advanced_Options,
description: Diagnostics.Disable_use_of_source_files_instead_of_declaration_files_from_referenced_projects
},
{
name: "noImplicitUseStrict",
type: "boolean",
+1 -1
View File
@@ -1654,7 +1654,7 @@ namespace ts {
*/
export function compose<T>(...args: ((t: T) => T)[]): (t: T) => T;
export function compose<T>(a: (t: T) => T, b: (t: T) => T, c: (t: T) => T, d: (t: T) => T, e: (t: T) => T): (t: T) => T {
if (e) {
if (!!e) {
const args: ((t: T) => T)[] = [];
for (let i = 0; i < arguments.length; i++) {
args[i] = arguments[i];
+57 -1
View File
@@ -1035,6 +1035,39 @@
"category": "Error",
"code": 1356
},
"An enum member name must be followed by a ',', '=', or '}'.": {
"category": "Error",
"code": 1357
},
"The types of '{0}' are incompatible between these types.": {
"category": "Error",
"code": 2200
},
"The types returned by '{0}' are incompatible between these types.": {
"category": "Error",
"code": 2201
},
"Call signature return types '{0}' and '{1}' are incompatible.": {
"category": "Error",
"code": 2202,
"elidedInCompatabilityPyramid": true
},
"Construct signature return types '{0}' and '{1}' are incompatible.": {
"category": "Error",
"code": 2203,
"elidedInCompatabilityPyramid": true
},
"Call signatures with no arguments have incompatible return types '{0}' and '{1}'.": {
"category": "Error",
"code": 2204,
"elidedInCompatabilityPyramid": true
},
"Construct signatures with no arguments have incompatible return types '{0}' and '{1}'.": {
"category": "Error",
"code": 2205,
"elidedInCompatabilityPyramid": true
},
"Duplicate identifier '{0}'.": {
"category": "Error",
@@ -2689,7 +2722,10 @@
"category": "Error",
"code": 2773
},
"This condition will always return true since the function is always defined. Did you mean to call it instead?": {
"category": "Error",
"code": 2774
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
"code": 4000
@@ -4003,6 +4039,10 @@
"category": "Message",
"code": 6220
},
"Disable use of source files instead of declaration files from referenced projects.": {
"category": "Message",
"code": 6221
},
"Projects to reference": {
"category": "Message",
@@ -4643,6 +4683,10 @@
"category": "Suggestion",
"code": 80007
},
"Numeric literals with absolute values equal to 2^53 or greater are too large to be represented accurately as integers.": {
"category": "Suggestion",
"code": 80008
},
"Add missing 'super()' call": {
"category": "Message",
@@ -5124,6 +5168,18 @@
"category": "Message",
"code": 95090
},
"Convert to a bigint numeric literal": {
"category": "Message",
"code": 95091
},
"Convert all to bigint numeric literals": {
"category": "Message",
"code": 95092
},
"Convert 'const' to 'let'": {
"category": "Message",
"code": 95093
},
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
+85 -40
View File
@@ -20,7 +20,7 @@ namespace ts {
export function forEachEmittedFile<T>(
host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) => T,
sourceFilesOrTargetSourceFile?: readonly SourceFile[] | SourceFile,
emitOnlyDtsFiles = false,
forceDtsEmit = false,
onlyBuildInfo?: boolean,
includeBuildInfo?: boolean) {
const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile);
@@ -29,7 +29,7 @@ namespace ts {
const prepends = host.getPrependNodes();
if (sourceFiles.length || prepends.length) {
const bundle = createBundle(sourceFiles, prepends);
const result = action(getOutputPathsFor(bundle, host, emitOnlyDtsFiles), bundle);
const result = action(getOutputPathsFor(bundle, host, forceDtsEmit), bundle);
if (result) {
return result;
}
@@ -38,21 +38,20 @@ namespace ts {
else {
if (!onlyBuildInfo) {
for (const sourceFile of sourceFiles) {
const result = action(getOutputPathsFor(sourceFile, host, emitOnlyDtsFiles), sourceFile);
const result = action(getOutputPathsFor(sourceFile, host, forceDtsEmit), sourceFile);
if (result) {
return result;
}
}
}
if (includeBuildInfo) {
const buildInfoPath = getOutputPathForBuildInfo(host.getCompilerOptions());
const buildInfoPath = getTsBuildInfoEmitOutputFilePath(host.getCompilerOptions());
if (buildInfoPath) return action({ buildInfoPath }, /*sourceFileOrBundle*/ undefined);
}
}
}
/*@internal*/
export function getOutputPathForBuildInfo(options: CompilerOptions) {
export function getTsBuildInfoEmitOutputFilePath(options: CompilerOptions) {
const configFile = options.configFilePath;
if (!isIncrementalCompilation(options)) return undefined;
if (options.tsBuildInfoFile) return options.tsBuildInfoFile;
@@ -80,7 +79,7 @@ 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 buildInfoPath = getOutputPathForBuildInfo(options);
const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options);
return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath };
}
@@ -170,38 +169,71 @@ namespace ts {
undefined;
}
function createAddOutput() {
let outputs: string[] | undefined;
return { addOutput, getOutputs };
function addOutput(path: string | undefined) {
if (path) {
(outputs || (outputs = [])).push(path);
}
}
function getOutputs(): readonly string[] {
return outputs || emptyArray;
}
}
function getSingleOutputFileNames(configFile: ParsedCommandLine, addOutput: ReturnType<typeof createAddOutput>["addOutput"]) {
const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false);
addOutput(jsFilePath);
addOutput(sourceMapFilePath);
addOutput(declarationFilePath);
addOutput(declarationMapPath);
addOutput(buildInfoPath);
}
function getOwnOutputFileNames(configFile: ParsedCommandLine, inputFileName: string, ignoreCase: boolean, addOutput: ReturnType<typeof createAddOutput>["addOutput"]) {
if (fileExtensionIs(inputFileName, Extension.Dts)) return;
const js = getOutputJSFileName(inputFileName, configFile, ignoreCase);
addOutput(js);
if (fileExtensionIs(inputFileName, Extension.Json)) return;
if (js && configFile.options.sourceMap) {
addOutput(`${js}.map`);
}
if (getEmitDeclarations(configFile.options) && hasTSFileExtension(inputFileName)) {
const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase);
addOutput(dts);
if (configFile.options.declarationMap) {
addOutput(`${dts}.map`);
}
}
}
/*@internal*/
export function getAllProjectOutputs(configFile: ParsedCommandLine, ignoreCase: boolean): readonly string[] {
let outputs: string[] | undefined;
const addOutput = (path: string | undefined) => path && (outputs || (outputs = [])).push(path);
const { addOutput, getOutputs } = createAddOutput();
if (configFile.options.outFile || configFile.options.out) {
const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false);
addOutput(jsFilePath);
addOutput(sourceMapFilePath);
addOutput(declarationFilePath);
addOutput(declarationMapPath);
addOutput(buildInfoPath);
getSingleOutputFileNames(configFile, addOutput);
}
else {
for (const inputFileName of configFile.fileNames) {
if (fileExtensionIs(inputFileName, Extension.Dts)) continue;
const js = getOutputJSFileName(inputFileName, configFile, ignoreCase);
addOutput(js);
if (fileExtensionIs(inputFileName, Extension.Json)) continue;
if (js && configFile.options.sourceMap) {
addOutput(`${js}.map`);
}
if (getEmitDeclarations(configFile.options) && hasTSFileExtension(inputFileName)) {
const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase);
addOutput(dts);
if (configFile.options.declarationMap) {
addOutput(`${dts}.map`);
}
}
getOwnOutputFileNames(configFile, inputFileName, ignoreCase, addOutput);
}
addOutput(getOutputPathForBuildInfo(configFile.options));
addOutput(getTsBuildInfoEmitOutputFilePath(configFile.options));
}
return outputs || emptyArray;
return getOutputs();
}
export function getOutputFileNames(commandLine: ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[] {
inputFileName = normalizePath(inputFileName);
Debug.assert(contains(commandLine.fileNames, inputFileName), `Expected fileName to be present in command line`);
const { addOutput, getOutputs } = createAddOutput();
if (commandLine.options.outFile || commandLine.options.out) {
getSingleOutputFileNames(commandLine, addOutput);
}
else {
getOwnOutputFileNames(commandLine, inputFileName, ignoreCase, addOutput);
}
return getOutputs();
}
/*@internal*/
@@ -220,14 +252,14 @@ namespace ts {
return getOutputDeclarationFileName(inputFileName, configFile, ignoreCase);
}
}
const buildInfoPath = getOutputPathForBuildInfo(configFile.options);
const buildInfoPath = getTsBuildInfoEmitOutputFilePath(configFile.options);
if (buildInfoPath) return buildInfoPath;
return Debug.fail(`project ${configFile.options.configFilePath} expected to have at least one output`);
}
/*@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 | undefined, { scriptTransformers, declarationTransformers }: EmitTransformers, emitOnlyDtsFiles?: boolean, onlyBuildInfo?: boolean): EmitResult {
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, { scriptTransformers, declarationTransformers }: EmitTransformers, emitOnlyDtsFiles?: boolean, onlyBuildInfo?: boolean, forceDtsEmit?: boolean): EmitResult {
const compilerOptions = host.getCompilerOptions();
const sourceMapDataList: SourceMapEmitResult[] | undefined = (compilerOptions.sourceMap || compilerOptions.inlineSourceMap || getAreDeclarationMapsEnabled(compilerOptions)) ? [] : undefined;
const emittedFilesList: string[] | undefined = compilerOptions.listEmittedFiles ? [] : undefined;
@@ -241,7 +273,14 @@ namespace ts {
// Emit each output file
enter();
forEachEmittedFile(host, emitSourceFileOrBundle, getSourceFilesToEmit(host, targetSourceFile), emitOnlyDtsFiles, onlyBuildInfo, !targetSourceFile);
forEachEmittedFile(
host,
emitSourceFileOrBundle,
getSourceFilesToEmit(host, targetSourceFile),
forceDtsEmit,
onlyBuildInfo,
!targetSourceFile
);
exit();
@@ -400,7 +439,7 @@ namespace ts {
});
const declBlocked = (!!declarationTransform.diagnostics && !!declarationTransform.diagnostics.length) || !!host.isEmitBlocked(declarationFilePath) || !!compilerOptions.noEmit;
emitSkipped = emitSkipped || declBlocked;
if (!declBlocked || emitOnlyDtsFiles) {
if (!declBlocked || forceDtsEmit) {
Debug.assert(declarationTransform.transformed.length === 1, "Should only see one output from the decl transform");
printSourceFileOrBundle(
declarationFilePath,
@@ -415,7 +454,7 @@ namespace ts {
// Explicitly do not passthru either `inline` option
}
);
if (emitOnlyDtsFiles && declarationTransform.transformed[0].kind === SyntaxKind.SourceFile) {
if (forceDtsEmit && declarationTransform.transformed[0].kind === SyntaxKind.SourceFile) {
const sourceFile = declarationTransform.transformed[0];
exportedModulesFromDeclarationEmit = sourceFile.exportedModulesFromDeclarationEmit;
}
@@ -1907,11 +1946,17 @@ namespace ts {
//
function emitTypePredicate(node: TypePredicateNode) {
if (node.assertsModifier) {
emit(node.assertsModifier);
writeSpace();
}
emit(node.parameterName);
writeSpace();
writeKeyword("is");
writeSpace();
emit(node.type);
if (node.type) {
writeSpace();
writeKeyword("is");
writeSpace();
emit(node.type);
}
}
function emitTypeReference(node: TypeReferenceNode) {
+12 -2
View File
@@ -669,16 +669,26 @@ namespace ts {
}
export function createTypePredicateNode(parameterName: Identifier | ThisTypeNode | string, type: TypeNode) {
return createTypePredicateNodeWithModifier(/*assertsModifier*/ undefined, parameterName, type);
}
export function createTypePredicateNodeWithModifier(assertsModifier: AssertsToken | undefined, parameterName: Identifier | ThisTypeNode | string, type: TypeNode | undefined) {
const node = createSynthesizedNode(SyntaxKind.TypePredicate) as TypePredicateNode;
node.assertsModifier = assertsModifier;
node.parameterName = asName(parameterName);
node.type = type;
return node;
}
export function updateTypePredicateNode(node: TypePredicateNode, parameterName: Identifier | ThisTypeNode, type: TypeNode) {
return node.parameterName !== parameterName
return updateTypePredicateNodeWithModifier(node, node.assertsModifier, parameterName, type);
}
export function updateTypePredicateNodeWithModifier(node: TypePredicateNode, assertsModifier: AssertsToken | undefined, parameterName: Identifier | ThisTypeNode, type: TypeNode | undefined) {
return node.assertsModifier !== assertsModifier
|| node.parameterName !== parameterName
|| node.type !== type
? updateNode(createTypePredicateNode(parameterName, type), node)
? updateNode(createTypePredicateNodeWithModifier(assertsModifier, parameterName, type), node)
: node;
}
+23 -2
View File
@@ -165,7 +165,8 @@ namespace ts {
return visitNode(cbNode, (<TypeReferenceNode>node).typeName) ||
visitNodes(cbNode, cbNodes, (<TypeReferenceNode>node).typeArguments);
case SyntaxKind.TypePredicate:
return visitNode(cbNode, (<TypePredicateNode>node).parameterName) ||
return visitNode(cbNode, (<TypePredicateNode>node).assertsModifier) ||
visitNode(cbNode, (<TypePredicateNode>node).parameterName) ||
visitNode(cbNode, (<TypePredicateNode>node).type);
case SyntaxKind.TypeQuery:
return visitNode(cbNode, (<TypeQueryNode>node).exprName);
@@ -770,7 +771,11 @@ namespace ts {
fixupParentReferences(sourceFile);
}
sourceFile.nodeCount = nodeCount;
sourceFile.identifierCount = identifierCount;
sourceFile.identifiers = identifiers;
sourceFile.parseDiagnostics = parseDiagnostics;
const result = sourceFile as JsonSourceFile;
clearState();
return result;
@@ -2121,7 +2126,7 @@ namespace ts {
// We didn't get a comma, and the list wasn't terminated, explicitly parse
// out a comma so we give a good error message.
parseExpected(SyntaxKind.CommaToken);
parseExpected(SyntaxKind.CommaToken, getExpectedCommaDiagnostic(kind));
// If the token was a semicolon, and the caller allows that, then skip it and
// continue. This ensures we get back on track and don't result in tons of
@@ -2164,6 +2169,10 @@ namespace ts {
return result;
}
function getExpectedCommaDiagnostic(kind: ParsingContext) {
return kind === ParsingContext.EnumMembers ? Diagnostics.An_enum_member_name_must_be_followed_by_a_or : undefined;
}
interface MissingList<T extends Node> extends NodeArray<T> {
isMissingList: true;
}
@@ -3033,6 +3042,8 @@ namespace ts {
return parseParenthesizedType();
case SyntaxKind.ImportKeyword:
return parseImportType();
case SyntaxKind.AssertsKeyword:
return lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine) ? parseAssertsTypePredicate() : parseTypeReference();
default:
return parseTypeReference();
}
@@ -3073,6 +3084,7 @@ namespace ts {
case SyntaxKind.DotDotDotToken:
case SyntaxKind.InferKeyword:
case SyntaxKind.ImportKeyword:
case SyntaxKind.AssertsKeyword:
return true;
case SyntaxKind.FunctionKeyword:
return !inStartOfParameter;
@@ -3249,6 +3261,7 @@ namespace ts {
const type = parseType();
if (typePredicateVariable) {
const node = <TypePredicateNode>createNode(SyntaxKind.TypePredicate, typePredicateVariable.pos);
node.assertsModifier = undefined;
node.parameterName = typePredicateVariable;
node.type = type;
return finishNode(node);
@@ -3266,6 +3279,14 @@ namespace ts {
}
}
function parseAssertsTypePredicate(): TypeNode {
const node = <TypePredicateNode>createNode(SyntaxKind.TypePredicate);
node.assertsModifier = parseExpectedToken(SyntaxKind.AssertsKeyword);
node.parameterName = token() === SyntaxKind.ThisKeyword ? parseThisTypeNode() : parseIdentifier();
node.type = parseOptional(SyntaxKind.IsKeyword) ? parseType() : undefined;
return finishNode(node);
}
function parseType(): TypeNode {
// The rules about 'yield' only apply to actual code/expression contexts. They don't
// apply to 'type' contexts. So we disable these parameters here before moving on.
+113 -42
View File
@@ -817,6 +817,8 @@ namespace ts {
let resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined;
let projectReferenceRedirects: Map<ResolvedProjectReference | false> | undefined;
let mapFromFileToProjectReferenceRedirects: Map<Path> | undefined;
let mapFromToProjectReferenceRedirectSource: Map<SourceOfProjectReferenceRedirect> | undefined;
const useSourceOfProjectReferenceRedirect = !!host.useSourceOfProjectReferenceRedirect && host.useSourceOfProjectReferenceRedirect();
const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
// We set `structuralIsReused` to `undefined` because `tryReuseStructureFromOldProgram` calls `tryReuseStructureFromOldProgram` which checks
@@ -831,17 +833,32 @@ namespace ts {
if (!resolvedProjectReferences) {
resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile);
}
if (host.setResolvedProjectReferenceCallbacks) {
host.setResolvedProjectReferenceCallbacks({
getSourceOfProjectReferenceRedirect,
forEachResolvedProjectReference
});
}
if (rootNames.length) {
for (const parsedRef of resolvedProjectReferences) {
if (!parsedRef) continue;
const out = parsedRef.commandLine.options.outFile || parsedRef.commandLine.options.out;
if (out) {
processSourceFile(changeExtension(out, ".d.ts"), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
if (useSourceOfProjectReferenceRedirect) {
if (out || getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) {
for (const fileName of parsedRef.commandLine.fileNames) {
processSourceFile(fileName, /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
}
}
}
else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) {
for (const fileName of parsedRef.commandLine.fileNames) {
if (!fileExtensionIs(fileName, Extension.Dts) && hasTSFileExtension(fileName)) {
processSourceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames()), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
else {
if (out) {
processSourceFile(changeExtension(out, ".d.ts"), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
}
else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) {
for (const fileName of parsedRef.commandLine.fileNames) {
if (!fileExtensionIs(fileName, Extension.Dts) && hasTSFileExtension(fileName)) {
processSourceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames()), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
}
}
}
}
@@ -955,6 +972,7 @@ namespace ts {
getResolvedProjectReferenceToRedirect,
getResolvedProjectReferenceByPath,
forEachResolvedProjectReference,
isSourceOfProjectReferenceRedirect,
emitBuildInfo
};
@@ -987,9 +1005,15 @@ namespace ts {
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
}
function isValidSourceFileForEmit(file: SourceFile) {
// source file is allowed to be emitted and its not source of project reference redirect
return sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect) &&
!isSourceOfProjectReferenceRedirect(file.fileName);
}
function getCommonSourceDirectory() {
if (commonSourceDirectory === undefined) {
const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect));
const emittedFiles = filter(files, file => isValidSourceFileForEmit(file));
if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) {
// If a rootDir is specified use it as the commonSourceDirectory
commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory);
@@ -1220,6 +1244,12 @@ namespace ts {
}
if (projectReferences) {
resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile);
if (host.setResolvedProjectReferenceCallbacks) {
host.setResolvedProjectReferenceCallbacks({
getSourceOfProjectReferenceRedirect,
forEachResolvedProjectReference
});
}
}
// check if program source files has changed in the way that can affect structure of the program
@@ -1359,18 +1389,16 @@ namespace ts {
// try to verify results of module resolution
for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) {
const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.originalFileName, currentDirectory);
if (resolveModuleNamesWorker) {
const moduleNames = getModuleNames(newSourceFile);
const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile);
// ensure that module resolution results are still correct
const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo);
if (resolutionsChanged) {
oldProgram.structureIsReused = StructureIsReused.SafeModules;
newSourceFile.resolvedModules = zipToMap(moduleNames, resolutions);
}
else {
newSourceFile.resolvedModules = oldSourceFile.resolvedModules;
}
const moduleNames = getModuleNames(newSourceFile);
const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile);
// ensure that module resolution results are still correct
const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo);
if (resolutionsChanged) {
oldProgram.structureIsReused = StructureIsReused.SafeModules;
newSourceFile.resolvedModules = zipToMap(moduleNames, resolutions);
}
else {
newSourceFile.resolvedModules = oldSourceFile.resolvedModules;
}
if (resolveTypeReferenceDirectiveNamesWorker) {
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
@@ -1403,6 +1431,13 @@ namespace ts {
for (const newSourceFile of newSourceFiles) {
const filePath = newSourceFile.path;
addFileToFilesByName(newSourceFile, filePath, newSourceFile.resolvedPath);
if (useSourceOfProjectReferenceRedirect) {
const redirectProject = getProjectReferenceRedirectProject(newSourceFile.fileName);
if (redirectProject && !(redirectProject.commandLine.options.outFile || redirectProject.commandLine.options.out)) {
const redirect = getProjectReferenceOutputName(redirectProject, newSourceFile.fileName);
addFileToFilesByName(newSourceFile, toPath(redirect), /*redirectedPath*/ undefined);
}
}
// Set the file as found during node modules search if it was found that way in old progra,
if (oldProgram.isSourceFileFromExternalLibrary(oldProgram.getSourceFileByPath(newSourceFile.resolvedPath)!)) {
sourceFilesFoundSearchingNodeModules.set(filePath, true);
@@ -1530,18 +1565,18 @@ namespace ts {
return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false));
}
function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, transformers?: CustomTransformers): EmitResult {
return runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnlyDtsFiles, transformers));
function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, transformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult {
return runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnlyDtsFiles, transformers, forceDtsEmit));
}
function isEmitBlocked(emitFileName: string): boolean {
return hasEmitBlockingDiagnostics.has(toPath(emitFileName));
}
function emitWorker(program: Program, sourceFile: SourceFile | undefined, writeFileCallback: WriteFileCallback | undefined, cancellationToken: CancellationToken | undefined, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult {
function emitWorker(program: Program, sourceFile: SourceFile | undefined, writeFileCallback: WriteFileCallback | undefined, cancellationToken: CancellationToken | undefined, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult {
let declarationDiagnostics: readonly Diagnostic[] = [];
if (!emitOnlyDtsFiles) {
if (!forceDtsEmit) {
if (options.noEmit) {
return { diagnostics: declarationDiagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true };
}
@@ -1590,6 +1625,8 @@ namespace ts {
sourceFile,
getTransformers(options, customTransformers, emitOnlyDtsFiles),
emitOnlyDtsFiles,
/*onlyBuildInfo*/ false,
forceDtsEmit
);
performance.mark("afterEmit");
@@ -1680,7 +1717,7 @@ namespace ts {
function getSemanticDiagnosticsForFileNoCache(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] | undefined {
return runWithCancellationToken(() => {
if (skipTypeChecking(sourceFile, options)) {
if (skipTypeChecking(sourceFile, options, program)) {
return emptyArray;
}
@@ -1757,13 +1794,12 @@ namespace ts {
switch (parent.kind) {
case SyntaxKind.Parameter:
case SyntaxKind.PropertyDeclaration:
if ((<ParameterDeclaration | PropertyDeclaration>parent).questionToken === node) {
case SyntaxKind.MethodDeclaration:
if ((<ParameterDeclaration | PropertyDeclaration | MethodDeclaration>parent).questionToken === node) {
diagnostics.push(createDiagnosticForNode(node, Diagnostics._0_can_only_be_used_in_a_ts_file, "?"));
return;
}
// falls through
case SyntaxKind.MethodDeclaration:
// falls through
case SyntaxKind.MethodSignature:
case SyntaxKind.Constructor:
case SyntaxKind.GetAccessor:
@@ -1833,7 +1869,6 @@ namespace ts {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.Constructor:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
@@ -1841,7 +1876,7 @@ namespace ts {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.ArrowFunction:
// Check type parameters
if (nodes === (<ClassLikeDeclaration | FunctionLikeDeclaration>parent).typeParameters) {
if (nodes === (<DeclarationWithTypeParameterChildren>parent).typeParameters) {
diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.type_parameter_declarations_can_only_be_used_in_a_ts_file));
return;
}
@@ -1849,8 +1884,8 @@ namespace ts {
case SyntaxKind.VariableStatement:
// Check modifiers
if (nodes === (<ClassDeclaration | FunctionLikeDeclaration | VariableStatement>parent).modifiers) {
return checkModifiers(<NodeArray<Modifier>>nodes, parent.kind === SyntaxKind.VariableStatement);
if (nodes === parent.modifiers) {
return checkModifiers(parent.modifiers, parent.kind === SyntaxKind.VariableStatement);
}
break;
case SyntaxKind.PropertyDeclaration:
@@ -1876,8 +1911,9 @@ namespace ts {
case SyntaxKind.ExpressionWithTypeArguments:
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.TaggedTemplateExpression:
// Check type arguments
if (nodes === (<CallExpression | NewExpression | ExpressionWithTypeArguments | JsxOpeningLikeElement>parent).typeArguments) {
if (nodes === (<NodeWithTypeArguments>parent).typeArguments) {
diagnostics.push(createDiagnosticForNodeArray(nodes, Diagnostics.type_arguments_can_only_be_used_in_a_ts_file));
return;
}
@@ -2233,6 +2269,16 @@ namespace ts {
// Get source file from normalized fileName
function findSourceFile(fileName: string, path: Path, isDefaultLib: boolean, ignoreNoDefaultLib: boolean, refFile: RefFile | undefined, packageId: PackageId | undefined): SourceFile | undefined {
if (useSourceOfProjectReferenceRedirect) {
const source = getSourceOfProjectReferenceRedirect(fileName);
if (source) {
const file = isString(source) ?
findSourceFile(source, toPath(source), isDefaultLib, ignoreNoDefaultLib, refFile, packageId) :
undefined;
if (file) addFileToFilesByName(file, path, /*redirectedPath*/ undefined);
return file;
}
}
const originalFileName = fileName;
if (filesByName.has(path)) {
const file = filesByName.get(path);
@@ -2281,7 +2327,7 @@ namespace ts {
}
let redirectedPath: Path | undefined;
if (refFile) {
if (refFile && !useSourceOfProjectReferenceRedirect) {
const redirectProject = getProjectReferenceRedirectProject(fileName);
if (redirectProject) {
if (redirectProject.commandLine.options.outFile || redirectProject.commandLine.options.out) {
@@ -2450,6 +2496,36 @@ namespace ts {
});
}
function getSourceOfProjectReferenceRedirect(file: string) {
if (!isDeclarationFileName(file)) return undefined;
if (mapFromToProjectReferenceRedirectSource === undefined) {
mapFromToProjectReferenceRedirectSource = createMap();
forEachResolvedProjectReference(resolvedRef => {
if (resolvedRef) {
const out = resolvedRef.commandLine.options.outFile || resolvedRef.commandLine.options.out;
if (out) {
// Dont know which source file it means so return true?
const outputDts = changeExtension(out, Extension.Dts);
mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), true);
}
else {
forEach(resolvedRef.commandLine.fileNames, fileName => {
if (!fileExtensionIs(fileName, Extension.Dts) && hasTSFileExtension(fileName)) {
const outputDts = getOutputDeclarationFileName(fileName, resolvedRef.commandLine, host.useCaseSensitiveFileNames());
mapFromToProjectReferenceRedirectSource!.set(toPath(outputDts), fileName);
}
});
}
}
});
}
return mapFromToProjectReferenceRedirectSource.get(toPath(file));
}
function isSourceOfProjectReferenceRedirect(fileName: string) {
return useSourceOfProjectReferenceRedirect && !!getResolvedProjectReferenceToRedirect(fileName);
}
function forEachProjectReference<T>(
projectReferences: readonly ProjectReference[] | undefined,
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
@@ -2806,10 +2882,6 @@ namespace ts {
}
if (options.isolatedModules) {
if (getEmitDeclarations(options)) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, getEmitDeclarationOptionName(options), "isolatedModules");
}
if (options.out) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "out", "isolatedModules");
}
@@ -2861,8 +2933,7 @@ namespace ts {
const rootPaths = arrayToSet(rootNames, toPath);
for (const file of files) {
// Ignore file that is not emitted
if (!sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect)) continue;
if (!rootPaths.has(file.path)) {
if (isValidSourceFileForEmit(file) && !rootPaths.has(file.path)) {
addProgramDiagnosticAtRefPath(
file,
rootPaths,
@@ -3107,7 +3178,7 @@ namespace ts {
}
function verifyProjectReferences() {
const buildInfoPath = !options.noEmit && !options.suppressOutputPathCheck ? getOutputPathForBuildInfo(options) : undefined;
const buildInfoPath = !options.noEmit && !options.suppressOutputPathCheck ? getTsBuildInfoEmitOutputFilePath(options) : undefined;
forEachProjectReference(projectReferences, resolvedProjectReferences, (resolvedRef, index, parent) => {
const ref = (parent ? parent.commandLine.projectReferences : projectReferences)![index];
const parentFile = parent && parent.sourceFile as JsonSourceFile;
@@ -3134,7 +3205,7 @@ 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)) {
if (!parent && buildInfoPath && buildInfoPath === getTsBuildInfoEmitOutputFilePath(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);
}
+1
View File
@@ -66,6 +66,7 @@ namespace ts {
abstract: SyntaxKind.AbstractKeyword,
any: SyntaxKind.AnyKeyword,
as: SyntaxKind.AsKeyword,
asserts: SyntaxKind.AssertsKeyword,
bigint: SyntaxKind.BigIntKeyword,
boolean: SyntaxKind.BooleanKeyword,
break: SyntaxKind.BreakKeyword,
+30 -19
View File
@@ -522,6 +522,33 @@ namespace ts {
}
}
function recursiveCreateDirectory(directoryPath: string, sys: System) {
const basePath = getDirectoryPath(directoryPath);
const shouldCreateParent = basePath !== "" && directoryPath !== basePath && !sys.directoryExists(basePath);
if (shouldCreateParent) {
recursiveCreateDirectory(basePath, sys);
}
if (shouldCreateParent || !sys.directoryExists(directoryPath)) {
sys.createDirectory(directoryPath);
}
}
/**
* patch writefile to create folder before writing the file
*/
/*@internal*/
export function patchWriteFileEnsuringDirectory(sys: System) {
// patch writefile to create folder before writing the file
const originalWriteFile = sys.writeFile;
sys.writeFile = (path, data, writeBom) => {
const directoryPath = getDirectoryPath(normalizeSlashes(path));
if (directoryPath && !sys.directoryExists(directoryPath)) {
recursiveCreateDirectory(directoryPath, sys);
}
originalWriteFile.call(sys, path, data, writeBom);
};
}
/*@internal*/
export type BufferEncoding = "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex";
@@ -651,6 +678,8 @@ namespace ts {
base64decode?(input: string): string;
base64encode?(input: string): string;
/*@internal*/ bufferFrom?(input: string, encoding?: string): Buffer;
// For testing
/*@internal*/ now?(): Date;
}
export interface FileWatcher {
@@ -1365,17 +1394,6 @@ namespace ts {
};
}
function recursiveCreateDirectory(directoryPath: string, sys: System) {
const basePath = getDirectoryPath(directoryPath);
const shouldCreateParent = basePath !== "" && directoryPath !== basePath && !sys.directoryExists(basePath);
if (shouldCreateParent) {
recursiveCreateDirectory(basePath, sys);
}
if (shouldCreateParent || !sys.directoryExists(directoryPath)) {
sys.createDirectory(directoryPath);
}
}
let sys: System | undefined;
if (typeof ChakraHost !== "undefined") {
sys = getChakraSystem();
@@ -1387,14 +1405,7 @@ namespace ts {
}
if (sys) {
// patch writefile to create folder before writing the file
const originalWriteFile = sys.writeFile;
sys.writeFile = (path, data, writeBom) => {
const directoryPath = getDirectoryPath(normalizeSlashes(path));
if (directoryPath && !sys!.directoryExists(directoryPath)) {
recursiveCreateDirectory(directoryPath, sys!);
}
originalWriteFile.call(sys, path, data, writeBom);
};
patchWriteFileEnsuringDirectory(sys);
}
return sys!;
})();
+1 -1
View File
@@ -337,7 +337,7 @@ namespace ts {
if (constructor && constructor.body) {
let parameterPropertyDeclarationCount = 0;
for (let i = indexOfFirstStatement; i < constructor.body.statements.length; i++) {
if (isParameterPropertyDeclaration(getOriginalNode(constructor.body.statements[i]))) {
if (isParameterPropertyDeclaration(getOriginalNode(constructor.body.statements[i]), constructor)) {
parameterPropertyDeclarationCount++;
}
else {
@@ -135,7 +135,7 @@ namespace ts {
return getReturnTypeVisibilityError;
}
else if (isParameter(node)) {
if (isParameterPropertyDeclaration(node) && hasModifier(node.parent, ModifierFlags.Private)) {
if (isParameterPropertyDeclaration(node, node.parent) && hasModifier(node.parent, ModifierFlags.Private)) {
return getVariableDeclarationTypeVisibilityError;
}
return getParameterDeclarationTypeVisibilityError;
+1 -1
View File
@@ -2871,7 +2871,7 @@ namespace ts {
function tryEnterOrLeaveBlock(operationIndex: number): void {
if (blocks) {
for (; blockIndex < blockActions!.length && blockOffsets![blockIndex] <= operationIndex; blockIndex++) {
const block = blocks[blockIndex];
const block: CodeBlock = blocks[blockIndex];
const blockAction = blockActions![blockIndex];
switch (block.kind) {
case CodeBlockKind.Exception:
+2 -2
View File
@@ -895,7 +895,7 @@ namespace ts {
const members: ClassElement[] = [];
const constructor = getFirstConstructorWithBody(node);
const parametersWithPropertyAssignments = constructor &&
filter(constructor.parameters, isParameterPropertyDeclaration);
filter(constructor.parameters, p => isParameterPropertyDeclaration(p, constructor));
if (parametersWithPropertyAssignments) {
for (const parameter of parametersWithPropertyAssignments) {
@@ -1907,7 +1907,7 @@ namespace ts {
function transformConstructorBody(body: Block, constructor: ConstructorDeclaration) {
const parametersWithPropertyAssignments = constructor &&
filter(constructor.parameters, isParameterPropertyDeclaration);
filter(constructor.parameters, p => isParameterPropertyDeclaration(p, constructor));
if (!some(parametersWithPropertyAssignments)) {
return visitFunctionBody(body, visitor, context);
}
+7 -2
View File
@@ -308,6 +308,7 @@ namespace ts {
/*@internal*/ getUpToDateStatusOfProject(project: string): UpToDateStatus;
/*@internal*/ invalidateProject(configFilePath: ResolvedConfigFilePath, reloadLevel?: ConfigFileProgramReloadLevel): void;
/*@internal*/ buildNextInvalidatedProject(): void;
/*@internal*/ getAllParsedConfigs(): readonly ParsedCommandLine[];
}
/**
@@ -315,7 +316,7 @@ namespace ts {
*/
export function createBuilderStatusReporter(system: System, pretty?: boolean): DiagnosticReporter {
return diagnostic => {
let output = pretty ? `[${formatColorAndReset(new Date().toLocaleTimeString(), ForegroundColorEscapeSequences.Grey)}] ` : `${new Date().toLocaleTimeString()} - `;
let output = pretty ? `[${formatColorAndReset(getLocaleTimeString(system), ForegroundColorEscapeSequences.Grey)}] ` : `${getLocaleTimeString(system)} - `;
output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${system.newLine + system.newLine}`;
system.write(output);
};
@@ -1620,7 +1621,7 @@ namespace ts {
if (!state.buildInfoChecked.has(resolvedPath)) {
state.buildInfoChecked.set(resolvedPath, true);
const buildInfoPath = getOutputPathForBuildInfo(project.options);
const buildInfoPath = getTsBuildInfoEmitOutputFilePath(project.options);
if (buildInfoPath) {
const value = state.readFileWithCache(buildInfoPath);
const buildInfo = value && getBuildInfo(value);
@@ -2047,6 +2048,10 @@ namespace ts {
},
invalidateProject: (configFilePath, reloadLevel) => invalidateProject(state, configFilePath, reloadLevel || ConfigFileProgramReloadLevel.None),
buildNextInvalidatedProject: () => buildNextInvalidatedProject(state),
getAllParsedConfigs: () => arrayFrom(mapDefinedIterator(
state.configFileCache.values(),
config => isParsedCommandLine(config) ? config : undefined
)),
};
}
+97 -30
View File
@@ -32,6 +32,7 @@ namespace ts {
| SyntaxKind.AbstractKeyword
| SyntaxKind.AnyKeyword
| SyntaxKind.AsKeyword
| SyntaxKind.AssertsKeyword
| SyntaxKind.BigIntKeyword
| SyntaxKind.BooleanKeyword
| SyntaxKind.BreakKeyword
@@ -250,6 +251,7 @@ namespace ts {
// Contextual keywords
AbstractKeyword,
AsKeyword,
AssertsKeyword,
AnyKeyword,
AsyncKeyword,
AwaitKeyword,
@@ -361,8 +363,8 @@ namespace ts {
SemicolonClassElement,
// Element
Block,
VariableStatement,
EmptyStatement,
VariableStatement,
ExpressionStatement,
IfStatement,
DoStatement,
@@ -512,6 +514,8 @@ namespace ts {
LastTemplateToken = TemplateTail,
FirstBinaryOperator = LessThanToken,
LastBinaryOperator = CaretEqualsToken,
FirstStatement = VariableStatement,
LastStatement = DebuggerStatement,
FirstNode = QualifiedName,
FirstJSDocNode = JSDocTypeExpression,
LastJSDocNode = JSDocPropertyTag,
@@ -614,9 +618,13 @@ namespace ts {
/* @internal */
export const enum RelationComparisonResult {
Succeeded = 1, // Should be truthy
Failed = 2,
FailedAndReported = 3
Succeeded = 1 << 0, // Should be truthy
Failed = 1 << 1,
Reported = 1 << 2,
ReportsUnmeasurable = 1 << 3,
ReportsUnreliable = 1 << 4,
ReportsMask = ReportsUnmeasurable | ReportsUnreliable
}
export interface Node extends TextRange {
@@ -736,6 +744,7 @@ namespace ts {
export type AwaitKeywordToken = Token<SyntaxKind.AwaitKeyword>;
export type PlusToken = Token<SyntaxKind.PlusToken>;
export type MinusToken = Token<SyntaxKind.MinusToken>;
export type AssertsToken = Token<SyntaxKind.AssertsKeyword>;
export type Modifier
= Token<SyntaxKind.AbstractKeyword>
@@ -1037,6 +1046,7 @@ namespace ts {
questionToken?: QuestionToken;
exclamationToken?: ExclamationToken;
body?: Block | Expression;
/* @internal */ endFlowNode?: FlowNode;
}
export type FunctionLikeDeclaration =
@@ -1180,8 +1190,9 @@ namespace ts {
export interface TypePredicateNode extends TypeNode {
kind: SyntaxKind.TypePredicate;
parent: SignatureDeclaration | JSDocTypeExpression;
assertsModifier?: AssertsToken;
parameterName: Identifier | ThisTypeNode;
type: TypeNode;
type?: TypeNode;
}
export interface TypeQueryNode extends TypeNode {
@@ -2570,16 +2581,33 @@ namespace ts {
FalseCondition = 1 << 6, // Condition known to be false
SwitchClause = 1 << 7, // Switch statement clause
ArrayMutation = 1 << 8, // Potential array mutation
Referenced = 1 << 9, // Referenced as antecedent once
Shared = 1 << 10, // Referenced as antecedent more than once
PreFinally = 1 << 11, // Injected edge that links pre-finally label and pre-try flow
AfterFinally = 1 << 12, // Injected edge that links post-finally flow with the rest of the graph
Call = 1 << 9, // Potential assertion call
Referenced = 1 << 10, // Referenced as antecedent once
Shared = 1 << 11, // Referenced as antecedent more than once
PreFinally = 1 << 12, // Injected edge that links pre-finally label and pre-try flow
AfterFinally = 1 << 13, // Injected edge that links post-finally flow with the rest of the graph
/** @internal */
Cached = 1 << 13, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant
Cached = 1 << 14, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant
Label = BranchLabel | LoopLabel,
Condition = TrueCondition | FalseCondition
}
export type FlowNode =
| AfterFinallyFlow
| PreFinallyFlow
| FlowStart
| FlowLabel
| FlowAssignment
| FlowCall
| FlowCondition
| FlowSwitchClause
| FlowArrayMutation;
export interface FlowNodeBase {
flags: FlowFlags;
id?: number; // Node id used by flow type cache in checker
}
export interface FlowLock {
locked?: boolean;
}
@@ -2593,18 +2621,11 @@ namespace ts {
lock: FlowLock;
}
export type FlowNode =
| AfterFinallyFlow | PreFinallyFlow | FlowStart | FlowLabel | FlowAssignment | FlowCondition | FlowSwitchClause | FlowArrayMutation;
export interface FlowNodeBase {
flags: FlowFlags;
id?: number; // Node id used by flow type cache in checker
}
// FlowStart represents the start of a control flow. For a function expression or arrow
// function, the container property references the function (which in turn has a flowNode
// function, the node property references the function (which in turn has a flowNode
// property for the containing control flow).
export interface FlowStart extends FlowNodeBase {
container?: FunctionExpression | ArrowFunction | MethodDeclaration;
node?: FunctionExpression | ArrowFunction | MethodDeclaration;
}
// FlowLabel represents a junction with multiple possible preceding control flows.
@@ -2619,10 +2640,15 @@ namespace ts {
antecedent: FlowNode;
}
export interface FlowCall extends FlowNodeBase {
node: CallExpression;
antecedent: FlowNode;
}
// FlowCondition represents a condition that is known to be true or false at the
// node's location in the control flow.
export interface FlowCondition extends FlowNodeBase {
expression: Expression;
node: Expression;
antecedent: FlowNode;
}
@@ -2981,6 +3007,8 @@ namespace ts {
* will be invoked when writing the JavaScript and declaration files.
*/
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult;
/*@internal*/
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitResult; // eslint-disable-line @typescript-eslint/unified-signatures
getOptionsDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[];
getGlobalDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[];
@@ -3005,11 +3033,11 @@ namespace ts {
/* @internal */ getClassifiableNames(): UnderscoreEscapedMap<true>;
/* @internal */ getNodeCount(): number;
/* @internal */ getIdentifierCount(): number;
/* @internal */ getSymbolCount(): number;
/* @internal */ getTypeCount(): number;
/* @internal */ getRelationCacheSizes(): { assignable: number, identity: number, subtype: number };
getNodeCount(): number;
getIdentifierCount(): number;
getSymbolCount(): number;
getTypeCount(): number;
getRelationCacheSizes(): { assignable: number, identity: number, subtype: number };
/* @internal */ getFileProcessingDiagnostics(): DiagnosticCollection;
/* @internal */ getResolvedTypeReferenceDirectives(): Map<ResolvedTypeReferenceDirective | undefined>;
@@ -3037,6 +3065,7 @@ 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*/ isSourceOfProjectReferenceRedirect(fileName: string): boolean;
/*@internal*/ getProgramBuildInfo?(): ProgramBuildInfo | undefined;
/*@internal*/ emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult;
}
@@ -3137,6 +3166,7 @@ namespace ts {
getSourceFile(fileName: string): SourceFile | undefined;
getResolvedTypeReferenceDirectives(): ReadonlyMap<ResolvedTypeReferenceDirective | undefined>;
getProjectReferenceRedirect(fileName: string): string | undefined;
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
readonly redirectTargetsMap: RedirectTargetsMap;
}
@@ -3287,6 +3317,7 @@ namespace ts {
/* @internal */ getElementTypeOfArrayType(arrayType: Type): Type | undefined;
/* @internal */ createPromiseType(type: Type): Type;
/* @internal */ isTypeAssignableTo(source: Type, target: Type): boolean;
/* @internal */ createAnonymousType(symbol: Symbol, members: SymbolTable, callSignatures: Signature[], constructSignatures: Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): Type;
/* @internal */ createSignature(
declaration: SignatureDeclaration,
@@ -3525,25 +3556,45 @@ namespace ts {
export const enum TypePredicateKind {
This,
Identifier
Identifier,
AssertsThis,
AssertsIdentifier
}
export interface TypePredicateBase {
kind: TypePredicateKind;
type: Type;
type: Type | undefined;
}
export interface ThisTypePredicate extends TypePredicateBase {
kind: TypePredicateKind.This;
parameterName: undefined;
parameterIndex: undefined;
type: Type;
}
export interface IdentifierTypePredicate extends TypePredicateBase {
kind: TypePredicateKind.Identifier;
parameterName: string;
parameterIndex: number;
type: Type;
}
export type TypePredicate = IdentifierTypePredicate | ThisTypePredicate;
export interface AssertsThisTypePredicate extends TypePredicateBase {
kind: TypePredicateKind.AssertsThis;
parameterName: undefined;
parameterIndex: undefined;
type: Type | undefined;
}
export interface AssertsIdentifierTypePredicate extends TypePredicateBase {
kind: TypePredicateKind.AssertsIdentifier;
parameterName: string;
parameterIndex: number;
type: Type | undefined;
}
export type TypePredicate = ThisTypePredicate | IdentifierTypePredicate | AssertsThisTypePredicate | AssertsIdentifierTypePredicate;
/* @internal */
export type AnyImportSyntax = ImportDeclaration | ImportEqualsDeclaration;
@@ -3961,7 +4012,7 @@ namespace ts {
resolvedSignature?: Signature; // Cached signature of signature node or call expression
resolvedSymbol?: Symbol; // Cached name resolution result
resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result
maybeTypePredicate?: boolean; // Cached check whether call expression might reference a type predicate
effectsSignature?: Signature; // Signature with possible control flow effects
enumMemberValue?: string | number; // Constant value of enum member
isVisible?: boolean; // Is this node visible
containsArgumentsReference?: boolean; // Whether a function-like declaration contains an 'arguments' reference
@@ -3976,6 +4027,7 @@ namespace ts {
contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive
deferredNodes?: Map<Node>; // Set of nodes whose checking has been deferred
capturedBlockScopeBindings?: Symbol[]; // Block-scoped bindings captured beneath this part of an IterationStatement
isExhaustive?: boolean; // Is node an exhaustive switch statement
}
export const enum TypeFlags {
@@ -4200,7 +4252,7 @@ namespace ts {
}
// Object type or intersection of object types
export type BaseType = ObjectType | IntersectionType;
export type BaseType = ObjectType | IntersectionType | TypeVariable; // Also `any` and `object`
export interface InterfaceTypeWithDeclaredMembers extends InterfaceType {
declaredProperties: Symbol[]; // Declared members
@@ -4629,6 +4681,8 @@ namespace ts {
code: number;
message: string;
reportsUnnecessary?: {};
/* @internal */
elidedInCompatabilityPyramid?: boolean;
}
/**
@@ -4723,6 +4777,7 @@ namespace ts {
/* @internal */ diagnostics?: boolean;
/* @internal */ extendedDiagnostics?: boolean;
disableSizeLimit?: boolean;
disableSourceOfProjectReferenceRedirect?: boolean;
downlevelIteration?: boolean;
emitBOM?: boolean;
emitDecoratorMetadata?: boolean;
@@ -5251,11 +5306,23 @@ namespace ts {
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
createHash?(data: string): string;
getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined;
/* @internal */ setResolvedProjectReferenceCallbacks?(callbacks: ResolvedProjectReferenceCallbacks): void;
/* @internal */ useSourceOfProjectReferenceRedirect?(): boolean;
// TODO: later handle this in better way in builder host instead once the api for tsbuild finalizes and doesn't use compilerHost as base
/*@internal*/createDirectory?(directory: string): void;
}
/** true if --out otherwise source file name */
/*@internal*/
export type SourceOfProjectReferenceRedirect = string | true;
/*@internal*/
export interface ResolvedProjectReferenceCallbacks {
getSourceOfProjectReferenceRedirect(fileName: string): SourceOfProjectReferenceRedirect | undefined;
forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined;
}
/* @internal */
export const enum TransformFlags {
None = 0,
+12 -5
View File
@@ -975,6 +975,8 @@ namespace ts {
return getSpanOfTokenAtPosition(sourceFile, node.pos);
}
Debug.assert(!isJSDoc(errorNode));
const isMissing = nodeIsMissing(errorNode);
const pos = isMissing || isJsxText(node)
? errorNode.pos
@@ -1007,7 +1009,7 @@ namespace ts {
}
export function isDeclarationReadonly(declaration: Declaration): boolean {
return !!(getCombinedModifierFlags(declaration) & ModifierFlags.Readonly && !isParameterPropertyDeclaration(declaration));
return !!(getCombinedModifierFlags(declaration) & ModifierFlags.Readonly && !isParameterPropertyDeclaration(declaration, declaration.parent));
}
export function isVarConst(node: VariableDeclaration | VariableDeclarationList): boolean {
@@ -4981,8 +4983,8 @@ namespace ts {
}
export type ParameterPropertyDeclaration = ParameterDeclaration & { parent: ConstructorDeclaration, name: Identifier };
export function isParameterPropertyDeclaration(node: Node): node is ParameterPropertyDeclaration {
return hasModifier(node, ModifierFlags.ParameterPropertyModifier) && node.parent.kind === SyntaxKind.Constructor;
export function isParameterPropertyDeclaration(node: Node, parent: Node): node is ParameterPropertyDeclaration {
return hasModifier(node, ModifierFlags.ParameterPropertyModifier) && parent.kind === SyntaxKind.Constructor;
}
export function isEmptyBindingPattern(node: BindingName): node is BindingPattern {
@@ -8711,11 +8713,16 @@ namespace ts {
return { pos: typeParameters.pos - 1, end: typeParameters.end + 1 };
}
export function skipTypeChecking(sourceFile: SourceFile, options: CompilerOptions) {
export interface HostWithIsSourceOfProjectReferenceRedirect {
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
}
export function skipTypeChecking(sourceFile: SourceFile, options: CompilerOptions, host: HostWithIsSourceOfProjectReferenceRedirect) {
// If skipLibCheck is enabled, skip reporting errors if file is a declaration file.
// If skipDefaultLibCheck is enabled, skip reporting errors if file contains a
// '/// <reference no-default-lib="true"/>' directive.
return options.skipLibCheck && sourceFile.isDeclarationFile || options.skipDefaultLibCheck && sourceFile.hasNoDefaultLib;
return (options.skipLibCheck && sourceFile.isDeclarationFile ||
options.skipDefaultLibCheck && sourceFile.hasNoDefaultLib) ||
host.isSourceOfProjectReferenceRedirect(sourceFile.fileName);
}
export function isJsonEqual(a: unknown, b: unknown): boolean {
+2 -1
View File
@@ -333,7 +333,8 @@ namespace ts {
// Types
case SyntaxKind.TypePredicate:
return updateTypePredicateNode(<TypePredicateNode>node,
return updateTypePredicateNodeWithModifier(<TypePredicateNode>node,
visitNode((<TypePredicateNode>node).assertsModifier, visitor),
visitNode((<TypePredicateNode>node).parameterName, visitor),
visitNode((<TypePredicateNode>node).type, visitor, isTypeNode));
+12 -3
View File
@@ -54,6 +54,15 @@ namespace ts {
: newLine;
}
/**
* Get locale specific time based on whether we are in test mode
*/
export function getLocaleTimeString(system: System) {
return !system.now ?
new Date().toLocaleTimeString() :
system.now().toLocaleTimeString("en-US", { timeZone: "UTC" });
}
/**
* Create a function that reports watch status by writing to the system and handles the formating of the diagnostic
*/
@@ -61,7 +70,7 @@ namespace ts {
return pretty ?
(diagnostic, newLine, options) => {
clearScreenIfNotWatchingForFileChanges(system, diagnostic, options);
let output = `[${formatColorAndReset(new Date().toLocaleTimeString(), ForegroundColorEscapeSequences.Grey)}] `;
let output = `[${formatColorAndReset(getLocaleTimeString(system), ForegroundColorEscapeSequences.Grey)}] `;
output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${newLine + newLine}`;
system.write(output);
} :
@@ -72,7 +81,7 @@ namespace ts {
output += newLine;
}
output += `${new Date().toLocaleTimeString()} - `;
output += `${getLocaleTimeString(system)} - `;
output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${getPlainDiagnosticFollowingNewLines(diagnostic, newLine)}`;
system.write(output);
@@ -447,7 +456,7 @@ namespace ts {
}
export function readBuilderProgram(compilerOptions: CompilerOptions, host: ReadBuildProgramHost) {
if (compilerOptions.out || compilerOptions.outFile) return undefined;
const buildInfoPath = getOutputPathForBuildInfo(compilerOptions);
const buildInfoPath = getTsBuildInfoEmitOutputFilePath(compilerOptions);
if (!buildInfoPath) return undefined;
const content = host.readFile(buildInfoPath);
if (!content) return undefined;
+37 -21
View File
@@ -140,7 +140,7 @@ namespace fakes {
}
public createHash(data: string): string {
return data;
return `${ts.generateDjb2Hash(data)}-${data}`;
}
public realpath(path: string) {
@@ -164,6 +164,10 @@ namespace fakes {
return undefined;
}
}
now() {
return new Date(this.vfs.time());
}
}
/**
@@ -520,39 +524,51 @@ ${indentText}${text}`;
export const version = "FakeTSVersion";
export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost<ts.BuilderProgram> {
createProgram: ts.CreateProgram<ts.BuilderProgram>;
constructor(sys: System | vfs.FileSystem, options?: ts.CompilerOptions, setParentNodes?: boolean, createProgram?: ts.CreateProgram<ts.BuilderProgram>) {
super(sys, options, setParentNodes);
this.createProgram = createProgram || ts.createEmitAndSemanticDiagnosticsBuilderProgram;
}
readFile(path: string) {
const value = super.readFile(path);
export function patchSolutionBuilderHost(host: ts.SolutionBuilderHost<ts.BuilderProgram>, sys: System) {
const originalReadFile = host.readFile;
host.readFile = (path, encoding) => {
const value = originalReadFile.call(host, path, encoding);
if (!value || !ts.isBuildInfoFile(path)) return value;
const buildInfo = ts.getBuildInfo(value);
ts.Debug.assert(buildInfo.version === version);
buildInfo.version = ts.version;
return ts.getBuildInfoText(buildInfo);
};
if (host.writeFile) {
const originalWriteFile = host.writeFile;
host.writeFile = (fileName, content, writeByteOrderMark) => {
if (!ts.isBuildInfoFile(fileName)) return originalWriteFile.call(host, fileName, content, writeByteOrderMark);
const buildInfo = ts.getBuildInfo(content);
sanitizeBuildInfoProgram(buildInfo);
buildInfo.version = version;
originalWriteFile.call(host, fileName, ts.getBuildInfoText(buildInfo), writeByteOrderMark);
};
}
public writeFile(fileName: string, content: string, writeByteOrderMark: boolean) {
if (!ts.isBuildInfoFile(fileName)) return super.writeFile(fileName, content, writeByteOrderMark);
const buildInfo = ts.getBuildInfo(content);
sanitizeBuildInfoProgram(buildInfo);
buildInfo.version = version;
super.writeFile(fileName, ts.getBuildInfoText(buildInfo), writeByteOrderMark);
ts.Debug.assert(host.now === undefined);
host.now = () => new Date(sys.vfs.time());
ts.Debug.assertDefined(host.createHash);
}
export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost<ts.BuilderProgram> {
createProgram: ts.CreateProgram<ts.BuilderProgram>;
private constructor(sys: System | vfs.FileSystem, options?: ts.CompilerOptions, setParentNodes?: boolean, createProgram?: ts.CreateProgram<ts.BuilderProgram>) {
super(sys, options, setParentNodes);
this.createProgram = createProgram || ts.createEmitAndSemanticDiagnosticsBuilderProgram;
}
static create(sys: System | vfs.FileSystem, options?: ts.CompilerOptions, setParentNodes?: boolean, createProgram?: ts.CreateProgram<ts.BuilderProgram>) {
const host = new SolutionBuilderHost(sys, options, setParentNodes, createProgram);
patchSolutionBuilderHost(host, host.sys);
return host;
}
createHash(data: string) {
return `${ts.generateDjb2Hash(data)}-${data}`;
}
now() {
return new Date(this.sys.vfs.time());
}
diagnostics: SolutionBuilderDiagnostic[] = [];
reportDiagnostic(diagnostic: ts.Diagnostic) {
+35 -14
View File
@@ -32,6 +32,10 @@ namespace vfs {
let devCount = 0; // A monotonically increasing count of device ids
let inoCount = 0; // A monotonically increasing count of inodes
export interface DiffOptions {
includeChangedFileWithSameContent?: boolean;
}
/**
* Represents a virtual POSIX-like file system.
*/
@@ -693,21 +697,25 @@ namespace vfs {
* Generates a `FileSet` patch containing all the entries in this `FileSystem` that are not in `base`.
* @param base The base file system. If not provided, this file system's `shadowRoot` is used (if present).
*/
public diff(base = this.shadowRoot) {
public diff(base = this.shadowRoot, options: DiffOptions = {}) {
const differences: FileSet = {};
const hasDifferences = base ? FileSystem.rootDiff(differences, this, base) : FileSystem.trackCreatedInodes(differences, this, this._getRootLinks());
const hasDifferences = base ?
FileSystem.rootDiff(differences, this, base, options) :
FileSystem.trackCreatedInodes(differences, this, this._getRootLinks());
return hasDifferences ? differences : undefined;
}
/**
* Generates a `FileSet` patch containing all the entries in `chagned` that are not in `base`.
* Generates a `FileSet` patch containing all the entries in `changed` that are not in `base`.
*/
public static diff(changed: FileSystem, base: FileSystem) {
public static diff(changed: FileSystem, base: FileSystem, options: DiffOptions = {}) {
const differences: FileSet = {};
return FileSystem.rootDiff(differences, changed, base) ? differences : undefined;
return FileSystem.rootDiff(differences, changed, base, options) ?
differences :
undefined;
}
private static diffWorker(container: FileSet, changed: FileSystem, changedLinks: ReadonlyMap<string, Inode> | undefined, base: FileSystem, baseLinks: ReadonlyMap<string, Inode> | undefined) {
private static diffWorker(container: FileSet, changed: FileSystem, changedLinks: ReadonlyMap<string, Inode> | undefined, base: FileSystem, baseLinks: ReadonlyMap<string, Inode> | undefined, options: DiffOptions) {
if (changedLinks && !baseLinks) return FileSystem.trackCreatedInodes(container, changed, changedLinks);
if (baseLinks && !changedLinks) return FileSystem.trackDeletedInodes(container, baseLinks);
if (changedLinks && baseLinks) {
@@ -724,10 +732,10 @@ namespace vfs {
const baseNode = baseLinks.get(basename);
if (baseNode) {
if (isDirectory(changedNode) && isDirectory(baseNode)) {
return hasChanges = FileSystem.directoryDiff(container, basename, changed, changedNode, base, baseNode) || hasChanges;
return hasChanges = FileSystem.directoryDiff(container, basename, changed, changedNode, base, baseNode, options) || hasChanges;
}
if (isFile(changedNode) && isFile(baseNode)) {
return hasChanges = FileSystem.fileDiff(container, basename, changed, changedNode, base, baseNode) || hasChanges;
return hasChanges = FileSystem.fileDiff(container, basename, changed, changedNode, base, baseNode, options) || hasChanges;
}
if (isSymlink(changedNode) && isSymlink(baseNode)) {
return hasChanges = FileSystem.symlinkDiff(container, basename, changedNode, baseNode) || hasChanges;
@@ -740,7 +748,7 @@ namespace vfs {
return false;
}
private static rootDiff(container: FileSet, changed: FileSystem, base: FileSystem) {
private static rootDiff(container: FileSet, changed: FileSystem, base: FileSystem, options: DiffOptions) {
while (!changed._lazy.links && changed._shadowRoot) changed = changed._shadowRoot;
while (!base._lazy.links && base._shadowRoot) base = base._shadowRoot;
@@ -750,10 +758,10 @@ namespace vfs {
// no difference if the root links are empty and unshadowed
if (!changed._lazy.links && !changed._shadowRoot && !base._lazy.links && !base._shadowRoot) return false;
return FileSystem.diffWorker(container, changed, changed._getRootLinks(), base, base._getRootLinks());
return FileSystem.diffWorker(container, changed, changed._getRootLinks(), base, base._getRootLinks(), options);
}
private static directoryDiff(container: FileSet, basename: string, changed: FileSystem, changedNode: DirectoryInode, base: FileSystem, baseNode: DirectoryInode) {
private static directoryDiff(container: FileSet, basename: string, changed: FileSystem, changedNode: DirectoryInode, base: FileSystem, baseNode: DirectoryInode, options: DiffOptions) {
while (!changedNode.links && changedNode.shadowRoot) changedNode = changedNode.shadowRoot;
while (!baseNode.links && baseNode.shadowRoot) baseNode = baseNode.shadowRoot;
@@ -770,7 +778,7 @@ namespace vfs {
// no difference if both nodes have identical children
const children: FileSet = {};
if (!FileSystem.diffWorker(children, changed, changed._getLinks(changedNode), base, base._getLinks(baseNode))) {
if (!FileSystem.diffWorker(children, changed, changed._getLinks(changedNode), base, base._getLinks(baseNode), options)) {
return false;
}
@@ -778,7 +786,7 @@ namespace vfs {
return true;
}
private static fileDiff(container: FileSet, basename: string, changed: FileSystem, changedNode: FileInode, base: FileSystem, baseNode: FileInode) {
private static fileDiff(container: FileSet, basename: string, changed: FileSystem, changedNode: FileInode, base: FileSystem, baseNode: FileInode, options: DiffOptions) {
while (!changedNode.buffer && changedNode.shadowRoot) changedNode = changedNode.shadowRoot;
while (!baseNode.buffer && baseNode.shadowRoot) baseNode = baseNode.shadowRoot;
@@ -800,7 +808,11 @@ namespace vfs {
if (changedBuffer === baseBuffer) return false;
// no difference if both buffers are identical
if (Buffer.compare(changedBuffer, baseBuffer) === 0) return false;
if (Buffer.compare(changedBuffer, baseBuffer) === 0) {
if (!options.includeChangedFileWithSameContent) return false;
container[basename] = new SameFileContentFile(changedBuffer);
return true;
}
container[basename] = new File(changedBuffer);
return true;
@@ -1361,6 +1373,12 @@ namespace vfs {
}
}
export class SameFileContentFile extends File {
constructor(data: Buffer | string, metaAndEncoding?: { encoding?: string, meta?: Record<string, any> }) {
super(data, metaAndEncoding);
}
}
/** Extended options for a hard link in a `FileSet` */
export class Link {
public readonly path: string;
@@ -1549,6 +1567,9 @@ namespace vfs {
else if (entry instanceof Directory) {
text += formatPatchWorker(file, entry.files);
}
else if (entry instanceof SameFileContentFile) {
text += `//// [${file}] file written with same contents\r\n`;
}
else if (entry instanceof File) {
const content = typeof entry.data === "string" ? entry.data : entry.data.toString("utf8");
text += `//// [${file}]\r\n${content}\r\n\r\n`;
+19 -3
View File
@@ -44,7 +44,10 @@ interface Array<T> { length: number; [n: number]: T; }`
}
export function createServerHost(fileOrFolderList: readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost {
return new TestServerHost(/*withSafelist*/ true, fileOrFolderList, params);
const host = new TestServerHost(/*withSafelist*/ true, fileOrFolderList, params);
// Just like sys, patch the host to use writeFile
patchWriteFileEnsuringDirectory(host);
return host;
}
export interface File {
@@ -174,8 +177,8 @@ interface Array<T> { length: number; [n: number]: T; }`
}
}
export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[]) {
checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles);
export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[], additionalInfo?: string) {
checkMapKeys(`watchedFiles:: ${additionalInfo || ""}::`, host.watchedFiles, expectedFiles);
}
export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: ReadonlyMap<number>): void;
@@ -1016,6 +1019,19 @@ interface Array<T> { length: number; [n: number]: T; }`
}
}
export type TestServerHostTrackingWrittenFiles = TestServerHost & { writtenFiles: Map<true>; };
export function changeToHostTrackingWrittenFiles(inputHost: TestServerHost) {
const host = inputHost as TestServerHostTrackingWrittenFiles;
const originalWriteFile = host.writeFile;
host.writtenFiles = createMap<true>();
host.writeFile = (fileName, content) => {
originalWriteFile.call(host, fileName, content);
const path = host.toFullPath(fileName);
host.writtenFiles.set(path, true);
};
return host;
}
export const tsbuildProjectsLocation = "/user/username/projects";
export function getTsBuildProjectFilePath(project: string, file: string) {
return `${tsbuildProjectsLocation}/${project}/${file}`;
+11 -11
View File
@@ -18,7 +18,7 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;
all<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
@@ -26,7 +26,7 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6, T7, T8, T9>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>;
all<T1, T2, T3, T4, T5, T6, T7, T8, T9>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
@@ -34,7 +34,7 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6, T7, T8>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>;
all<T1, T2, T3, T4, T5, T6, T7, T8>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
@@ -42,7 +42,7 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6, T7>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>]): Promise<[T1, T2, T3, T4, T5, T6, T7]>;
all<T1, T2, T3, T4, T5, T6, T7>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>]): Promise<[T1, T2, T3, T4, T5, T6, T7]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
@@ -50,7 +50,7 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>]): Promise<[T1, T2, T3, T4, T5, T6]>;
all<T1, T2, T3, T4, T5, T6>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>]): Promise<[T1, T2, T3, T4, T5, T6]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
@@ -58,7 +58,7 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>]): Promise<[T1, T2, T3, T4, T5]>;
all<T1, T2, T3, T4, T5>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>]): Promise<[T1, T2, T3, T4, T5]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
@@ -66,7 +66,7 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>]): Promise<[T1, T2, T3, T4]>;
all<T1, T2, T3, T4>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>]): Promise<[T1, T2, T3, T4]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
@@ -74,7 +74,7 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): Promise<[T1, T2, T3]>;
all<T1, T2, T3>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): Promise<[T1, T2, T3]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
@@ -82,7 +82,7 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): Promise<[T1, T2]>;
all<T1, T2>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): Promise<[T1, T2]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
@@ -90,7 +90,7 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T>(values: (T | PromiseLike<T>)[]): Promise<T[]>;
all<T>(values: readonly (T | PromiseLike<T>)[]): Promise<T[]>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
@@ -98,7 +98,7 @@ interface PromiseConstructor {
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T>(values: T[]): Promise<T extends PromiseLike<infer U> ? U : T>;
race<T>(values: readonly T[]): Promise<T extends PromiseLike<infer U> ? U : T>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
+1 -1
View File
@@ -2,7 +2,7 @@
interface AsyncGenerator<T = unknown, TReturn = any, TNext = unknown> extends AsyncIterator<T, TReturn, TNext> {
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
next(...args: [] | [TNext | PromiseLike<TNext>]): Promise<IteratorResult<T, TReturn>>;
next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn>>;
return(value: TReturn | PromiseLike<TReturn>): Promise<IteratorResult<T, TReturn>>;
throw(e: any): Promise<IteratorResult<T, TReturn>>;
[Symbol.asyncIterator](): AsyncGenerator<T, TReturn, TNext>;
+1 -1
View File
@@ -11,7 +11,7 @@ interface SymbolConstructor {
interface AsyncIterator<T, TReturn = any, TNext = undefined> {
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
next(...args: [] | [TNext | PromiseLike<TNext>]): Promise<IteratorResult<T, TReturn>>;
next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn>>;
return?(value?: TReturn | PromiseLike<TReturn>): Promise<IteratorResult<T, TReturn>>;
throw?(e?: any): Promise<IteratorResult<T, TReturn>>;
}
+13 -4
View File
@@ -1777,6 +1777,12 @@ namespace ts.server {
configFileErrors.push(...parsedCommandLine.errors);
}
this.logger.info(`Config: ${configFilename} : ${JSON.stringify({
rootNames: parsedCommandLine.fileNames,
options: parsedCommandLine.options,
projectReferences: parsedCommandLine.projectReferences
}, /*replacer*/ undefined, " ")}`);
Debug.assert(!!parsedCommandLine.fileNames);
const compilerOptions = parsedCommandLine.options;
@@ -1818,7 +1824,7 @@ namespace ts.server {
let scriptInfo: ScriptInfo | NormalizedPath;
let path: Path;
// Use the project's fileExists so that it can use caching instead of reaching to disk for the query
if (!isDynamic && !project.fileExists(newRootFile)) {
if (!isDynamic && !project.fileExistsWithCache(newRootFile)) {
path = normalizedPathToPath(normalizedPath, this.currentDirectory, this.toCanonicalFileName);
const existingValue = projectRootFilesMap.get(path)!;
if (isScriptInfo(existingValue)) {
@@ -1851,7 +1857,7 @@ namespace ts.server {
projectRootFilesMap.forEach((value, path) => {
if (!newRootScriptInfoMap.has(path)) {
if (isScriptInfo(value)) {
project.removeFile(value, project.fileExists(path), /*detachFromProject*/ true);
project.removeFile(value, project.fileExistsWithCache(path), /*detachFromProject*/ true);
}
else {
projectRootFilesMap.delete(path);
@@ -2584,7 +2590,9 @@ namespace ts.server {
/*@internal*/
getOriginalLocationEnsuringConfiguredProject(project: Project, location: DocumentPosition): DocumentPosition | undefined {
const originalLocation = project.getSourceMapper().tryGetSourcePosition(location);
const originalLocation = project.isSourceOfProjectReferenceRedirect(location.fileName) ?
location :
project.getSourceMapper().tryGetSourcePosition(location);
if (!originalLocation) return undefined;
const { fileName } = originalLocation;
@@ -2595,7 +2603,8 @@ namespace ts.server {
if (!configFileName) return undefined;
const configuredProject = this.findConfiguredProjectByProjectName(configFileName) ||
this.createAndLoadConfiguredProject(configFileName, `Creating project for original file: ${originalFileInfo.fileName} for location: ${location.fileName}`);
this.createAndLoadConfiguredProject(configFileName, `Creating project for original file: ${originalFileInfo.fileName}${location !== originalLocation ? " for location: " + location.fileName : ""}`);
if (configuredProject === project) return originalLocation;
updateProjectIfDirty(configuredProject);
// Keep this configured project as referenced from project
addOriginalConfiguredProject(configuredProject);
+90 -3
View File
@@ -196,6 +196,11 @@ namespace ts.server {
/*@internal*/
originalConfiguredProjects: Map<true> | undefined;
/*@internal*/
getResolvedProjectReferenceToRedirect(_fileName: string): ResolvedProjectReference | undefined {
return undefined;
}
private readonly cancellationToken: ThrottledCancellationToken;
public isNonTsProject() {
@@ -391,6 +396,11 @@ namespace ts.server {
}
fileExists(file: string): boolean {
return this.fileExistsWithCache(file);
}
/* @internal */
fileExistsWithCache(file: string): boolean {
// As an optimization, don't hit the disks for files we already know don't exist
// (because we're watching for their creation).
const path = this.toPath(file);
@@ -527,8 +537,11 @@ namespace ts.server {
return this.projectService.getSourceFileLike(fileName, this);
}
private shouldEmitFile(scriptInfo: ScriptInfo) {
return scriptInfo && !scriptInfo.isDynamicOrHasMixedContent();
/*@internal*/
shouldEmitFile(scriptInfo: ScriptInfo | undefined) {
return scriptInfo &&
!scriptInfo.isDynamicOrHasMixedContent() &&
!this.program!.isSourceOfProjectReferenceRedirect(scriptInfo.path);
}
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] {
@@ -538,7 +551,7 @@ namespace ts.server {
updateProjectIfDirty(this);
this.builderState = BuilderState.create(this.program!, this.projectService.toCanonicalFileName, this.builderState);
return mapDefined(BuilderState.getFilesAffectedBy(this.builderState, this.program!, scriptInfo.path, this.cancellationToken, data => this.projectService.host.createHash!(data)), // TODO: GH#18217
sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)!) ? sourceFile.fileName : undefined);
sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined);
}
/**
@@ -1223,6 +1236,11 @@ namespace ts.server {
this.rootFilesMap.delete(info.path);
}
/*@internal*/
isSourceOfProjectReferenceRedirect(fileName: string) {
return !!this.program && this.program.isSourceOfProjectReferenceRedirect(fileName);
}
protected enableGlobalPlugins(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined) {
const host = this.projectService.host;
@@ -1475,6 +1493,8 @@ namespace ts.server {
configFileWatcher: FileWatcher | undefined;
private directoriesWatchedForWildcards: Map<WildcardDirectoryWatcher> | undefined;
readonly canonicalConfigFilePath: NormalizedPath;
private projectReferenceCallbacks: ResolvedProjectReferenceCallbacks | undefined;
private mapOfDeclarationDirectories: Map<true> | undefined;
/* @internal */
pendingReload: ConfigFileProgramReloadLevel | undefined;
@@ -1520,6 +1540,63 @@ namespace ts.server {
this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName));
}
/* @internal */
setResolvedProjectReferenceCallbacks(projectReferenceCallbacks: ResolvedProjectReferenceCallbacks) {
this.projectReferenceCallbacks = projectReferenceCallbacks;
}
/* @internal */
useSourceOfProjectReferenceRedirect = () => !!this.languageServiceEnabled &&
!this.getCompilerOptions().disableSourceOfProjectReferenceRedirect;
/**
* This implementation of fileExists checks if the file being requested is
* .d.ts file for the referenced Project.
* If it is it returns true irrespective of whether that file exists on host
*/
fileExists(file: string): boolean {
// Project references go to source file instead of .d.ts file
if (this.useSourceOfProjectReferenceRedirect() && this.projectReferenceCallbacks) {
const source = this.projectReferenceCallbacks.getSourceOfProjectReferenceRedirect(file);
if (source) return isString(source) ? super.fileExists(source) : true;
}
return super.fileExists(file);
}
/**
* This implementation of directoryExists checks if the directory being requested is
* directory of .d.ts file for the referenced Project.
* If it is it returns true irrespective of whether that directory exists on host
*/
directoryExists(path: string): boolean {
if (super.directoryExists(path)) return true;
if (!this.useSourceOfProjectReferenceRedirect() || !this.projectReferenceCallbacks) return false;
if (!this.mapOfDeclarationDirectories) {
this.mapOfDeclarationDirectories = createMap();
this.projectReferenceCallbacks.forEachResolvedProjectReference(ref => {
if (!ref) return;
const out = ref.commandLine.options.outFile || ref.commandLine.options.outDir;
if (out) {
this.mapOfDeclarationDirectories!.set(getDirectoryPath(this.toPath(out)), true);
}
else {
// Set declaration's in different locations only, if they are next to source the directory present doesnt change
const declarationDir = ref.commandLine.options.declarationDir || ref.commandLine.options.outDir;
if (declarationDir) {
this.mapOfDeclarationDirectories!.set(this.toPath(declarationDir), true);
}
}
});
}
const dirPath = this.toPath(path);
const dirPathWithTrailingDirectorySeparator = `${dirPath}${directorySeparator}`;
return !!forEachKey(
this.mapOfDeclarationDirectories,
declDirPath => dirPath === declDirPath || startsWith(declDirPath, dirPathWithTrailingDirectorySeparator)
);
}
/**
* If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph
* @returns: true if set of files in the project stays the same and false - otherwise.
@@ -1528,6 +1605,8 @@ namespace ts.server {
this.isInitialLoadPending = returnFalse;
const reloadLevel = this.pendingReload;
this.pendingReload = ConfigFileProgramReloadLevel.None;
this.projectReferenceCallbacks = undefined;
this.mapOfDeclarationDirectories = undefined;
let result: boolean;
switch (reloadLevel) {
case ConfigFileProgramReloadLevel.Partial:
@@ -1570,6 +1649,12 @@ namespace ts.server {
return program && program.forEachResolvedProjectReference(cb);
}
/*@internal*/
getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined {
const program = this.getCurrentProgram();
return program && program.getResolvedProjectReferenceToRedirect(fileName);
}
/*@internal*/
enablePluginsWithOptions(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined) {
const host = this.projectService.host;
@@ -1652,6 +1737,8 @@ namespace ts.server {
this.stopWatchingWildCards();
this.projectErrors = undefined;
this.configFileSpecs = undefined;
this.projectReferenceCallbacks = undefined;
this.mapOfDeclarationDirectories = undefined;
super.close();
}
+4 -2
View File
@@ -495,15 +495,17 @@ namespace ts.server {
// the default project; if no configured projects, the first external project should
// be the default project; otherwise the first inferred project should be the default.
let firstExternalProject;
let firstConfiguredProject;
for (const project of this.containingProjects) {
if (project.projectKind === ProjectKind.Configured) {
return project;
if (!project.isSourceOfProjectReferenceRedirect(this.fileName)) return project;
if (!firstConfiguredProject) firstConfiguredProject = project;
}
else if (project.projectKind === ProjectKind.External && !firstExternalProject) {
firstExternalProject = project;
}
}
return firstExternalProject || this.containingProjects[0];
return firstConfiguredProject || firstExternalProject || this.containingProjects[0];
}
}
+9 -5
View File
@@ -448,7 +448,9 @@ namespace ts.server {
function getDefinitionInProject(definition: DocumentPosition | undefined, definingProject: Project, project: Project): DocumentPosition | undefined {
if (!definition || project.containsFile(toNormalizedPath(definition.fileName))) return definition;
const mappedDefinition = definingProject.getLanguageService().getSourceMapper().tryGetGeneratedPosition(definition);
const mappedDefinition = definingProject.isSourceOfProjectReferenceRedirect(definition.fileName) ?
definition :
definingProject.getLanguageService().getSourceMapper().tryGetGeneratedPosition(definition);
return mappedDefinition && project.containsFile(toNormalizedPath(mappedDefinition.fileName)) ? mappedDefinition : undefined;
}
@@ -477,7 +479,7 @@ namespace ts.server {
for (const symlinkedProject of symlinkedProjects) addToTodo({ project: symlinkedProject, location: originalLocation as TLocation }, toDo!, seenProjects);
});
}
return originalLocation;
return originalLocation === location ? undefined : originalLocation;
});
return toDo;
}
@@ -1037,7 +1039,9 @@ namespace ts.server {
private getEmitOutput(args: protocol.FileRequestArgs): EmitOutput {
const { file, project } = this.getFileAndProject(args);
return project.getLanguageService().getEmitOutput(file);
return project.shouldEmitFile(project.getScriptInfo(file)) ?
project.getLanguageService().getEmitOutput(file) :
{ emitSkipped: true, outputFiles: [] };
}
private mapDefinitionInfo(definitions: readonly DefinitionInfo[], project: Project): readonly protocol.FileSpanWithContext[] {
@@ -1672,10 +1676,10 @@ namespace ts.server {
}
}
private createCheckList(fileNames: string[], defaultProject?: Project): PendingErrorCheck[] {
private createCheckList(fileNames: string[]): PendingErrorCheck[] {
return mapDefined<string, PendingErrorCheck>(fileNames, uncheckedFileName => {
const fileName = toNormalizedPath(uncheckedFileName);
const project = defaultProject || this.projectService.tryGetDefaultProjectForFile(fileName);
const project = this.projectService.tryGetDefaultProjectForFile(fileName);
return project && { fileName, project };
});
}
@@ -0,0 +1,32 @@
/* @internal */
namespace ts.codefix {
const fixId = "fixConvertConstToLet";
const errorCodes = [Diagnostics.Cannot_assign_to_0_because_it_is_a_constant.code];
registerCodeFix({
errorCodes,
getCodeActions: context => {
const { sourceFile, span, program } = context;
const variableStatement = getVariableStatement(sourceFile, span.start, program);
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, variableStatement));
return [createCodeFixAction(fixId, changes, Diagnostics.Convert_const_to_let, fixId, Diagnostics.Convert_const_to_let)];
},
fixIds: [fixId]
});
function getVariableStatement(sourceFile: SourceFile, pos: number, program: Program) {
const token = getTokenAtPosition(sourceFile, pos);
const checker = program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(token);
if (symbol) {
return symbol.valueDeclaration.parent.parent as VariableStatement;
}
}
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, variableStatement?: VariableStatement) {
if (!variableStatement) {
return;
}
const start = variableStatement.getStart();
changes.replaceRangeWithText(sourceFile, { pos: start, end: start + 5 }, "let");
}
}
@@ -15,11 +15,14 @@ namespace ts.codefix {
return [createCodeFixAction(fixId, changes, Diagnostics.Add_async_modifier_to_containing_function, fixId, Diagnostics.Add_all_missing_async_modifiers)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
const nodes = getNodes(diag.file, diag.start);
if (!nodes) return;
doChange(changes, context.sourceFile, nodes);
}),
getAllCodeActions: context => {
const seen = createMap<true>();
return codeFixAll(context, errorCodes, (changes, diag) => {
const nodes = getNodes(diag.file, diag.start);
if (!nodes || !addToSeen(seen, getNodeId(nodes.insertBefore))) return;
doChange(changes, context.sourceFile, nodes);
});
},
});
function getReturnType(expr: FunctionDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction) {
+230 -100
View File
@@ -393,6 +393,19 @@ namespace ts.codefix {
function inferTypeFromReferences(program: Program, references: readonly Identifier[], cancellationToken: CancellationToken) {
const checker = program.getTypeChecker();
const builtinConstructors: { [s: string]: (t: Type) => Type } = {
string: () => checker.getStringType(),
number: () => checker.getNumberType(),
Array: t => checker.createArrayType(t),
Promise: t => checker.createPromiseType(t),
};
const builtins = [
checker.getStringType(),
checker.getNumberType(),
checker.createArrayType(checker.getAnyType()),
checker.createPromiseType(checker.getAnyType()),
];
return {
single,
parameters,
@@ -401,26 +414,74 @@ namespace ts.codefix {
interface CallUsage {
argumentTypes: Type[];
returnType: Usage;
return_: Usage;
}
interface Usage {
isNumber?: boolean;
isString?: boolean;
isNumber: boolean | undefined;
isString: boolean | undefined;
/** Used ambiguously, eg x + ___ or object[___]; results in string | number if no other evidence exists */
isNumberOrString?: boolean;
isNumberOrString: boolean | undefined;
candidateTypes?: Type[];
properties?: UnderscoreEscapedMap<Usage>;
calls?: CallUsage[];
constructs?: CallUsage[];
numberIndex?: Usage;
stringIndex?: Usage;
candidateThisTypes?: Type[];
candidateTypes: Type[] | undefined;
properties: UnderscoreEscapedMap<Usage> | undefined;
calls: CallUsage[] | undefined;
constructs: CallUsage[] | undefined;
numberIndex: Usage | undefined;
stringIndex: Usage | undefined;
candidateThisTypes: Type[] | undefined;
inferredTypes: Type[] | undefined;
}
function createEmptyUsage(): Usage {
return {
isNumber: undefined,
isString: undefined,
isNumberOrString: undefined,
candidateTypes: undefined,
properties: undefined,
calls: undefined,
constructs: undefined,
numberIndex: undefined,
stringIndex: undefined,
candidateThisTypes: undefined,
inferredTypes: undefined,
};
}
function combineUsages(usages: Usage[]): Usage {
const combinedProperties = createUnderscoreEscapedMap<Usage[]>();
for (const u of usages) {
if (u.properties) {
u.properties.forEach((p, name) => {
if (!combinedProperties.has(name)) {
combinedProperties.set(name, []);
}
combinedProperties.get(name)!.push(p);
});
}
}
const properties = createUnderscoreEscapedMap<Usage>();
combinedProperties.forEach((ps, name) => {
properties.set(name, combineUsages(ps));
});
return {
isNumber: usages.some(u => u.isNumber),
isString: usages.some(u => u.isString),
isNumberOrString: usages.some(u => u.isNumberOrString),
candidateTypes: flatMap(usages, u => u.candidateTypes) as Type[],
properties,
calls: flatMap(usages, u => u.calls) as CallUsage[],
constructs: flatMap(usages, u => u.constructs) as CallUsage[],
numberIndex: forEach(usages, u => u.numberIndex),
stringIndex: forEach(usages, u => u.stringIndex),
candidateThisTypes: flatMap(usages, u => u.candidateThisTypes) as Type[],
inferredTypes: undefined, // clear type cache
};
}
function single(): Type {
return unifyFromUsage(inferTypesFromReferencesSingle(references));
return combineTypes(inferTypesFromReferencesSingle(references));
}
function parameters(declaration: FunctionLike): ParameterInference[] | undefined {
@@ -428,7 +489,7 @@ namespace ts.codefix {
return undefined;
}
const usage: Usage = {};
const usage = createEmptyUsage();
for (const reference of references) {
cancellationToken.throwIfCancellationRequested();
calculateUsageOfNode(reference, usage);
@@ -456,7 +517,7 @@ namespace ts.codefix {
const inferred = inferTypesFromReferencesSingle(getReferences(parameter.name, program, cancellationToken));
types.push(...(isRest ? mapDefined(inferred, checker.getElementTypeOfArrayType) : inferred));
}
const type = unifyFromUsage(types);
const type = combineTypes(types);
return {
type: isRest ? checker.createArrayType(type) : type,
isOptional: isOptional && !isRest,
@@ -466,22 +527,22 @@ namespace ts.codefix {
}
function thisParameter() {
const usage: Usage = {};
const usage = createEmptyUsage();
for (const reference of references) {
cancellationToken.throwIfCancellationRequested();
calculateUsageOfNode(reference, usage);
}
return unifyFromUsage(usage.candidateThisTypes || emptyArray);
return combineTypes(usage.candidateThisTypes || emptyArray);
}
function inferTypesFromReferencesSingle(references: readonly Identifier[]): Type[] {
const usage: Usage = {};
const usage: Usage = createEmptyUsage();
for (const reference of references) {
cancellationToken.throwIfCancellationRequested();
calculateUsageOfNode(reference, usage);
}
return inferFromUsage(usage);
return inferTypes(usage);
}
function calculateUsageOfNode(node: Expression, usage: Usage): void {
@@ -490,6 +551,9 @@ namespace ts.codefix {
}
switch (node.parent.kind) {
case SyntaxKind.ExpressionStatement:
addCandidateType(usage, checker.getVoidType());
break;
case SyntaxKind.PostfixUnaryExpression:
usage.isNumber = true;
break;
@@ -632,6 +696,9 @@ namespace ts.codefix {
else if (otherOperandType.flags & TypeFlags.StringLike) {
usage.isString = true;
}
else if (otherOperandType.flags & TypeFlags.Any) {
// do nothing, maybe we'll learn something elsewhere
}
else {
usage.isNumberOrString = true;
}
@@ -677,7 +744,7 @@ namespace ts.codefix {
function inferTypeFromCallExpression(parent: CallExpression | NewExpression, usage: Usage): void {
const call: CallUsage = {
argumentTypes: [],
returnType: {}
return_: createEmptyUsage()
};
if (parent.arguments) {
@@ -686,7 +753,7 @@ namespace ts.codefix {
}
}
calculateUsageOfNode(parent, call.returnType);
calculateUsageOfNode(parent, call.return_);
if (parent.kind === SyntaxKind.CallExpression) {
(usage.calls || (usage.calls = [])).push(call);
}
@@ -700,7 +767,7 @@ namespace ts.codefix {
if (!usage.properties) {
usage.properties = createUnderscoreEscapedMap<Usage>();
}
const propertyUsage = usage.properties.get(name) || { };
const propertyUsage = usage.properties.get(name) || createEmptyUsage();
calculateUsageOfNode(parent, propertyUsage);
usage.properties.set(name, propertyUsage);
}
@@ -712,7 +779,7 @@ namespace ts.codefix {
}
else {
const indexType = checker.getTypeAtLocation(parent.argumentExpression);
const indexUsage = {};
const indexUsage = createEmptyUsage();
calculateUsageOfNode(parent, indexUsage);
if (indexType.flags & TypeFlags.NumberLike) {
usage.numberIndex = indexUsage;
@@ -752,8 +819,12 @@ namespace ts.codefix {
return inferences.filter(i => toRemove.every(f => !f(i)));
}
function unifyFromUsage(inferences: readonly Type[], fallback = checker.getAnyType()): Type {
if (!inferences.length) return fallback;
function combineFromUsage(usage: Usage) {
return combineTypes(inferTypes(usage));
}
function combineTypes(inferences: readonly Type[]): Type {
if (!inferences.length) return checker.getAnyType();
// 1. string or number individually override string | number
// 2. non-any, non-void overrides any or void
@@ -776,12 +847,12 @@ namespace ts.codefix {
const anons = good.filter(i => checker.getObjectFlags(i) & ObjectFlags.Anonymous) as AnonymousType[];
if (anons.length) {
good = good.filter(i => !(checker.getObjectFlags(i) & ObjectFlags.Anonymous));
good.push(unifyAnonymousTypes(anons));
good.push(combineAnonymousTypes(anons));
}
return checker.getWidenedType(checker.getUnionType(good));
return checker.getWidenedType(checker.getUnionType(good.map(checker.getBaseTypeOfLiteralType), UnionReduction.Subtype));
}
function unifyAnonymousTypes(anons: AnonymousType[]) {
function combineAnonymousTypes(anons: AnonymousType[]) {
if (anons.length === 1) {
return anons[0];
}
@@ -822,7 +893,7 @@ namespace ts.codefix {
numberIndices.length ? checker.createIndexInfo(checker.getUnionType(numberIndices), numberIndexReadonly) : undefined);
}
function inferFromUsage(usage: Usage) {
function inferTypes(usage: Usage): Type[] {
const types = [];
if (usage.isNumber) {
@@ -834,92 +905,155 @@ namespace ts.codefix {
if (usage.isNumberOrString) {
types.push(checker.getUnionType([checker.getStringType(), checker.getNumberType()]));
}
if (usage.numberIndex) {
types.push(checker.createArrayType(combineFromUsage(usage.numberIndex)));
}
if (usage.properties && usage.properties.size
|| usage.calls && usage.calls.length
|| usage.constructs && usage.constructs.length
|| usage.stringIndex) {
types.push(inferStructuralType(usage));
}
types.push(...(usage.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t)));
types.push(...inferNamedTypesFromProperties(usage));
if (usage.properties && hasCalls(usage.properties.get("then" as __String))) {
const paramType = getParameterTypeFromCalls(0, usage.properties.get("then" as __String)!.calls!, /*isRestParameter*/ false)!; // TODO: GH#18217
const types = paramType.getCallSignatures().map(sig => sig.getReturnType());
types.push(checker.createPromiseType(types.length ? checker.getUnionType(types, UnionReduction.Subtype) : checker.getAnyType()));
}
else if (usage.properties && hasCalls(usage.properties.get("push" as __String))) {
types.push(checker.createArrayType(getParameterTypeFromCalls(0, usage.properties.get("push" as __String)!.calls!, /*isRestParameter*/ false)!));
}
if (usage.numberIndex) {
types.push(checker.createArrayType(recur(usage.numberIndex)));
}
else if (usage.properties || usage.calls || usage.constructs || usage.stringIndex) {
const members = createUnderscoreEscapedMap<Symbol>();
const callSignatures: Signature[] = [];
const constructSignatures: Signature[] = [];
let stringIndexInfo: IndexInfo | undefined;
if (usage.properties) {
usage.properties.forEach((u, name) => {
const symbol = checker.createSymbol(SymbolFlags.Property, name);
symbol.type = recur(u);
members.set(name, symbol);
});
}
if (usage.calls) {
for (const call of usage.calls) {
callSignatures.push(getSignatureFromCall(call));
}
}
if (usage.constructs) {
for (const construct of usage.constructs) {
constructSignatures.push(getSignatureFromCall(construct));
}
}
if (usage.stringIndex) {
stringIndexInfo = checker.createIndexInfo(recur(usage.stringIndex), /*isReadonly*/ false);
}
types.push(checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined)); // TODO: GH#18217
}
return types;
function recur(innerUsage: Usage): Type {
return unifyFromUsage(inferFromUsage(innerUsage));
}
}
function getParameterTypeFromCalls(parameterIndex: number, calls: CallUsage[], isRestParameter: boolean) {
let types: Type[] = [];
if (calls) {
for (const call of calls) {
if (call.argumentTypes.length > parameterIndex) {
if (isRestParameter) {
types = concatenate(types, map(call.argumentTypes.slice(parameterIndex), a => checker.getBaseTypeOfLiteralType(a)));
}
else {
types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[parameterIndex]));
function inferStructuralType(usage: Usage) {
const members = createUnderscoreEscapedMap<Symbol>();
if (usage.properties) {
usage.properties.forEach((u, name) => {
const symbol = checker.createSymbol(SymbolFlags.Property, name);
symbol.type = combineFromUsage(u);
members.set(name, symbol);
});
}
const callSignatures: Signature[] = usage.calls ? [getSignatureFromCalls(usage.calls)] : [];
const constructSignatures: Signature[] = usage.constructs ? [getSignatureFromCalls(usage.constructs)] : [];
const stringIndexInfo = usage.stringIndex && checker.createIndexInfo(combineFromUsage(usage.stringIndex), /*isReadonly*/ false);
return checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined); // TODO: GH#18217
}
function inferNamedTypesFromProperties(usage: Usage): Type[] {
if (!usage.properties || !usage.properties.size) return [];
const types = builtins.filter(t => allPropertiesAreAssignableToUsage(t, usage));
if (0 < types.length && types.length < 3) {
return types.map(t => inferInstantiationFromUsage(t, usage));
}
return [];
}
function allPropertiesAreAssignableToUsage(type: Type, usage: Usage) {
if (!usage.properties) return false;
return !forEachEntry(usage.properties, (propUsage, name) => {
const source = checker.getTypeOfPropertyOfType(type, name as string);
if (!source) {
return true;
}
if (propUsage.calls) {
const sigs = checker.getSignaturesOfType(source, SignatureKind.Call);
return !sigs.length || !checker.isTypeAssignableTo(source, getFunctionFromCalls(propUsage.calls));
}
else {
return !checker.isTypeAssignableTo(source, combineFromUsage(propUsage));
}
});
}
/**
* inference is limited to
* 1. generic types with a single parameter
* 2. inference to/from calls with a single signature
*/
function inferInstantiationFromUsage(type: Type, usage: Usage) {
if (!(getObjectFlags(type) & ObjectFlags.Reference) || !usage.properties) {
return type;
}
const generic = (type as TypeReference).target;
const singleTypeParameter = singleOrUndefined(generic.typeParameters);
if (!singleTypeParameter) return type;
const types: Type[] = [];
usage.properties.forEach((propUsage, name) => {
const genericPropertyType = checker.getTypeOfPropertyOfType(generic, name as string);
Debug.assert(!!genericPropertyType, "generic should have all the properties of its reference.");
types.push(...inferTypeParameters(genericPropertyType!, combineFromUsage(propUsage), singleTypeParameter));
});
return builtinConstructors[type.symbol.escapedName as string](combineTypes(types));
}
function inferTypeParameters(genericType: Type, usageType: Type, typeParameter: Type): readonly Type[] {
if (genericType === typeParameter) {
return [usageType];
}
else if (genericType.flags & TypeFlags.UnionOrIntersection) {
return flatMap((genericType as UnionOrIntersectionType).types, t => inferTypeParameters(t, usageType, typeParameter));
}
else if (getObjectFlags(genericType) & ObjectFlags.Reference && getObjectFlags(usageType) & ObjectFlags.Reference) {
// this is wrong because we need a reference to the targetType to, so we can check that it's also a reference
const genericArgs = (genericType as TypeReference).typeArguments;
const usageArgs = (usageType as TypeReference).typeArguments;
const types = [];
if (genericArgs && usageArgs) {
for (let i = 0; i < genericArgs.length; i++) {
if (usageArgs[i]) {
types.push(...inferTypeParameters(genericArgs[i], usageArgs[i], typeParameter));
}
}
}
return types;
}
if (types.length) {
const type = checker.getWidenedType(checker.getUnionType(types, UnionReduction.Subtype));
return isRestParameter ? checker.createArrayType(type) : type;
const genericSigs = checker.getSignaturesOfType(genericType, SignatureKind.Call);
const usageSigs = checker.getSignaturesOfType(usageType, SignatureKind.Call);
if (genericSigs.length === 1 && usageSigs.length === 1) {
return inferFromSignatures(genericSigs[0], usageSigs[0], typeParameter);
}
return undefined;
return [];
}
function getSignatureFromCall(call: CallUsage): Signature {
function inferFromSignatures(genericSig: Signature, usageSig: Signature, typeParameter: Type) {
const types = [];
for (let i = 0; i < genericSig.parameters.length; i++) {
const genericParam = genericSig.parameters[i];
const usageParam = usageSig.parameters[i];
const isRest = genericSig.declaration && isRestParameter(genericSig.declaration.parameters[i]);
if (!usageParam) {
break;
}
let genericParamType = checker.getTypeOfSymbolAtLocation(genericParam, genericParam.valueDeclaration);
const elementType = isRest && checker.getElementTypeOfArrayType(genericParamType);
if (elementType) {
genericParamType = elementType;
}
const targetType = (usageParam as SymbolLinks).type || checker.getTypeOfSymbolAtLocation(usageParam, usageParam.valueDeclaration);
types.push(...inferTypeParameters(genericParamType, targetType, typeParameter));
}
const genericReturn = checker.getReturnTypeOfSignature(genericSig);
const usageReturn = checker.getReturnTypeOfSignature(usageSig);
types.push(...inferTypeParameters(genericReturn, usageReturn, typeParameter));
return types;
}
function getFunctionFromCalls(calls: CallUsage[]) {
return checker.createAnonymousType(undefined!, createSymbolTable(), [getSignatureFromCalls(calls)], emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined);
}
function getSignatureFromCalls(calls: CallUsage[]): Signature {
const parameters: Symbol[] = [];
for (let i = 0; i < call.argumentTypes.length; i++) {
const length = Math.max(...calls.map(c => c.argumentTypes.length));
for (let i = 0; i < length; i++) {
const symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, escapeLeadingUnderscores(`arg${i}`));
symbol.type = checker.getWidenedType(checker.getBaseTypeOfLiteralType(call.argumentTypes[i]));
symbol.type = combineTypes(calls.map(call => call.argumentTypes[i] || checker.getUndefinedType()));
if (calls.some(call => call.argumentTypes[i] === undefined)) {
symbol.flags |= SymbolFlags.Optional;
}
parameters.push(symbol);
}
const returnType = unifyFromUsage(inferFromUsage(call.returnType), checker.getVoidType());
const returnType = combineFromUsage(combineUsages(calls.map(call => call.return_)));
// TODO: GH#18217
return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, call.argumentTypes.length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
}
function addCandidateType(usage: Usage, type: Type | undefined) {
@@ -933,9 +1067,5 @@ namespace ts.codefix {
(usage.candidateThisTypes || (usage.candidateThisTypes = [])).push(type);
}
}
function hasCalls(usage: Usage | undefined): boolean {
return !!usage && !!usage.calls;
}
}
}
@@ -0,0 +1,33 @@
/* @internal */
namespace ts.codefix {
const fixId = "useBigintLiteral";
const errorCodes = [
Diagnostics.Numeric_literals_with_absolute_values_equal_to_2_53_or_greater_are_too_large_to_be_represented_accurately_as_integers.code,
];
registerCodeFix({
errorCodes,
getCodeActions: context => {
const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span));
if (changes.length > 0) {
return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_a_bigint_numeric_literal, fixId, Diagnostics.Convert_all_to_bigint_numeric_literals)];
}
},
fixIds: [fixId],
getAllCodeActions: context => {
return codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag));
},
});
function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, span: TextSpan) {
const numericLiteral = tryCast(getTokenAtPosition(sourceFile, span.start), isNumericLiteral);
if (!numericLiteral) {
return;
}
// We use .getText to overcome parser inaccuracies: https://github.com/microsoft/TypeScript/issues/33298
const newText = numericLiteral.getText(sourceFile) + "n";
changeTracker.replaceNode(sourceFile, numericLiteral, createBigIntLiteral(newText));
}
}
+2 -2
View File
@@ -1145,7 +1145,7 @@ namespace ts.FindAllReferences.Core {
}
export function eachSymbolReferenceInFile<T>(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, cb: (token: Identifier) => T): T | undefined {
const symbol = isParameterPropertyDeclaration(definition.parent)
const symbol = isParameterPropertyDeclaration(definition.parent, definition.parent.parent)
? first(checker.getSymbolsOfParameterPropertyDeclaration(definition.parent, definition.text))
: checker.getSymbolAtLocation(definition);
if (!symbol) return undefined;
@@ -1886,7 +1886,7 @@ namespace ts.FindAllReferences.Core {
const res = fromRoot(symbol);
if (res) return res;
if (symbol.valueDeclaration && isParameterPropertyDeclaration(symbol.valueDeclaration)) {
if (symbol.valueDeclaration && isParameterPropertyDeclaration(symbol.valueDeclaration, symbol.valueDeclaration.parent)) {
// For a parameter property, now try on the other symbol (property if this was a parameter, parameter if this was a property).
const paramProps = checker.getSymbolsOfParameterPropertyDeclaration(cast(symbol.valueDeclaration, isParameter), symbol.name);
Debug.assert(paramProps.length === 2 && !!(paramProps[0].flags & SymbolFlags.FunctionScopedVariable) && !!(paramProps[1].flags & SymbolFlags.Property)); // is [parameter, property]
+39 -39
View File
@@ -56,7 +56,7 @@ namespace ts.NavigationBar {
curCancellationToken = cancellationToken;
curSourceFile = sourceFile;
try {
return map(topLevelItems(rootNavigationBarNode(sourceFile)), convertToTopLevelItem);
return map(primaryNavBarMenuItems(rootNavigationBarNode(sourceFile)), convertToPrimaryNavBarMenuItem);
}
finally {
reset();
@@ -111,8 +111,8 @@ namespace ts.NavigationBar {
return root;
}
function addLeafNode(node: Node): void {
pushChild(parent, emptyNavigationBarNode(node));
function addLeafNode(node: Node, name?: DeclarationName): void {
pushChild(parent, emptyNavigationBarNode(node, name));
}
function emptyNavigationBarNode(node: Node, name?: DeclarationName): NavigationBarNode {
@@ -197,7 +197,7 @@ namespace ts.NavigationBar {
// Parameter properties are children of the class, not the constructor.
for (const param of ctr.parameters) {
if (isParameterPropertyDeclaration(param)) {
if (isParameterPropertyDeclaration(param, ctr)) {
addLeafNode(param);
}
}
@@ -243,23 +243,26 @@ namespace ts.NavigationBar {
}
break;
case SyntaxKind.ShorthandPropertyAssignment:
addNodeWithRecursiveChild(node, (<ShorthandPropertyAssignment>node).name);
break;
case SyntaxKind.SpreadAssignment:
const { expression } = <SpreadAssignment>node;
// Use the expression as the name of the SpreadAssignment, otherwise show as <unknown>.
isIdentifier(expression) ? addLeafNode(node, expression) : addLeafNode(node);
break;
case SyntaxKind.BindingElement:
case SyntaxKind.PropertyAssignment:
case SyntaxKind.VariableDeclaration:
const { name, initializer } = <VariableDeclaration | BindingElement>node;
const { name, initializer } = <VariableDeclaration | PropertyAssignment | BindingElement>node;
if (isBindingPattern(name)) {
addChildrenRecursively(name);
}
else if (initializer && isFunctionOrClassExpression(initializer)) {
if (initializer.name) {
// Don't add a node for the VariableDeclaration, just for the initializer.
addChildrenRecursively(initializer);
}
else {
// Add a node for the VariableDeclaration, but not for the initializer.
startNode(node);
forEachChild(initializer, addChildrenRecursively);
endNode();
}
// Add a node for the VariableDeclaration, but not for the initializer.
startNode(node);
forEachChild(initializer, addChildrenRecursively);
endNode();
}
else {
addNodeWithRecursiveChild(node, initializer);
@@ -699,12 +702,15 @@ namespace ts.NavigationBar {
}
}
/** Flattens the NavNode tree to a list, keeping only the top-level items. */
function topLevelItems(root: NavigationBarNode): NavigationBarNode[] {
const topLevel: NavigationBarNode[] = [];
/** Flattens the NavNode tree to a list of items to appear in the primary navbar menu. */
function primaryNavBarMenuItems(root: NavigationBarNode): NavigationBarNode[] {
// The primary (middle) navbar menu displays the general code navigation hierarchy, similar to the navtree.
// The secondary (right) navbar menu displays the child items of whichever primary item is selected.
// Some less interesting items without their own child navigation items (e.g. a local variable declaration) only show up in the secondary menu.
const primaryNavBarMenuItems: NavigationBarNode[] = [];
function recur(item: NavigationBarNode) {
if (isTopLevel(item)) {
topLevel.push(item);
if (shouldAppearInPrimaryNavBarMenu(item)) {
primaryNavBarMenuItems.push(item);
if (item.children) {
for (const child of item.children) {
recur(child);
@@ -713,9 +719,16 @@ namespace ts.NavigationBar {
}
}
recur(root);
return topLevel;
return primaryNavBarMenuItems;
function isTopLevel(item: NavigationBarNode): boolean {
/** Determines if a node should appear in the primary navbar menu. */
function shouldAppearInPrimaryNavBarMenu(item: NavigationBarNode): boolean {
// Items with children should always appear in the primary navbar menu.
if (item.children) {
return true;
}
// Some nodes are otherwise important enough to always include in the primary navigation menu.
switch (navigationBarNodeKind(item)) {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
@@ -728,13 +741,6 @@ namespace ts.NavigationBar {
case SyntaxKind.JSDocCallbackTag:
return true;
case SyntaxKind.Constructor:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.VariableDeclaration:
return hasSomeImportantChild(item);
case SyntaxKind.ArrowFunction:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
@@ -755,15 +761,9 @@ namespace ts.NavigationBar {
case SyntaxKind.Constructor:
return true;
default:
return hasSomeImportantChild(item);
return false;
}
}
function hasSomeImportantChild(item: NavigationBarNode): boolean {
return some(item.children, child => {
const childKind = navigationBarNodeKind(child);
return childKind !== SyntaxKind.VariableDeclaration && childKind !== SyntaxKind.BindingElement;
});
}
}
}
@@ -778,19 +778,19 @@ namespace ts.NavigationBar {
};
}
function convertToTopLevelItem(n: NavigationBarNode): NavigationBarItem {
function convertToPrimaryNavBarMenuItem(n: NavigationBarNode): NavigationBarItem {
return {
text: getItemName(n.node, n.name),
kind: getNodeKind(n.node),
kindModifiers: getModifiers(n.node),
spans: getSpans(n),
childItems: map(n.children, convertToChildItem) || emptyChildItemArray,
childItems: map(n.children, convertToSecondaryNavBarMenuItem) || emptyChildItemArray,
indent: n.indent,
bolded: false,
grayed: false
};
function convertToChildItem(n: NavigationBarNode): NavigationBarItem {
function convertToSecondaryNavBarMenuItem(n: NavigationBarNode): NavigationBarItem {
return {
text: getItemName(n.node, n.name),
kind: getNodeKind(n.node),
+77 -2
View File
@@ -116,6 +116,7 @@ namespace ts.refactor.extractSymbol {
export const cannotExtractRange: DiagnosticMessage = createMessage("Cannot extract range.");
export const cannotExtractImport: DiagnosticMessage = createMessage("Cannot extract import statement.");
export const cannotExtractSuper: DiagnosticMessage = createMessage("Cannot extract super call.");
export const cannotExtractJSDoc: DiagnosticMessage = createMessage("Cannot extract JSDoc.");
export const cannotExtractEmpty: DiagnosticMessage = createMessage("Cannot extract empty range.");
export const expressionExpected: DiagnosticMessage = createMessage("expression expected.");
export const uselessConstantType: DiagnosticMessage = createMessage("No reason to extract constant of type.");
@@ -246,6 +247,10 @@ namespace ts.refactor.extractSymbol {
return { targetRange: { range: statements, facts: rangeFacts, declarations } };
}
if (isJSDoc(start)) {
return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractJSDoc)] };
}
if (isReturnStatement(start) && !start.expression) {
// Makes no sense to extract an expression-less return statement.
return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] };
@@ -1006,11 +1011,14 @@ namespace ts.refactor.extractSymbol {
const localNameText = getUniqueName(isClassLike(scope) ? "newProperty" : "newLocal", file);
const isJS = isInJSFile(scope);
const variableType = isJS || !checker.isContextSensitive(node)
let variableType = isJS || !checker.isContextSensitive(node)
? undefined
: checker.typeToTypeNode(checker.getContextualType(node)!, scope, NodeBuilderFlags.NoTruncation); // TODO: GH#18217
const initializer = transformConstantInitializer(node, substitutions);
let initializer = transformConstantInitializer(node, substitutions);
({ variableType, initializer } = transformFunctionInitializerAndType(variableType, initializer));
suppressLeadingAndTrailingTrivia(initializer);
const changeTracker = textChanges.ChangeTracker.fromContext(context);
@@ -1102,6 +1110,73 @@ namespace ts.refactor.extractSymbol {
const renameFilename = node.getSourceFile().fileName;
const renameLocation = getRenameLocation(edits, renameFilename, localNameText, /*isDeclaredBeforeUse*/ true);
return { renameFilename, renameLocation, edits };
function transformFunctionInitializerAndType(variableType: TypeNode | undefined, initializer: Expression): { variableType: TypeNode | undefined, initializer: Expression } {
// If no contextual type exists there is nothing to transfer to the function signature
if (variableType === undefined) return { variableType, initializer };
// Only do this for function expressions and arrow functions that are not generic
if (!isFunctionExpression(initializer) && !isArrowFunction(initializer) || !!initializer.typeParameters) return { variableType, initializer };
const functionType = checker.getTypeAtLocation(node);
const functionSignature = singleOrUndefined(checker.getSignaturesOfType(functionType, SignatureKind.Call));
// If no function signature, maybe there was an error, do nothing
if (!functionSignature) return { variableType, initializer };
// If the function signature has generic type parameters we don't attempt to move the parameters
if (!!functionSignature.getTypeParameters()) return { variableType, initializer };
// We add parameter types if needed
const parameters: ParameterDeclaration[] = [];
let hasAny = false;
for (const p of initializer.parameters) {
if (p.type) {
parameters.push(p);
}
else {
const paramType = checker.getTypeAtLocation(p);
if (paramType === checker.getAnyType()) hasAny = true;
parameters.push(updateParameter(p,
p.decorators, p.modifiers, p.dotDotDotToken,
p.name, p.questionToken, p.type || checker.typeToTypeNode(paramType, scope, NodeBuilderFlags.NoTruncation), p.initializer));
}
}
// If a parameter was inferred as any we skip adding function parameters at all.
// Turning an implicit any (which under common settings is a error) to an explicit
// is probably actually a worse refactor outcome.
if (hasAny) return { variableType, initializer };
variableType = undefined;
if (isArrowFunction(initializer)) {
initializer = updateArrowFunction(initializer, node.modifiers, initializer.typeParameters,
parameters,
initializer.type || checker.typeToTypeNode(functionSignature.getReturnType(), scope, NodeBuilderFlags.NoTruncation),
initializer.equalsGreaterThanToken,
initializer.body);
}
else {
if (functionSignature && !!functionSignature.thisParameter) {
const firstParameter = firstOrUndefined(parameters);
// If the function signature has a this parameter and if the first defined parameter is not the this parameter, we must add it
// Note: If this parameter was already there, it would have been previously updated with the type if not type was present
if ((!firstParameter || (isIdentifier(firstParameter.name) && firstParameter.name.escapedText !== "this"))) {
const thisType = checker.getTypeOfSymbolAtLocation(functionSignature.thisParameter, node);
parameters.splice(0, 0, createParameter(
/* decorators */ undefined,
/* modifiers */ undefined,
/* dotDotDotToken */ undefined,
"this",
/* questionToken */ undefined,
checker.typeToTypeNode(thisType, scope, NodeBuilderFlags.NoTruncation)
));
}
}
initializer = updateFunctionExpression(initializer, node.modifiers, initializer.asteriskToken,
initializer.name, initializer.typeParameters,
parameters,
initializer.type || checker.typeToTypeNode(functionSignature.getReturnType(), scope, NodeBuilderFlags.NoTruncation),
initializer.body);
}
return { variableType, initializer };
}
}
function getContainingVariableDeclarationIfInList(node: Node, scope: Scope) {
@@ -92,7 +92,7 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
}
function isAcceptedDeclaration(node: Node): node is AcceptedDeclaration {
return isParameterPropertyDeclaration(node) || isPropertyDeclaration(node) || isPropertyAssignment(node);
return isParameterPropertyDeclaration(node, node.parent) || isPropertyDeclaration(node) || isPropertyAssignment(node);
}
function createPropertyName (name: string, originalName: AcceptedNameType) {
@@ -214,7 +214,7 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
}
function insertAccessor(changeTracker: textChanges.ChangeTracker, file: SourceFile, accessor: AccessorDeclaration, declaration: AcceptedDeclaration, container: ContainerDeclaration) {
isParameterPropertyDeclaration(declaration) ? changeTracker.insertNodeAtClassStart(file, <ClassLikeDeclaration>container, accessor) :
isParameterPropertyDeclaration(declaration, declaration.parent) ? changeTracker.insertNodeAtClassStart(file, <ClassLikeDeclaration>container, accessor) :
isPropertyAssignment(declaration) ? changeTracker.insertNodeAfterComma(file, declaration, accessor) :
changeTracker.insertNodeAfter(file, declaration, accessor);
}
+10 -4
View File
@@ -1149,10 +1149,10 @@ namespace ts {
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
getCurrentDirectory: () => currentDirectory,
getProgram,
fileExists: host.fileExists && (f => host.fileExists!(f)),
readFile: host.readFile && ((f, encoding) => host.readFile!(f, encoding)),
getDocumentPositionMapper: host.getDocumentPositionMapper && ((generatedFileName, sourceFileName) => host.getDocumentPositionMapper!(generatedFileName, sourceFileName)),
getSourceFileLike: host.getSourceFileLike && (f => host.getSourceFileLike!(f)),
fileExists: maybeBind(host, host.fileExists),
readFile: maybeBind(host, host.readFile),
getDocumentPositionMapper: maybeBind(host, host.getDocumentPositionMapper),
getSourceFileLike: maybeBind(host, host.getSourceFileLike),
log
});
@@ -1250,6 +1250,12 @@ namespace ts {
if (host.resolveTypeReferenceDirectives) {
compilerHost.resolveTypeReferenceDirectives = (...args) => host.resolveTypeReferenceDirectives!(...args);
}
if (host.setResolvedProjectReferenceCallbacks) {
compilerHost.setResolvedProjectReferenceCallbacks = callbacks => host.setResolvedProjectReferenceCallbacks!(callbacks);
}
if (host.useSourceOfProjectReferenceRedirect) {
compilerHost.useSourceOfProjectReferenceRedirect = () => host.useSourceOfProjectReferenceRedirect!();
}
const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings);
const options: CreateProgramOptions = {
+5
View File
@@ -70,6 +70,11 @@ namespace ts {
if (!sourceFile) return undefined;
const program = host.getProgram()!;
// If this is source file of project reference source (instead of redirect) there is no generated position
if (program.isSourceOfProjectReferenceRedirect(sourceFile.fileName)) {
return undefined;
}
const options = program.getCompilerOptions();
const outPath = options.outFile || options.out;
+2
View File
@@ -79,9 +79,11 @@
"codefixes/fixStrictClassInitialization.ts",
"codefixes/requireInTs.ts",
"codefixes/useDefaultImport.ts",
"codefixes/useBigintLiteral.ts",
"codefixes/fixAddModuleReferTypeMissingTypeof.ts",
"codefixes/convertToMappedObjectType.ts",
"codefixes/removeUnnecessaryAwait.ts",
"codefixes/convertConstToLet.ts",
"refactors/convertExport.ts",
"refactors/convertImport.ts",
"refactors/extractSymbol.ts",
+4
View File
@@ -234,6 +234,10 @@ namespace ts {
getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined;
/* @internal */
getSourceFileLike?(fileName: string): SourceFileLike | undefined;
/* @internal */
setResolvedProjectReferenceCallbacks?(callbacks: ResolvedProjectReferenceCallbacks): void;
/* @internal */
useSourceOfProjectReferenceRedirect?(): boolean;
}
/* @internal */
+5
View File
@@ -353,8 +353,13 @@ namespace ts {
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
return ScriptElementKind.memberFunctionElement;
case SyntaxKind.PropertyAssignment:
const {initializer} = node as PropertyAssignment;
return isFunctionLike(initializer) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement;
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
case SyntaxKind.ShorthandPropertyAssignment:
case SyntaxKind.SpreadAssignment:
return ScriptElementKind.memberVariableElement;
case SyntaxKind.IndexSignature: return ScriptElementKind.indexSignatureElement;
case SyntaxKind.ConstructSignature: return ScriptElementKind.constructSignatureElement;
+10 -5
View File
@@ -10,6 +10,7 @@ interface ExecResult {
interface UserConfig {
types: string[];
cloneUrl: string;
path?: string;
}
@@ -49,13 +50,17 @@ abstract class ExternalCompileRunnerBase extends RunnerBase {
const stdio = isWorker ? "pipe" : "inherit";
let types: string[] | undefined;
if (fs.existsSync(path.join(cwd, "test.json"))) {
const submoduleDir = path.join(cwd, directoryName);
exec("git", ["reset", "HEAD", "--hard"], { cwd: submoduleDir });
exec("git", ["clean", "-f"], { cwd: submoduleDir });
exec("git", ["submodule", "update", "--init", "--remote", "."], { cwd: originalCwd });
const config = JSON.parse(fs.readFileSync(path.join(cwd, "test.json"), { encoding: "utf8" })) as UserConfig;
ts.Debug.assert(!!config.types, "Bad format from test.json: Types field must be present.");
ts.Debug.assert(!!config.cloneUrl, "Bad format from test.json: cloneUrl field must be present.");
const submoduleDir = path.join(cwd, directoryName);
if (!fs.existsSync(submoduleDir)) {
exec("git", ["clone", config.cloneUrl, directoryName], { cwd });
}
exec("git", ["reset", "HEAD", "--hard"], { cwd: submoduleDir });
exec("git", ["clean", "-f"], { cwd: submoduleDir });
exec("git", ["pull", "-f"], { cwd: submoduleDir });
types = config.types;
cwd = config.path ? path.join(cwd, config.path) : submoduleDir;
+3
View File
@@ -38,6 +38,7 @@
"unittests/services/extract/helpers.ts",
"unittests/tsbuild/helpers.ts",
"unittests/tsc/helpers.ts",
"unittests/tscWatch/helpers.ts",
"unittests/tsserver/helpers.ts",
@@ -145,6 +146,8 @@
"unittests/tsserver/occurences.ts",
"unittests/tsserver/openFile.ts",
"unittests/tsserver/projectErrors.ts",
"unittests/tsserver/projectReferenceCompileOnSave.ts",
"unittests/tsserver/projectReferenceErrors.ts",
"unittests/tsserver/projectReferences.ts",
"unittests/tsserver/projects.ts",
"unittests/tsserver/refactors.ts",
+18
View File
@@ -160,4 +160,22 @@ namespace ts {
}
}
});
describe("unittests:: Program.getNodeCount / Program.getIdentifierCount", () => {
it("works on projects that have .json files", () => {
const main = new documents.TextDocument("/main.ts", 'export { version } from "./package.json";');
const pkg = new documents.TextDocument("/package.json", '{"version": "1.0.0"}');
const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, pkg], cwd: "/" });
const program = createProgram(["/main.ts"], { resolveJsonModule: true }, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed }));
const json = program.getSourceFile("/package.json")!;
assert.equal(json.scriptKind, ScriptKind.JSON);
assert.isNumber(json.nodeCount);
assert.isNumber(json.identifierCount);
assert.isNotNaN(program.getNodeCount());
assert.isNotNaN(program.getIdentifierCount());
});
});
}
@@ -380,6 +380,10 @@ switch (x) {
`[#|{ 1;|] }`,
[refactor.extractSymbol.Messages.cannotExtractRange.message]);
testExtractRangeFailed("extractRangeFailed19",
`[#|/** @type {number} */|] const foo = 1;`,
[refactor.extractSymbol.Messages.cannotExtractJSDoc.message]);
testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, [refactor.extractSymbol.Messages.cannotExtractIdentifier.message]);
});
}
+29 -1
View File
@@ -300,6 +300,34 @@ namespace ts {
});
});
// https://github.com/microsoft/TypeScript/issues/33295
testBaseline("transformParameterProperty", () => {
return transpileModule("", {
transformers: {
before: [transformAddParameterProperty],
},
compilerOptions: {
target: ScriptTarget.ES5,
newLine: NewLineKind.CarriageReturnLineFeed,
}
}).outputText;
function transformAddParameterProperty(_context: TransformationContext) {
return (sourceFile: SourceFile): SourceFile => {
return visitNode(sourceFile);
};
function visitNode(sf: SourceFile) {
// produce `class Foo { constructor(@Dec private x) {} }`;
// The decorator is required to trigger ts.ts transformations.
const classDecl = createClassDeclaration([], [], "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [
createConstructor(/*decorators*/ undefined, /*modifiers*/ undefined, [
createParameter(/*decorators*/ [createDecorator(createIdentifier("Dec"))], /*modifiers*/ [createModifier(SyntaxKind.PrivateKeyword)], /*dotDotDotToken*/ undefined, "x")], createBlock([]))
]);
return updateSourceFileNode(sf, [classDecl]);
}
}
});
function baselineDeclarationTransform(text: string, opts: TranspileOptions) {
const fs = vfs.createFromFileSystem(Harness.IO, /*caseSensitive*/ true, { documents: [new documents.TextDocument("/.src/index.ts", text)] });
const host = new fakes.CompilerHost(fs, opts.compilerOptions);
@@ -389,7 +417,7 @@ class Clazz {
}
`, {
transformers: {
before: [addSyntheticComment(n => isPropertyDeclaration(n) || isParameterPropertyDeclaration(n) || isClassDeclaration(n) || isConstructorDeclaration(n))],
before: [addSyntheticComment(n => isPropertyDeclaration(n) || isParameterPropertyDeclaration(n, n.parent) || isClassDeclaration(n) || isConstructorDeclaration(n))],
},
compilerOptions: {
target: ScriptTarget.ES2015,
@@ -1,27 +1,8 @@
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, readonly string[]];
const enum source { config, ts }
const sources: [Sources, Sources] = [
@@ -43,68 +24,52 @@ namespace ts {
]
];
before(() => {
outFileFs = loadProjectFromDisk("tests/projects/amdModulesWithOut", time);
outFileFs = loadProjectFromDisk("tests/projects/amdModulesWithOut");
});
after(() => {
outFileFs = undefined!;
});
interface VerifyOutFileScenarioInput {
scenario: string;
modifyFs: (fs: vfs.FileSystem) => void;
subScenario: string;
modifyFs?: (fs: vfs.FileSystem) => void;
modifyAgainFs?: (fs: vfs.FileSystem) => void;
}
function verifyOutFileScenario({
scenario,
subScenario,
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]]
],
lastProjectOutput: 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
verifyTscIncrementalEdits({
scenario: "amdModulesWithOut",
subScenario,
fs: () => outFileFs,
commandLineArgs: ["--b", "/src/app", "--verbose"],
baselineSourceMap: true,
modifyFs,
incrementalScenarios: [
{
buildKind: BuildKind.IncrementalDtsUnchanged,
modifyFs: fs => appendText(fs, relName(sources[project.lib][source.ts][1]), "console.log(x);")
},
...(modifyAgainFs ? [{
buildKind: BuildKind.IncrementalHeadersChange,
modifyFs: modifyAgainFs
}] : emptyArray),
]
});
}
describe("Prepend output with .tsbuildinfo", () => {
verifyOutFileScenario({
scenario: "modules and globals mixed in amd",
modifyFs: noop
subScenario: "modules and globals mixed in amd",
});
// Prologues
describe("Prologues", () => {
verifyOutFileScenario({
scenario: "multiple prologues in all projects",
subScenario: "multiple prologues in all projects",
modifyFs: fs => {
enableStrict(fs, sources[project.lib][source.config]);
addTestPrologue(fs, sources[project.lib][source.ts][0], `"myPrologue"`);
@@ -122,7 +87,7 @@ namespace ts {
describe("Shebang", () => {
// changes declaration because its emitted in .d.ts file
verifyOutFileScenario({
scenario: "shebang in all projects",
subScenario: "shebang in all projects",
modifyFs: fs => {
addShebang(fs, "lib", "file0");
addShebang(fs, "lib", "file1");
@@ -134,7 +99,7 @@ namespace ts {
// emitHelpers
describe("emitHelpers", () => {
verifyOutFileScenario({
scenario: "multiple emitHelpers in all projects",
subScenario: "multiple emitHelpers in all projects",
modifyFs: fs => {
addSpread(fs, "lib", "file0");
addRest(fs, "lib", "file1");
@@ -149,7 +114,7 @@ namespace ts {
describe("triple slash refs", () => {
// changes declaration because its emitted in .d.ts file
verifyOutFileScenario({
scenario: "triple slash refs in all projects",
subScenario: "triple slash refs in all projects",
modifyFs: fs => {
addTripleSlashRef(fs, "lib", "file0");
addTripleSlashRef(fs, "app", "file4");
@@ -193,7 +158,7 @@ ${internal} export enum internalEnum { a, b, c }`);
// Verify initial + incremental edits
verifyOutFileScenario({
scenario: "stripInternal",
subScenario: "stripInternal",
modifyFs: stripInternalScenario,
modifyAgainFs: fs => replaceText(fs, sources[project.lib][source.ts][1], `export const`, `/*@internal*/ export const`),
});
@@ -207,47 +172,13 @@ ${internal} export enum internalEnum { a, b, c }`);
replaceText(fs, sources[project.app][source.ts][0], "file1", "lib/file1");
}
const libOutputFile: 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"
];
verifyTsbuildOutput({
scenario: "when the module resolution finds original source file",
projFs: () => outFileFs,
time,
tick,
proj: "amdModulesWithOut",
rootNames: ["/src/app"],
expectedMapFileNames: [
libOutputFile[ext.jsmap],
libOutputFile[ext.dtsmap],
outputFiles[project.app][ext.jsmap],
outputFiles[project.app][ext.dtsmap],
],
expectedBuildInfoFilesForSectionBaselines: [
[libOutputFile[ext.buildinfo], libOutputFile[ext.js], libOutputFile[ext.dts]],
[outputFiles[project.app][ext.buildinfo], outputFiles[project.app][ext.js], outputFiles[project.app][ext.dts]]
],
lastProjectOutput: outputFiles[project.app][ext.js],
initialBuild: {
modifyFs,
expectedDiagnostics: [
getExpectedDiagnosticForProjectsInBuild("src/lib/tsconfig.json", "src/app/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/lib/tsconfig.json", "src/module.js"],
[Diagnostics.Building_project_0, sources[project.lib][source.config]],
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/app/tsconfig.json", "src/app/module.js"],
[Diagnostics.Building_project_0, sources[project.app][source.config]],
]
},
outputFiles: [
...libOutputFile,
...outputFiles[project.app]
],
baselineOnly: true,
verifyDiagnostics: true
verifyTsc({
scenario: "amdModulesWithOut",
subScenario: "when the module resolution finds original source file",
fs: () => outFileFs,
commandLineArgs: ["-b", "/src/app", "--verbose"],
modifyFs,
baselineSourceMap: true,
});
});
});
@@ -19,7 +19,7 @@ namespace ts {
it("verify that subsequent builds after initial build doesnt build anything", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
createSolutionBuilder(host, ["/src"], { verbose: true }).build();
host.assertDiagnosticMessages(
getExpectedDiagnosticForProjectsInBuild("src/src/folder/tsconfig.json", "src/src/folder2/tsconfig.json", "src/src/tsconfig.json", "src/tests/tsconfig.json", "src/tsconfig.json"),
+2 -4
View File
@@ -1,10 +1,8 @@
namespace ts {
describe("unittests:: tsbuild:: on demo project", () => {
let projFs: vfs.FileSystem;
const { time } = getTime();
before(() => {
projFs = loadProjectFromDisk("tests/projects/demo", time);
projFs = loadProjectFromDisk("tests/projects/demo");
});
after(() => {
@@ -49,7 +47,7 @@ namespace ts {
function verifyBuild({ modifyDiskLayout, expectedExitStatus, expectedDiagnostics, expectedOutputs, notExpectedOutputs }: VerifyBuild) {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
modifyDiskLayout(fs);
const builder = createSolutionBuilder(host, ["/src/tsconfig.json"], { verbose: true });
const exitStatus = builder.build();
@@ -1,109 +1,53 @@
namespace ts {
describe("unittests:: tsbuild:: on project with emitDeclarationOnly set to true", () => {
let projFs: vfs.FileSystem;
const { time, tick } = getTime();
before(() => {
projFs = loadProjectFromDisk("tests/projects/emitDeclarationOnly", time);
projFs = loadProjectFromDisk("tests/projects/emitDeclarationOnly");
});
after(() => {
projFs = undefined!;
});
function verifyEmitDeclarationOnly(disableMap?: true) {
verifyTsbuildOutput({
scenario: `only dts output in circular import project with emitDeclarationOnly${disableMap ? "" : " and declarationMap"}`,
projFs: () => projFs,
time,
tick,
proj: "emitDeclarationOnly",
rootNames: ["/src"],
lastProjectOutput: `/src/lib/index.d.ts`,
outputFiles: [
"/src/lib/a.d.ts",
"/src/lib/b.d.ts",
"/src/lib/c.d.ts",
"/src/lib/index.d.ts",
"/src/tsconfig.tsbuildinfo",
...(disableMap ? emptyArray : [
"/src/lib/a.d.ts.map",
"/src/lib/b.d.ts.map",
"/src/lib/c.d.ts.map",
"/src/lib/index.d.ts.map"
])
],
initialBuild: {
modifyFs: disableMap ?
(fs => replaceText(fs, "/src/tsconfig.json", `"declarationMap": true,`, "")) :
noop,
expectedDiagnostics: [
getExpectedDiagnosticForProjectsInBuild("src/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/tsconfig.json", "src/lib/a.d.ts"],
[Diagnostics.Building_project_0, "/src/tsconfig.json"]
]
},
incrementalDtsChangedBuild: {
verifyTscIncrementalEdits({
subScenario: `only dts output in circular import project with emitDeclarationOnly${disableMap ? "" : " and declarationMap"}`,
fs: () => projFs,
scenario: "emitDeclarationOnly",
commandLineArgs: ["--b", "/src", "--verbose"],
modifyFs: disableMap ?
(fs => replaceText(fs, "/src/tsconfig.json", `"declarationMap": true,`, "")) :
undefined,
incrementalScenarios: [{
buildKind: BuildKind.IncrementalDtsChange,
modifyFs: fs => replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"),
expectedDiagnostics: [
getExpectedDiagnosticForProjectsInBuild("src/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/tsconfig.json", "src/lib/a.d.ts", "src/src/a.ts"],
[Diagnostics.Building_project_0, "/src/tsconfig.json"]
]
},
baselineOnly: true,
verifyDiagnostics: true
}],
});
}
verifyEmitDeclarationOnly();
verifyEmitDeclarationOnly(/*disableMap*/ true);
verifyTsbuildOutput({
scenario: `only dts output in non circular imports project with emitDeclarationOnly`,
projFs: () => projFs,
time,
tick,
proj: "emitDeclarationOnly",
rootNames: ["/src"],
lastProjectOutput: `/src/lib/a.d.ts`,
outputFiles: [
"/src/lib/a.d.ts",
"/src/lib/b.d.ts",
"/src/lib/c.d.ts",
"/src/tsconfig.tsbuildinfo",
"/src/lib/a.d.ts.map",
"/src/lib/b.d.ts.map",
"/src/lib/c.d.ts.map",
],
initialBuild: {
modifyFs: fs => {
fs.rimrafSync("/src/src/index.ts");
replaceText(fs, "/src/src/a.ts", `import { B } from "./b";`, `export class B { prop = "hello"; }`);
verifyTscIncrementalEdits({
subScenario: `only dts output in non circular imports project with emitDeclarationOnly`,
fs: () => projFs,
scenario: "emitDeclarationOnly",
commandLineArgs: ["--b", "/src", "--verbose"],
modifyFs: fs => {
fs.rimrafSync("/src/src/index.ts");
replaceText(fs, "/src/src/a.ts", `import { B } from "./b";`, `export class B { prop = "hello"; }`);
},
incrementalScenarios: [
{
buildKind: BuildKind.IncrementalDtsChange,
modifyFs: fs => replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"),
},
expectedDiagnostics: [
getExpectedDiagnosticForProjectsInBuild("src/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/tsconfig.json", "src/lib/a.d.ts"],
[Diagnostics.Building_project_0, "/src/tsconfig.json"]
]
},
incrementalDtsChangedBuild: {
modifyFs: fs => replaceText(fs, "/src/src/a.ts", "b: B;", "b: B; foo: any;"),
expectedDiagnostics: [
getExpectedDiagnosticForProjectsInBuild("src/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/tsconfig.json", "src/lib/a.d.ts", "src/src/a.ts"],
[Diagnostics.Building_project_0, "/src/tsconfig.json"]
]
},
incrementalDtsUnchangedBuild: {
modifyFs: fs => replaceText(fs, "/src/src/a.ts", "export interface A {", `class C { }
{
buildKind: BuildKind.IncrementalDtsUnchanged,
modifyFs: fs => replaceText(fs, "/src/src/a.ts", "export interface A {", `class C { }
export interface A {`),
expectedDiagnostics: [
getExpectedDiagnosticForProjectsInBuild("src/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/tsconfig.json", "src/lib/a.d.ts", "src/src/a.ts"],
[Diagnostics.Building_project_0, "/src/tsconfig.json"],
[Diagnostics.Updating_unchanged_output_timestamps_of_project_0, "/src/tsconfig.json"]
]
},
baselineOnly: true,
verifyDiagnostics: true
},
],
});
});
}
@@ -10,7 +10,7 @@ namespace ts {
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 host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/no-references"], { dry: false, force: false, verbose: false });
host.clearDiagnostics();
@@ -26,7 +26,7 @@ namespace ts {
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 host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/with-references"], { dry: false, force: false, verbose: false });
host.clearDiagnostics();
@@ -17,7 +17,7 @@ namespace ts {
before(() => {
const fs = new vfs.FileSystem(false);
host = new fakes.SolutionBuilderHost(fs);
host = fakes.SolutionBuilderHost.create(fs);
writeProjects(fs, ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"], deps);
});
+107 -201
View File
@@ -113,9 +113,11 @@ interface Symbol {
}
`;
/**
* Load project from disk into /src folder
*/
export function loadProjectFromDisk(
root: string,
time?: vfs.FileSystemOptions["time"],
libContentToAppend?: string
): vfs.FileSystem {
const resolver = vfs.createResolver(Harness.IO);
@@ -125,22 +127,22 @@ interface Symbol {
},
cwd: "/",
meta: { defaultLibLocation: "/lib" },
time
});
addLibAndMakeReadonly(fs, libContentToAppend);
return fs;
}
/**
* All the files must be in /src
*/
export function loadProjectFromFiles(
files: vfs.FileSet,
time?: vfs.FileSystemOptions["time"],
libContentToAppend?: string
): vfs.FileSystem {
const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
files,
cwd: "/",
meta: { defaultLibLocation: "/lib" },
time
});
addLibAndMakeReadonly(fs, libContentToAppend);
return fs;
@@ -152,6 +154,26 @@ interface Symbol {
fs.makeReadonly();
}
/**
* Gets the FS mountuing existing fs's /src and /lib folder
*/
export function getFsWithTime(baseFs: vfs.FileSystem) {
const { time, tick } = getTime();
const host = new fakes.System(baseFs) as any as vfs.FileSystemResolverHost;
host.getWorkspaceRoot = notImplemented;
const resolver = vfs.createResolver(host);
const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
files: {
["/src"]: new vfs.Mount("/src", resolver),
["/lib"]: new vfs.Mount("/lib", resolver)
},
cwd: "/",
meta: { defaultLibLocation: "/lib" },
time
});
return { fs, time, tick };
}
export function verifyOutputsPresent(fs: vfs.FileSystem, outputs: readonly string[]) {
for (const output of outputs) {
assert(fs.existsSync(output), `Expect file ${output} to exist`);
@@ -164,9 +186,10 @@ interface Symbol {
}
}
function generateSourceMapBaselineFiles(fs: vfs.FileSystem, mapFileNames: readonly string[]) {
for (const mapFile of mapFileNames) {
if (!fs.existsSync(mapFile)) continue;
export function generateSourceMapBaselineFiles(fs: vfs.FileSystem, mapFileNames: Iterator<string>) {
while (true) {
const { value: mapFile, done } = mapFileNames.next();
if (done) break;
const text = Harness.SourceMapRecorder.getSourceMapRecordWithVFS(fs, mapFile);
fs.writeFileSync(`${mapFile}.baseline.txt`, text);
}
@@ -229,234 +252,117 @@ interface Symbol {
}
}
interface BuildInput {
fs: vfs.FileSystem;
tick: () => void;
rootNames: readonly string[];
expectedMapFileNames?: readonly string[];
expectedBuildInfoFilesForSectionBaselines?: readonly 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);
export function baselineBuildInfo(
configs: readonly ParsedCommandLine[],
fs: vfs.FileSystem,
writtenFiles: Map<true>
) {
let expectedBuildInfoFiles: BuildInfoSectionBaselineFiles[] | undefined;
for (const { options } of configs) {
const out = options.outFile || options.out;
if (out) {
const { jsFilePath, declarationFilePath, buildInfoPath } = getOutputPathsForBundle(options, /*forceDts*/ false);
if (buildInfoPath && writtenFiles.has(buildInfoPath)) {
(expectedBuildInfoFiles || (expectedBuildInfoFiles = [])).push(
[buildInfoPath, jsFilePath, declarationFilePath]
);
}
}
return originalReadFile.call(host, path);
};
builder.build();
if (expectedMapFileNames) 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);
// eslint-disable-next-line no-null/no-null
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: readonly string[], ...filesWithTwoReadCalls: string[]) {
const map = arrayToMap(filesReadOnce, identity, () => 1);
for (const fileWithTwoReadCalls of filesWithTwoReadCalls) {
map.set(fileWithTwoReadCalls, 2);
}
return map;
if (expectedBuildInfoFiles) generateBuildInfoSectionBaselineFiles(fs, expectedBuildInfoFiles);
}
export interface ExpectedBuildOutput {
expectedDiagnostics?: readonly fakes.ExpectedDiagnostic[];
expectedReadFiles?: ReadonlyMap<number>;
}
export interface BuildState extends ExpectedBuildOutput {
export interface TscIncremental {
buildKind: BuildKind;
modifyFs: (fs: vfs.FileSystem) => void;
subScenario?: string;
}
export interface VerifyTsBuildInput {
scenario: string;
projFs: () => vfs.FileSystem;
time: () => number;
tick: () => void;
proj: string;
rootNames: readonly string[];
/** map file names to generate baseline of */
expectedMapFileNames?: readonly string[];
expectedBuildInfoFilesForSectionBaselines?: readonly BuildInfoSectionBaselineFiles[];
lastProjectOutput: string;
initialBuild: BuildState;
outputFiles?: readonly string[];
incrementalDtsChangedBuild?: BuildState;
incrementalDtsUnchangedBuild?: BuildState;
incrementalHeaderChangedBuild?: BuildState;
baselineOnly?: true;
verifyDiagnostics?: true;
export interface VerifyTsBuildInput extends TscCompile {
incrementalScenarios: TscIncremental[];
}
export function verifyTsbuildOutput({
scenario, projFs, time, tick, proj, rootNames, outputFiles, baselineOnly, verifyDiagnostics,
expectedMapFileNames, expectedBuildInfoFilesForSectionBaselines, lastProjectOutput,
initialBuild, incrementalDtsChangedBuild, incrementalDtsUnchangedBuild, incrementalHeaderChangedBuild
export function verifyTscIncrementalEdits({
subScenario, fs, scenario, commandLineArgs,
baselineSourceMap, modifyFs, baselineReadFileCalls,
incrementalScenarios
}: VerifyTsBuildInput) {
describe(`tsc --b ${proj}:: ${scenario}`, () => {
let fs: vfs.FileSystem;
let actualReadFileMap: Map<number>;
let firstBuildTime: number;
let host: fakes.SolutionBuilderHost;
describe(`tsc --b ${scenario}:: ${subScenario}`, () => {
let tick: () => void;
let sys: TscCompileSystem;
before(() => {
const result = build({
fs: projFs().shadow(),
tick,
rootNames,
expectedMapFileNames,
expectedBuildInfoFilesForSectionBaselines,
modifyFs: initialBuild.modifyFs,
let baseFs: vfs.FileSystem;
({ fs: baseFs, tick } = getFsWithTime(fs()));
sys = tscCompile({
scenario,
subScenario,
fs: () => baseFs.makeReadonly(),
commandLineArgs,
modifyFs: fs => {
if (modifyFs) modifyFs(fs);
tick();
},
baselineSourceMap,
baselineReadFileCalls
});
({ fs, actualReadFileMap, host } = result);
firstBuildTime = time();
Debug.assert(!!incrementalScenarios.length, `${scenario}/${subScenario}:: No incremental scenarios, you probably want to use verifyTsc instead.`);
});
after(() => {
fs = undefined!;
actualReadFileMap = undefined!;
host = undefined!;
sys = undefined!;
tick = undefined!;
});
describe("initialBuild", () => {
if (!baselineOnly || verifyDiagnostics) {
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));
});
}
verifyTscBaseline(() => sys);
});
function incrementalBuild(subScenario: string, incrementalModifyFs: (fs: vfs.FileSystem) => void, incrementalExpectedDiagnostics: readonly fakes.ExpectedDiagnostic[] | undefined, incrementalExpectedReadFiles: ReadonlyMap<number> | undefined) {
describe(subScenario, () => {
let newFs: vfs.FileSystem;
let actualReadFileMap: Map<number>;
let host: fakes.SolutionBuilderHost;
let beforeBuildTime: number;
let afterBuildTime: number;
for (const { buildKind, modifyFs, subScenario: incrementalSubScenario } of incrementalScenarios) {
describe(incrementalSubScenario || buildKind, () => {
let newSys: TscCompileSystem;
before(() => {
beforeBuildTime = fs.statSync(lastProjectOutput).mtimeMs;
Debug.assert(buildKind !== BuildKind.Initial, "Incremental edit cannot be initial compilation");
tick();
newFs = fs.shadow();
tick();
({ actualReadFileMap, host } = build({
fs: newFs,
tick,
rootNames,
expectedMapFileNames,
expectedBuildInfoFilesForSectionBaselines,
modifyFs: incrementalModifyFs,
}));
afterBuildTime = newFs.statSync(lastProjectOutput).mtimeMs;
newSys = tscCompile({
scenario,
subScenario: incrementalSubScenario || subScenario,
buildKind,
fs: () => sys.vfs,
commandLineArgs,
modifyFs: fs => {
tick();
modifyFs(fs);
tick();
},
baselineSourceMap,
baselineReadFileCalls
});
});
after(() => {
newFs = undefined!;
actualReadFileMap = undefined!;
host = undefined!;
newSys = undefined!;
});
it("verify build output times", () => {
assert.equal(beforeBuildTime, firstBuildTime, "First build timestamp is correct");
assert.equal(afterBuildTime, time(), "Second build timestamp is correct");
});
if (!baselineOnly || verifyDiagnostics) {
it(`verify diagnostics`, () => {
host.assertDiagnosticMessages(...(incrementalExpectedDiagnostics || emptyArray));
});
}
else {
// Build should pass without errors if not verifying diagnostics
it(`verify no errors`, () => {
host.assertErrors(/*empty*/);
});
}
it(`Generates files matching the baseline`, () => {
generateBaseline(newFs, proj, scenario, subScenario, fs);
});
if (!baselineOnly) {
it("verify readFile calls", () => {
verifyReadFileCalls(actualReadFileMap, Debug.assertDefined(incrementalExpectedReadFiles));
});
}
verifyTscBaseline(() => newSys);
it(`Verify emit output file text is same when built clean`, () => {
const expectedOutputFiles = Debug.assertDefined(outputFiles);
const { fs } = build({
fs: newFs.shadow(),
tick,
rootNames,
const sys = tscCompile({
scenario,
subScenario,
fs: () => newSys.vfs,
commandLineArgs,
modifyFs: fs => {
tick();
// Delete output files
for (const outputFile of expectedOutputFiles) {
if (fs.existsSync(outputFile)) {
fs.rimrafSync(outputFile);
}
}
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, commandLineArgs, { clean: true });
builder.clean();
},
});
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;
for (const outputFile of arrayFrom(sys.writtenFiles.keys())) {
const expectedText = sys.readFile(outputFile);
const actualText = newSys.readFile(outputFile);
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
);
}
});
}
@@ -1,48 +1,59 @@
namespace ts {
describe("unittests:: tsbuild:: inferredTypeFromTransitiveModule::", () => {
let projFs: vfs.FileSystem;
const { time, tick } = getTime();
before(() => {
projFs = loadProjectFromDisk("tests/projects/inferredTypeFromTransitiveModule", time);
projFs = loadProjectFromDisk("tests/projects/inferredTypeFromTransitiveModule");
});
after(() => {
projFs = undefined!;
});
verifyTsbuildOutput({
scenario: "inferred type from transitive module",
projFs: () => projFs,
time,
tick,
proj: "inferredTypeFromTransitiveModule",
rootNames: ["/src"],
lastProjectOutput: `/src/obj/index.js`,
outputFiles: [
"/src/obj/bar.js", "/src/obj/bar.d.ts",
"/src/obj/bundling.js", "/src/obj/bundling.d.ts",
"/src/obj/lazyIndex.js", "/src/obj/lazyIndex.d.ts",
"/src/obj/index.js", "/src/obj/index.d.ts",
"/src/obj/tsconfig.tsbuildinfo"
],
initialBuild: {
modifyFs: noop,
expectedDiagnostics: [
getExpectedDiagnosticForProjectsInBuild("src/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/tsconfig.json", "src/obj/bar.js"],
[Diagnostics.Building_project_0, "/src/tsconfig.json"]
]
verifyTscIncrementalEdits({
scenario: "inferredTypeFromTransitiveModule",
subScenario: "inferred type from transitive module",
fs: () => projFs,
commandLineArgs: ["--b", "/src", "--verbose"],
incrementalScenarios: [{
buildKind: BuildKind.IncrementalDtsChange,
modifyFs: changeBarParam,
}],
});
verifyTscIncrementalEdits({
subScenario: "inferred type from transitive module with isolatedModules",
fs: () => projFs,
scenario: "inferredTypeFromTransitiveModule",
commandLineArgs: ["--b", "/src", "--verbose"],
modifyFs: changeToIsolatedModules,
incrementalScenarios: [{
buildKind: BuildKind.IncrementalDtsChange,
modifyFs: changeBarParam
}]
});
verifyTscIncrementalEdits({
scenario: "inferredTypeFromTransitiveModule",
subScenario: "reports errors in files affected by change in signature with isolatedModules",
fs: () => projFs,
commandLineArgs: ["--b", "/src", "--verbose"],
modifyFs: fs => {
changeToIsolatedModules(fs);
appendText(fs, "/src/lazyIndex.ts", `
import { default as bar } from './bar';
bar("hello");`);
},
incrementalDtsChangedBuild: {
modifyFs: fs => replaceText(fs, "/src/bar.ts", "param: string", ""),
expectedDiagnostics: [
getExpectedDiagnosticForProjectsInBuild("src/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/tsconfig.json", "src/obj/bar.js", "src/bar.ts"],
[Diagnostics.Building_project_0, "/src/tsconfig.json"],
[Diagnostics.Updating_unchanged_output_timestamps_of_project_0, "/src/tsconfig.json"]
]
},
baselineOnly: true,
verifyDiagnostics: true
incrementalScenarios: [{
buildKind: BuildKind.IncrementalDtsChange,
modifyFs: changeBarParam
}]
});
});
function changeToIsolatedModules(fs: vfs.FileSystem) {
replaceText(fs, "/src/tsconfig.json", `"incremental": true`, `"incremental": true, "isolatedModules": true`);
}
function changeBarParam(fs: vfs.FileSystem) {
replaceText(fs, "/src/bar.ts", "param: string", "");
}
}
@@ -1,46 +1,22 @@
namespace ts {
describe("unittests:: tsbuild:: lateBoundSymbol:: interface is merged and contains late bound member", () => {
let projFs: vfs.FileSystem;
const { time, tick } = getTime();
before(() => {
projFs = loadProjectFromDisk("tests/projects/lateBoundSymbol", time);
projFs = loadProjectFromDisk("tests/projects/lateBoundSymbol");
});
after(() => {
projFs = undefined!; // Release the contents
});
verifyTsbuildOutput({
scenario: "interface is merged and contains late bound member",
projFs: () => projFs,
time,
tick,
proj: "lateBoundSymbol",
rootNames: ["/src/tsconfig.json"],
lastProjectOutput: "/src/src/main.js",
outputFiles: [
"/src/src/hkt.js",
"/src/src/main.js",
"/src/tsconfig.tsbuildinfo",
],
initialBuild: {
modifyFs: noop,
expectedDiagnostics: [
getExpectedDiagnosticForProjectsInBuild("src/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, "src/tsconfig.json", "src/src/hkt.js"],
[Diagnostics.Building_project_0, "/src/tsconfig.json"]
]
},
incrementalDtsUnchangedBuild: {
verifyTscIncrementalEdits({
subScenario: "interface is merged and contains late bound member",
fs: () => projFs,
scenario: "lateBoundSymbol",
commandLineArgs: ["--b", "/src/tsconfig.json", "--verbose"],
incrementalScenarios: [{
buildKind: BuildKind.IncrementalDtsUnchanged,
modifyFs: fs => replaceText(fs, "/src/src/main.ts", "const x = 10;", ""),
expectedDiagnostics: [
getExpectedDiagnosticForProjectsInBuild("src/tsconfig.json"),
[Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, "src/tsconfig.json", "src/src/hkt.js", "src/src/main.ts"],
[Diagnostics.Building_project_0, "/src/tsconfig.json"],
[Diagnostics.Updating_unchanged_output_timestamps_of_project_0, "/src/tsconfig.json"]
]
},
baselineOnly: true,
verifyDiagnostics: true
}]
});
});
}
@@ -3,7 +3,7 @@ namespace ts {
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 host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tsconfig.json"], {});
builder.build();
host.assertDiagnosticMessages(
@@ -1,16 +1,16 @@
namespace ts {
// https://github.com/microsoft/TypeScript/issues/31696
describe("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers to referenced projects resolve correctly", () => {
let projFs: vfs.FileSystem;
const { time, tick } = getTime();
before(() => {
projFs = loadProjectFromFiles({
"/src/common/nominal.ts": utils.dedent`
verifyTsc({
scenario: "moduleSpecifiers",
subScenario: `synthesized module specifiers resolve correctly`,
fs: () => loadProjectFromFiles({
"/src/solution/common/nominal.ts": utils.dedent`
export declare type Nominal<T, Name extends string> = T & {
[Symbol.species]: Name;
};
`,
"/src/common/tsconfig.json": utils.dedent`
"/src/solution/common/tsconfig.json": utils.dedent`
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
@@ -18,12 +18,12 @@ namespace ts {
},
"include": ["nominal.ts"]
}`,
"/src/sub-project/index.ts": utils.dedent`
"/src/solution/sub-project/index.ts": utils.dedent`
import { Nominal } from '../common/nominal';
export type MyNominal = Nominal<string, 'MyNominal'>;
`,
"/src/sub-project/tsconfig.json": utils.dedent`
"/src/solution/sub-project/tsconfig.json": utils.dedent`
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
@@ -34,7 +34,7 @@ namespace ts {
],
"include": ["./index.ts"]
}`,
"/src/sub-project-2/index.ts": utils.dedent`
"/src/solution/sub-project-2/index.ts": utils.dedent`
import { MyNominal } from '../sub-project/index';
const variable = {
@@ -45,7 +45,7 @@ namespace ts {
return 'key';
}
`,
"/src/sub-project-2/tsconfig.json": utils.dedent`
"/src/solution/sub-project-2/tsconfig.json": utils.dedent`
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
@@ -56,7 +56,7 @@ namespace ts {
],
"include": ["./index.ts"]
}`,
"/src/tsconfig.json": utils.dedent`
"/src/solution/tsconfig.json": utils.dedent`
{
"compilerOptions": {
"composite": true
@@ -67,7 +67,7 @@ namespace ts {
],
"include": []
}`,
"/tsconfig.base.json": utils.dedent`
"/src/tsconfig.base.json": utils.dedent`
{
"compilerOptions": {
"skipLibCheck": true,
@@ -75,32 +75,17 @@ namespace ts {
"outDir": "lib",
}
}`,
"/tsconfig.json": utils.dedent`{
"/src/tsconfig.json": utils.dedent`{
"compilerOptions": {
"composite": true
},
"references": [
{ "path": "./src" }
{ "path": "./solution" }
],
"include": []
}`
}, time, symbolLibContent);
});
after(() => {
projFs = undefined!;
});
verifyTsbuildOutput({
scenario: `synthesized module specifiers resolve correctly`,
projFs: () => projFs,
time,
tick,
proj: "moduleSpecifiers",
rootNames: ["/"],
lastProjectOutput: `/src/lib/index.d.ts`,
initialBuild: {
modifyFs: noop,
},
baselineOnly: true
}, symbolLibContent),
commandLineArgs: ["-b", "/src", "--verbose"]
});
});
}
+80 -272
View File
@@ -55,21 +55,7 @@ namespace 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: readonly 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],
@@ -84,247 +70,78 @@ namespace ts {
[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],
]
);
let dtsChangedExpectedDiagnostics: readonly 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],
],
outputFiles[project.first][ext.dts], // dts changes so once read old content, and once new (to emit third)
);
let dtsChangedExpectedDiagnosticsDependOrdered: readonly 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: readonly 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],
]
);
let dtsUnchangedExpectedDiagnosticsDependOrdered: readonly 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);
outFileFs = loadProjectFromDisk("tests/projects/outfile-concat");
});
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, baseOptions?: BuildOptions) {
return ts.createSolutionBuilder(host, ["/src/third"], { dry: false, force: false, verbose: true, ...(baseOptions || {}) });
}
function getInitialExpectedReadFiles(additionalSourceFiles?: readonly 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)
return value;
}
function getDtsChangedReadFiles(dependOrdered?: boolean, additionalSourceFiles?: readonly 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?: readonly 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;
subScenario: string;
modifyFs?: (fs: vfs.FileSystem) => void;
modifyAgainFs?: (fs: vfs.FileSystem) => void;
additionalSourceFiles?: readonly string[];
expectedBuildInfoFilesForSectionBaselines?: readonly BuildInfoSectionBaselineFiles[];
dependOrdered?: true;
ignoreDtsChanged?: true;
ignoreDtsUnchanged?: true;
baselineOnly?: true;
}
function verifyOutFileScenario({
scenario,
subScenario,
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,
lastProjectOutput: outputFiles[project.third][ext.js],
initialBuild: {
modifyFs,
expectedDiagnostics: initialExpectedDiagnostics,
expectedReadFiles: initialExpectedReadFiles
},
incrementalDtsChangedBuild: !ignoreDtsChanged ? {
const incrementalScenarios: TscIncremental[] = [];
if (!ignoreDtsChanged) {
incrementalScenarios.push({
buildKind: BuildKind.IncrementalDtsChange,
modifyFs: fs => replaceText(fs, relSources[project.first][source.ts][part.one], "Hello", "Hola"),
expectedDiagnostics: dependOrdered ?
dtsChangedExpectedDiagnosticsDependOrdered :
dtsChangedExpectedDiagnostics,
expectedReadFiles: dtsChangedReadFiles
} : undefined,
incrementalDtsUnchangedBuild: !ignoreDtsUnchanged ? {
});
}
if (!ignoreDtsUnchanged) {
incrementalScenarios.push({
buildKind: BuildKind.IncrementalDtsUnchanged,
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
});
});
}
if (modifyAgainFs) {
incrementalScenarios.push({
buildKind: BuildKind.IncrementalHeadersChange,
modifyFs: modifyAgainFs
});
}
const input: VerifyTsBuildInput = {
subScenario,
fs: () => outFileFs,
scenario: "outfile-concat",
commandLineArgs: ["--b", "/src/third", "--verbose"],
baselineSourceMap: true,
modifyFs,
baselineReadFileCalls: !baselineOnly,
incrementalScenarios,
};
return incrementalScenarios.length ?
verifyTscIncrementalEdits(input) :
verifyTsc(input);
}
// Verify initial + incremental edits
verifyOutFileScenario({
scenario: "baseline sectioned sourcemaps",
modifyFs: noop
subScenario: "baseline sectioned sourcemaps",
});
// Verify baseline with build info + dts unChanged
verifyOutFileScenario({
scenario: "when final project is not composite but uses project references",
subScenario: "when final project is not composite but uses project references",
modifyFs: fs => replaceText(fs, sources[project.third][source.config], `"composite": true,`, ""),
ignoreDtsChanged: true,
baselineOnly: true
@@ -332,7 +149,7 @@ namespace ts {
// Verify baseline with build info
verifyOutFileScenario({
scenario: "when final project is not composite but incremental",
subScenario: "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,
@@ -341,14 +158,9 @@ namespace ts {
// Verify baseline with build info
verifyOutFileScenario({
scenario: "when final project specifies tsBuildInfoFile",
subScenario: "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
@@ -361,7 +173,7 @@ namespace ts {
...outputFiles[project.second],
...outputFiles[project.third]
];
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host);
builder.build();
host.assertDiagnosticMessages(...initialExpectedDiagnostics);
@@ -377,13 +189,13 @@ namespace ts {
});
it("verify buildInfo absence results in new build", () => {
const fs = outFileFs.shadow();
const { fs, tick } = getFsWithTime(outFileFs);
const expectedOutputs = [
...outputFiles[project.first],
...outputFiles[project.second],
...outputFiles[project.third]
];
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
let builder = createSolutionBuilder(host);
builder.build();
host.assertDiagnosticMessages(...initialExpectedDiagnostics);
@@ -391,7 +203,11 @@ namespace ts {
verifyOutputsPresent(fs, expectedOutputs);
// Delete bundle info
host.clearDiagnostics();
tick();
host.deleteFile(outputFiles[project.first][ext.buildinfo]);
tick();
builder = createSolutionBuilder(host);
builder.build();
host.assertDiagnosticMessages(
@@ -407,7 +223,7 @@ namespace ts {
it("verify that if incremental is set to false, tsbuildinfo is not generated", () => {
const fs = outFileFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
replaceText(fs, sources[project.third][source.config], `"composite": true,`, "");
const builder = createSolutionBuilder(host);
builder.build();
@@ -418,14 +234,16 @@ namespace ts {
});
it("rebuilds completely when version in tsbuildinfo doesnt match ts version", () => {
const fs = outFileFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const { fs, tick } = getFsWithTime(outFileFs);
const host = fakes.SolutionBuilderHost.create(fs);
let builder = createSolutionBuilder(host);
builder.build();
host.assertDiagnosticMessages(...initialExpectedDiagnostics);
host.clearDiagnostics();
tick();
builder = createSolutionBuilder(host);
changeCompilerVersion(host);
tick();
builder.build();
host.assertDiagnosticMessages(
getExpectedDiagnosticForProjectsInBuild(relSources[project.first][source.config], relSources[project.second][source.config], relSources[project.third][source.config]),
@@ -439,12 +257,12 @@ namespace ts {
});
it("rebuilds completely when command line incremental flag changes between non dts changes", () => {
const fs = outFileFs.shadow();
const { fs, tick } = getFsWithTime(outFileFs);
// Make non composite third project
replaceText(fs, sources[project.third][source.config], `"composite": true,`, "");
// Build with command line incremental
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
let builder = createSolutionBuilder(host, { incremental: true });
builder.build();
host.assertDiagnosticMessages(...initialExpectedDiagnostics);
@@ -483,7 +301,7 @@ namespace ts {
it("builds till project specified", () => {
const fs = outFileFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, { verbose: false });
const result = builder.build(sources[project.second][source.config]);
host.assertDiagnosticMessages(/*empty*/);
@@ -496,7 +314,7 @@ namespace ts {
it("cleans till project specified", () => {
const fs = outFileFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, { verbose: false });
builder.build();
const result = builder.clean(sources[project.second][source.config]);
@@ -513,7 +331,7 @@ namespace ts {
describe("Prologues", () => {
// Verify initial + incremental edits
verifyOutFileScenario({
scenario: "strict in all projects",
subScenario: "strict in all projects",
modifyFs: fs => {
enableStrict(fs, sources[project.first][source.config]);
enableStrict(fs, sources[project.second][source.config]);
@@ -524,7 +342,7 @@ namespace ts {
// Verify ignore dtsChanged
verifyOutFileScenario({
scenario: "strict in one dependency",
subScenario: "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,
@@ -533,7 +351,7 @@ namespace ts {
// Verify initial + incremental edits - sourcemap verification
verifyOutFileScenario({
scenario: "multiple prologues in all projects",
subScenario: "multiple prologues in all projects",
modifyFs: fs => {
enableStrict(fs, sources[project.first][source.config]);
addTestPrologue(fs, sources[project.first][source.ts][part.one], `"myPrologue"`);
@@ -549,7 +367,7 @@ namespace ts {
// Verify ignore dtsChanged
verifyOutFileScenario({
scenario: "multiple prologues in different projects",
subScenario: "multiple prologues in different projects",
modifyFs: fs => {
enableStrict(fs, sources[project.first][source.config]);
addTestPrologue(fs, sources[project.second][source.ts][part.one], `"myPrologue"`);
@@ -567,7 +385,7 @@ namespace ts {
// changes declaration because its emitted in .d.ts file
// Verify initial + incremental edits
verifyOutFileScenario({
scenario: "shebang in all projects",
subScenario: "shebang in all projects",
modifyFs: fs => {
addShebang(fs, "first", "first_PART1");
addShebang(fs, "first", "first_part2");
@@ -578,7 +396,7 @@ namespace ts {
// Verify ignore dtsChanged
verifyOutFileScenario({
scenario: "shebang in only one dependency project",
subScenario: "shebang in only one dependency project",
modifyFs: fs => addShebang(fs, "second", "second_part1"),
ignoreDtsChanged: true,
baselineOnly: true
@@ -589,7 +407,7 @@ namespace ts {
describe("emitHelpers", () => {
// Verify initial + incremental edits
verifyOutFileScenario({
scenario: "emitHelpers in all projects",
subScenario: "emitHelpers in all projects",
modifyFs: fs => {
addRest(fs, "first", "first_PART1");
addRest(fs, "second", "second_part1");
@@ -600,7 +418,7 @@ namespace ts {
// Verify ignore dtsChanged
verifyOutFileScenario({
scenario: "emitHelpers in only one dependency project",
subScenario: "emitHelpers in only one dependency project",
modifyFs: fs => {
addStubFoo(fs, "first", "first_PART1");
addRest(fs, "second", "second_part1");
@@ -612,7 +430,7 @@ namespace ts {
// Verify ignore dtsChanged
verifyOutFileScenario({
scenario: "multiple emitHelpers in all projects",
subScenario: "multiple emitHelpers in all projects",
modifyFs: fs => {
addRest(fs, "first", "first_PART1");
addSpread(fs, "first", "first_part3");
@@ -628,7 +446,7 @@ namespace ts {
// Verify ignore dtsChanged
verifyOutFileScenario({
scenario: "multiple emitHelpers in different projects",
subScenario: "multiple emitHelpers in different projects",
modifyFs: fs => {
addRest(fs, "first", "first_PART1");
addSpread(fs, "second", "second_part1");
@@ -645,24 +463,18 @@ namespace ts {
// changes declaration because its emitted in .d.ts file
// Verify initial + incremental edits
verifyOutFileScenario({
scenario: "triple slash refs in all projects",
subScenario: "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",
subScenario: "triple slash refs in one project",
modifyFs: fs => addTripleSlashRef(fs, "second", "second_part1"),
additionalSourceFiles: [
getTripleSlashRef("second")
],
ignoreDtsChanged: true,
baselineOnly: true
});
@@ -721,14 +533,14 @@ ${internal} enum internalEnum { a, b, c }`);
// Verify initial + incremental edits
verifyOutFileScenario({
scenario: "stripInternal",
subScenario: "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",
subScenario: "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,
@@ -737,7 +549,7 @@ ${internal} enum internalEnum { a, b, c }`);
// Verify ignore dtsChanged
verifyOutFileScenario({
scenario: "stripInternal jsdoc style comment",
subScenario: "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,
@@ -746,7 +558,7 @@ ${internal} enum internalEnum { a, b, c }`);
// Verify ignore dtsChanged
verifyOutFileScenario({
scenario: "stripInternal jsdoc style with comments emit enabled",
subScenario: "stripInternal jsdoc style with comments emit enabled",
modifyFs: fs => stripInternalScenario(fs, /*removeCommentsDisabled*/ true, /*jsDocStyle*/ true),
ignoreDtsChanged: true,
baselineOnly: true
@@ -766,37 +578,33 @@ ${internal} enum internalEnum { a, b, c }`);
// Verify initial + incremental edits
verifyOutFileScenario({
scenario: "stripInternal when one-two-three are prepended in order",
subScenario: "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",
subScenario: "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",
subScenario: "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",
subScenario: "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
});
@@ -804,7 +612,7 @@ ${internal} enum internalEnum { a, b, c }`);
// only baseline
verifyOutFileScenario({
scenario: "stripInternal baseline when internal is inside another internal",
subScenario: "stripInternal baseline when internal is inside another internal",
modifyFs: fs => {
stripInternalOfThird(fs);
prependText(fs, sources[project.first][source.ts][part.one], `namespace ts {
@@ -843,7 +651,7 @@ ${internal} enum internalEnum { a, b, c }`);
// only baseline
verifyOutFileScenario({
scenario: "stripInternal when few members of enum are internal",
subScenario: "stripInternal when few members of enum are internal",
modifyFs: fs => {
stripInternalOfThird(fs);
prependText(fs, sources[project.first][source.ts][part.one], `enum TokenFlags {
@@ -883,7 +691,7 @@ ${internal} enum internalEnum { a, b, c }`);
// Verify ignore dtsChanged
verifyOutFileScenario({
scenario: "when source files are empty in the own file",
subScenario: "when source files are empty in the own file",
modifyFs: makeThirdEmptySourceFile,
ignoreDtsChanged: true,
baselineOnly: true
@@ -891,7 +699,7 @@ ${internal} enum internalEnum { a, b, c }`);
// only baseline
verifyOutFileScenario({
scenario: "declarationMap and sourceMap disabled",
subScenario: "declarationMap and sourceMap disabled",
modifyFs: fs => {
makeThirdEmptySourceFile(fs);
replaceText(fs, sources[project.third][source.config], `"composite": true,`, "");
@@ -921,7 +729,7 @@ ${internal} enum internalEnum { a, b, c }`);
replaceText(fs, sources[project.second][source.config], `"outFile": "../2/second-output.js",`, "");
replaceText(fs, sources[project.third][source.config], `"outFile": "./thirdjs/output/third-output.js",`, "");
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host);
builder.build();
host.assertDiagnosticMessages(
@@ -16,7 +16,7 @@ namespace ts {
"/src/dist/main/b.js", "/src/dist/main/b.d.ts"
];
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/src/main", "/src/src/other"], {});
builder.build();
host.assertDiagnosticMessages(/*empty*/);
@@ -34,7 +34,7 @@ namespace ts {
];
const fs = projFs.shadow();
replaceText(fs, "/src/tsconfig.base.json", `"rootDir": "./src/",`, "");
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/src/main"], { verbose: true });
builder.build();
host.assertDiagnosticMessages(
@@ -69,7 +69,7 @@ namespace ts {
fs.writeFileSync("/src/src/other/tsconfig.json", JSON.stringify({
compilerOptions: { composite: true, outDir: "../../dist/" },
}));
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/src/main"], { verbose: true });
builder.build();
host.assertDiagnosticMessages(
@@ -105,7 +105,7 @@ namespace ts {
fs.writeFileSync("/src/src/other/tsconfig.other.json", JSON.stringify({
compilerOptions: { composite: true, outDir: "../../dist/" },
}));
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/src/main/tsconfig.main.json"], { verbose: true });
builder.build();
host.assertDiagnosticMessages(
@@ -1,10 +1,9 @@
namespace ts {
describe("unittests:: tsbuild:: with resolveJsonModule option on project resolveJsonModuleAndComposite", () => {
let projFs: vfs.FileSystem;
const { time, tick } = getTime();
const allExpectedOutputs = ["/src/dist/src/index.js", "/src/dist/src/index.d.ts", "/src/dist/src/hello.json"];
before(() => {
projFs = loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite", time);
projFs = loadProjectFromDisk("tests/projects/resolveJsonModuleAndComposite");
});
after(() => {
@@ -17,7 +16,7 @@ namespace ts {
}
function verifyProjectWithResolveJsonModuleWithFs(fs: vfs.FileSystem, configFile: string, allExpectedOutputs: readonly string[], ...expectedDiagnosticMessages: fakes.ExpectedDiagnostic[]) {
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, [configFile], { dry: false, force: false, verbose: false });
builder.build();
host.assertDiagnosticMessages(...expectedDiagnosticMessages);
@@ -65,10 +64,10 @@ export default hello.hello`);
});
it("with resolveJsonModule and sourceMap", () => {
const fs = projFs.shadow();
const { fs, tick } = getFsWithTime(projFs);
const configFile = "src/tsconfig_withFiles.json";
replaceText(fs, configFile, `"composite": true,`, `"composite": true, "sourceMap": true,`);
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
let builder = createSolutionBuilder(host, [configFile], { verbose: true });
builder.build();
host.assertDiagnosticMessages(
@@ -88,10 +87,10 @@ export default hello.hello`);
});
it("with resolveJsonModule and without outDir", () => {
const fs = projFs.shadow();
const { fs, tick } = getFsWithTime(projFs);
const configFile = "src/tsconfig_withFiles.json";
replaceText(fs, configFile, `"outDir": "dist",`, "");
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
let builder = createSolutionBuilder(host, [configFile], { verbose: true });
builder.build();
host.assertDiagnosticMessages(
@@ -112,10 +111,9 @@ export default hello.hello`);
});
describe("unittests:: tsbuild:: with resolveJsonModule option on project importJsonFromProjectReference", () => {
const { time, tick } = getTime();
let projFs: vfs.FileSystem;
before(() => {
projFs = loadProjectFromDisk("tests/projects/importJsonFromProjectReference", time);
projFs = loadProjectFromDisk("tests/projects/importJsonFromProjectReference");
});
after(() => {
@@ -124,11 +122,11 @@ export default hello.hello`);
it("when importing json module from project reference", () => {
const expectedOutput = "/src/main/index.js";
const fs = projFs.shadow();
const { fs, tick } = getFsWithTime(projFs);
const configFile = "src/tsconfig.json";
const stringsConfigFile = "src/strings/tsconfig.json";
const mainConfigFile = "src/main/tsconfig.json";
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
let builder = createSolutionBuilder(host, [configFile], { verbose: true });
builder.build();
host.assertDiagnosticMessages(
+98 -427
View File
@@ -1,14 +1,13 @@
namespace ts {
describe("unittests:: tsbuild:: on 'sample1' project", () => {
let projFs: vfs.FileSystem;
const { time, tick } = getTime();
const testsOutputs = ["/src/tests/index.js", "/src/tests/index.d.ts", "/src/tests/tsconfig.tsbuildinfo"];
const logicOutputs = ["/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts", "/src/logic/tsconfig.tsbuildinfo"];
const coreOutputs = ["/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map", "/src/core/tsconfig.tsbuildinfo"];
const allExpectedOutputs = [...testsOutputs, ...logicOutputs, ...coreOutputs];
before(() => {
projFs = loadProjectFromDisk("tests/projects/sample1", time);
projFs = loadProjectFromDisk("tests/projects/sample1");
});
after(() => {
@@ -18,7 +17,7 @@ namespace ts {
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 host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
host.clearDiagnostics();
@@ -36,7 +35,7 @@ namespace ts {
references: [{ path: "../core" }]
}));
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], {});
builder.build();
host.assertDiagnosticMessages(/*empty*/);
@@ -52,7 +51,7 @@ namespace ts {
references: [{ path: "../core" }]
}));
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], {});
builder.build();
host.assertDiagnosticMessages(/*empty*/);
@@ -64,7 +63,7 @@ namespace ts {
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 host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/core"], { verbose: true });
builder.build();
host.assertDiagnosticMessages(
@@ -79,7 +78,7 @@ namespace ts {
describe("dry builds", () => {
it("doesn't write any files in a dry build", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: true, force: false, verbose: false });
builder.build();
host.assertDiagnosticMessages(
@@ -93,8 +92,8 @@ namespace ts {
});
it("indicates that it would skip builds during a dry build", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const { fs, tick } = getFsWithTime(projFs);
const host = fakes.SolutionBuilderHost.create(fs);
let builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
builder.build();
@@ -114,7 +113,7 @@ namespace ts {
describe("clean builds", () => {
it("removes all files it built", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
builder.build();
@@ -136,7 +135,7 @@ namespace ts {
it("cleans till project specified", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], {});
builder.build();
const result = builder.clean("/src/logic");
@@ -148,7 +147,7 @@ namespace ts {
it("cleaning project in not build order doesnt throw error", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], {});
builder.build();
const result = builder.clean("/src/logic2");
@@ -160,8 +159,8 @@ namespace ts {
describe("force builds", () => {
it("always builds under --force", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const { fs, time, tick } = getFsWithTime(projFs);
const host = fakes.SolutionBuilderHost.create(fs);
let builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: true, verbose: false });
builder.build();
@@ -187,8 +186,8 @@ namespace ts {
describe("can detect when and what to rebuild", () => {
function initializeWithBuild(opts?: BuildOptions) {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const { fs, tick } = getFsWithTime(projFs);
const host = fakes.SolutionBuilderHost.create(fs);
let builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true });
builder.build();
host.clearDiagnostics();
@@ -199,7 +198,7 @@ namespace ts {
it("Builds the project", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true });
builder.build();
host.assertDiagnosticMessages(
@@ -273,8 +272,8 @@ namespace ts {
});
it("does not rebuild if there is no program and bundle in the ts build info event if version doesnt match ts version", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs, /*options*/ undefined, /*setParentNodes*/ undefined, createAbstractBuilder);
const { fs, tick } = getFsWithTime(projFs);
const host = fakes.SolutionBuilderHost.create(fs, /*options*/ undefined, /*setParentNodes*/ undefined, createAbstractBuilder);
let builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true });
builder.build();
host.assertDiagnosticMessages(
@@ -329,10 +328,10 @@ namespace ts {
});
it("rebuilds when extended config file changes", () => {
const fs = projFs.shadow();
const { fs, tick } = getFsWithTime(projFs);
fs.writeFileSync("/src/tests/tsconfig.base.json", JSON.stringify({ compilerOptions: { target: "es3" } }));
replaceText(fs, "/src/tests/tsconfig.json", `"references": [`, `"extends": "./tsconfig.base.json", "references": [`);
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
let builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true });
builder.build();
host.assertDiagnosticMessages(
@@ -360,7 +359,7 @@ namespace ts {
it("builds till project specified", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], {});
const result = builder.build("/src/logic");
host.assertDiagnosticMessages(/*empty*/);
@@ -371,7 +370,7 @@ namespace ts {
it("building project in not build order doesnt throw error", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], {});
const result = builder.build("/src/logic2");
host.assertDiagnosticMessages(/*empty*/);
@@ -386,7 +385,7 @@ namespace ts {
}
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], {});
verifyBuildNextResult({
project: "/src/core/tsconfig.json" as ResolvedConfigFileName,
@@ -420,7 +419,7 @@ namespace ts {
it("building using buildReferencedProject", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { verbose: true });
builder.buildReferences("/src/tests");
host.assertDiagnosticMessages(
@@ -438,7 +437,7 @@ namespace ts {
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 host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: true });
// Induce an error in the middle project
@@ -462,8 +461,8 @@ namespace ts {
describe("project invalidation", () => {
it("invalidates projects correctly", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const { fs, time, tick } = getFsWithTime(projFs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
builder.build();
@@ -518,7 +517,7 @@ export class cNew {}`);
describe("lists files", () => {
it("listFiles", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { listFiles: true });
builder.build();
assert.deepEqual(host.traces, [
@@ -545,7 +544,7 @@ export class cNew {}`);
it("listEmittedFiles", () => {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { listEmittedFiles: true });
builder.build();
assert.deepEqual(host.traces, [
@@ -568,338 +567,73 @@ export class cNew {}`);
});
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"
],
lastProjectOutput: "/src/tests/index.js",
initialBuild,
incrementalDtsChangedBuild: {
modifyFs: fs => appendText(fs, "/src/core/index.ts", `
verifyTscIncrementalEdits({
subScenario: "sample",
fs: () => projFs,
scenario: "sample1",
commandLineArgs: ["--b", "/src/tests", "--verbose"],
baselineSourceMap: true,
baselineReadFileCalls: true,
incrementalScenarios: [
{
buildKind: BuildKind.IncrementalDtsChange,
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", `
},
{
buildKind: BuildKind.IncrementalDtsUnchanged,
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"
],
lastProjectOutput: "/src/tests/index.js",
initialBuild,
incrementalDtsChangedBuild: {
modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"declaration": true,`, `"declaration": true,
},
{
subScenario: "when logic config changes declaration dir",
buildKind: BuildKind.IncrementalDtsChange,
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"
],
lastProjectOutput: "/src/tests/index.js",
initialBuild: {
modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"composite": true,`, `"composite": true,
verifyTsc({
scenario: "sample1",
subScenario: "when logic specifies tsBuildInfoFile",
fs: () => projFs,
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",
]
commandLineArgs: ["--b", "/src/tests", "--verbose"],
baselineSourceMap: true,
baselineReadFileCalls: true
});
verifyTsbuildOutput({
scenario: "when declaration option changes",
projFs: () => projFs,
time,
tick,
proj: "sample1",
rootNames: ["/src/core"],
lastProjectOutput: "/src/core/index.js",
initialBuild: {
modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{
verifyTscIncrementalEdits({
subScenario: "when declaration option changes",
fs: () => projFs,
scenario: "sample1",
commandLineArgs: ["--b", "/src/core", "--verbose"],
modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{
"compilerOptions": {
"incremental": true,
"skipDefaultLibCheck": true
}
}`),
expectedDiagnostics: [
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"],
]
},
incrementalDtsChangedBuild: {
incrementalScenarios: [{
buildKind: BuildKind.IncrementalDtsChange,
modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"incremental": true,`, `"incremental": true, "declaration": true,`),
expectedDiagnostics: [
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.d.ts"],
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"]
]
},
outputFiles: [
"/src/core/anotherModule.js",
"/src/core/anotherModule.d.ts",
"/src/core/index.js",
"/src/core/index.d.ts",
"/src/core/tsconfig.tsbuildinfo",
],
baselineOnly: true,
verifyDiagnostics: true
}],
});
verifyTsbuildOutput({
scenario: "when target option changes",
projFs: () => projFs,
time,
tick,
proj: "sample1",
rootNames: ["/src/core"],
lastProjectOutput: "/src/core/index.js",
initialBuild: {
modifyFs: fs => {
fs.writeFileSync("/lib/lib.esnext.full.d.ts", `/// <reference no-default-lib="true"/>
verifyTscIncrementalEdits({
subScenario: "when target option changes",
fs: () => projFs,
scenario: "sample1",
commandLineArgs: ["--b", "/src/core", "--verbose"],
modifyFs: fs => {
fs.writeFileSync("/lib/lib.esnext.full.d.ts", `/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />`);
fs.writeFileSync("/lib/lib.esnext.d.ts", libContent);
fs.writeFileSync("/lib/lib.d.ts", `/// <reference no-default-lib="true"/>
fs.writeFileSync("/lib/lib.esnext.d.ts", libContent);
fs.writeFileSync("/lib/lib.d.ts", `/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />`);
fs.writeFileSync("/src/core/tsconfig.json", `{
fs.writeFileSync("/src/core/tsconfig.json", `{
"compilerOptions": {
"incremental": true,
"listFiles": true,
@@ -907,80 +641,36 @@ class someClass { }`),
"target": "esnext",
}
}`);
},
expectedDiagnostics: [
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"],
]
},
incrementalDtsChangedBuild: {
incrementalScenarios: [{
buildKind: BuildKind.IncrementalDtsChange,
modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", "esnext", "es5"),
expectedDiagnostics: [
getExpectedDiagnosticForProjectsInBuild("src/core/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/tsconfig.json"],
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"]
]
},
outputFiles: [
"/src/core/anotherModule.js",
"/src/core/anotherModule.d.ts",
"/src/core/index.js",
"/src/core/index.d.ts",
"/src/core/tsconfig.tsbuildinfo",
],
baselineOnly: true,
verifyDiagnostics: true
}],
});
verifyTsbuildOutput({
scenario: "when module option changes",
projFs: () => projFs,
time,
tick,
proj: "sample1",
rootNames: ["/src/core"],
lastProjectOutput: "/src/core/index.js",
initialBuild: {
modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{
verifyTscIncrementalEdits({
subScenario: "when module option changes",
fs: () => projFs,
scenario: "sample1",
commandLineArgs: ["--b", "/src/core", "--verbose"],
modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{
"compilerOptions": {
"incremental": true,
"module": "commonjs"
}
}`),
expectedDiagnostics: [
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"],
]
},
incrementalDtsChangedBuild: {
incrementalScenarios: [{
buildKind: BuildKind.IncrementalDtsChange,
modifyFs: fs => replaceText(fs, "/src/core/tsconfig.json", `"module": "commonjs"`, `"module": "amd"`),
expectedDiagnostics: [
getExpectedDiagnosticForProjectsInBuild("src/core/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/tsconfig.json"],
[Diagnostics.Building_project_0, "/src/core/tsconfig.json"]
]
},
outputFiles: [
"/src/core/anotherModule.js",
"/src/core/index.js",
"/src/core/tsconfig.tsbuildinfo",
],
baselineOnly: true,
verifyDiagnostics: true
}],
});
verifyTsbuildOutput({
scenario: "when esModuleInterop option changes",
projFs: () => projFs,
time,
tick,
proj: "sample1",
rootNames: ["/src/tests"],
lastProjectOutput: "/src/tests/index.js",
initialBuild: {
modifyFs: fs => fs.writeFileSync("/src/tests/tsconfig.json", `{
verifyTscIncrementalEdits({
subScenario: "when esModuleInterop option changes",
fs: () => projFs,
scenario: "sample1",
commandLineArgs: ["--b", "/src/tests", "--verbose"],
modifyFs: fs => fs.writeFileSync("/src/tests/tsconfig.json", `{
"references": [
{ "path": "../core" },
{ "path": "../logic" }
@@ -994,29 +684,10 @@ class someClass { }`),
"esModuleInterop": false
}
}`),
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"]
]
},
incrementalDtsChangedBuild: {
incrementalScenarios: [{
buildKind: BuildKind.IncrementalDtsChange,
modifyFs: fs => replaceText(fs, "/src/tests/tsconfig.json", `"esModuleInterop": false`, `"esModuleInterop": true`),
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_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/tsconfig.json"],
[Diagnostics.Building_project_0, "/src/tests/tsconfig.json"]
]
},
outputFiles: [],
baselineOnly: true,
verifyDiagnostics: true
}],
});
});
});
@@ -27,7 +27,7 @@ namespace ts {
function verifyBuild(modifyDiskLayout: (fs: vfs.FileSystem) => void, allExpectedOutputs: readonly string[], expectedFileTraces: readonly string[], ...expectedDiagnostics: fakes.ExpectedDiagnostic[]) {
const fs = projFs.shadow();
const host = new fakes.SolutionBuilderHost(fs);
const host = fakes.SolutionBuilderHost.create(fs);
modifyDiskLayout(fs);
const builder = createSolutionBuilder(host, ["/src/tsconfig.c.json"], { listFiles: true });
builder.build();
+112 -93
View File
@@ -2,18 +2,12 @@ namespace ts.tscWatch {
import projectsLocation = TestFSWithWatch.tsbuildProjectsLocation;
import getFilePathInProject = TestFSWithWatch.getTsBuildProjectFilePath;
import getFileFromProject = TestFSWithWatch.getTsBuildProjectFile;
type TsBuildWatchSystem = WatchedSystem & { writtenFiles: Map<true>; };
type TsBuildWatchSystem = TestFSWithWatch.TestServerHostTrackingWrittenFiles;
function createTsBuildWatchSystem(fileOrFolderList: readonly TestFSWithWatch.FileOrFolderOrSymLink[], params?: TestFSWithWatch.TestServerHostCreationParameters) {
const host = createWatchedSystem(fileOrFolderList, params) as TsBuildWatchSystem;
const originalWriteFile = host.writeFile;
host.writtenFiles = createMap<true>();
host.writeFile = (fileName, content) => {
originalWriteFile.call(host, fileName, content);
const path = host.toFullPath(fileName);
host.writtenFiles.set(path, true);
};
return host;
return TestFSWithWatch.changeToHostTrackingWrittenFiles(
createWatchedSystem(fileOrFolderList, params)
);
}
export function createSolutionBuilder(system: WatchedSystem, rootNames: readonly string[], defaultOptions?: BuildOptions) {
@@ -710,8 +704,8 @@ let x: string = 10;`);
const coreIndexDts = projectFileName(SubProject.core, "index.d.ts");
const coreAnotherModuleDts = projectFileName(SubProject.core, "anotherModule.d.ts");
const logicIndexDts = projectFileName(SubProject.logic, "index.d.ts");
const expectedWatchedFiles = () => [core[0], logic[0], ...tests, libFile].map(f => f.path).concat([coreIndexDts, coreAnotherModuleDts, logicIndexDts].map(f => f.toLowerCase()));
const expectedWatchedDirectoriesRecursive = projectSystem.getTypeRootsFromLocation(projectPath(SubProject.tests));
const expectedProjectFiles = () => [libFile, ...tests, ...logic.slice(1), ...core.slice(1, core.length - 1)].map(f => f.path);
const expectedProgramFiles = () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, logicIndexDts];
function createSolutionAndWatchMode() {
@@ -723,12 +717,19 @@ let x: string = 10;`);
}
function verifyWatches(host: TsBuildWatchSystem, withTsserver?: boolean) {
verifyWatchesOfProject(host, withTsserver ? expectedWatchedFiles().filter(f => f !== tests[1].path.toLowerCase()) : expectedWatchedFiles(), expectedWatchedDirectoriesRecursive);
verifyWatchesOfProject(
host,
withTsserver ?
[...core.slice(0, core.length - 1), ...logic, tests[0], libFile].map(f => f.path.toLowerCase()) :
[core[0], logic[0], ...tests, libFile].map(f => f.path).concat([coreIndexDts, coreAnotherModuleDts, logicIndexDts].map(f => f.toLowerCase())),
expectedWatchedDirectoriesRecursive
);
}
function verifyScenario(
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void,
expectedFilesAfterEdit: () => readonly string[]
expectedProgramFilesAfterEdit: () => readonly string[],
expectedProjectFilesAfterEdit: () => readonly string[]
) {
it("with tsc-watch", () => {
const { host, solutionBuilder, watch } = createSolutionAndWatchMode();
@@ -737,7 +738,7 @@ let x: string = 10;`);
host.checkTimeoutQueueLengthAndRun(1);
checkOutputErrorsIncremental(host, emptyArray);
checkProgramActualFiles(watch(), expectedFilesAfterEdit());
checkProgramActualFiles(watch(), expectedProgramFilesAfterEdit());
});
@@ -747,7 +748,7 @@ let x: string = 10;`);
edit(host, solutionBuilder);
host.checkTimeoutQueueLengthAndRun(2);
checkProjectActualFiles(service, tests[0].path, [tests[0].path, ...expectedFilesAfterEdit()]);
checkProjectActualFiles(service, tests[0].path, expectedProjectFilesAfterEdit());
});
}
@@ -777,7 +778,7 @@ function foo() {
// not ideal, but currently because of d.ts but no new file is written
// There will be timeout queued even though file contents are same
}, expectedProgramFiles);
}, expectedProgramFiles, expectedProjectFiles);
});
describe("non local edit in ts file, rebuilds in watch compilation", () => {
@@ -787,7 +788,7 @@ export function gfoo() {
}`);
solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath);
solutionBuilder.buildNextInvalidatedProject();
}, expectedProgramFiles);
}, expectedProgramFiles, expectedProjectFiles);
});
describe("change in project reference config file builds correctly", () => {
@@ -798,7 +799,7 @@ export function gfoo() {
}));
solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath, ConfigFileProgramReloadLevel.Full);
solutionBuilder.buildNextInvalidatedProject();
}, () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")]);
}, () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")], expectedProjectFiles);
});
});
@@ -888,7 +889,9 @@ export function gfoo() {
const aDts = dtsFile(multiFolder ? "a/index" : "a"), bDts = dtsFile(multiFolder ? "b/index" : "b");
const expectedFiles = [jsFile(multiFolder ? "a/index" : "a"), aDts, jsFile(multiFolder ? "b/index" : "b"), bDts, jsFile(multiFolder ? "c/index" : "c")];
const expectedProgramFiles = [cTs.path, libFile.path, aDts, refs.path, bDts];
const expectedProjectFiles = [cTs.path, libFile.path, aTs.path, refs.path, bTs.path];
const expectedWatchedFiles = expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase());
const expectedProjectWatchedFiles = expectedProjectFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase());
const expectedWatchedDirectories = multiFolder ? [
getProjectPath(project).toLowerCase() // watches for directories created for resolution of b
] : emptyArray;
@@ -926,22 +929,29 @@ export function gfoo() {
}
function verifyProject(host: TsBuildWatchSystem, service: projectSystem.TestProjectService, orphanInfos?: readonly string[]) {
verifyServerState(host, service, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos);
verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos });
}
function verifyServerState(
host: TsBuildWatchSystem,
service: projectSystem.TestProjectService,
expectedProgramFiles: readonly string[],
expectedWatchedFiles: readonly string[],
expectedWatchedDirectoriesRecursive: readonly string[],
orphanInfos?: readonly string[]) {
checkProjectActualFiles(service, cTsconfig.path, expectedProgramFiles.concat(cTsconfig.path));
const watchedFiles = expectedWatchedFiles.filter(f => f !== cTs.path.toLowerCase());
if (orphanInfos) {
interface VerifyServerState {
host: TsBuildWatchSystem;
service: projectSystem.TestProjectService;
expectedProjectFiles: readonly string[];
expectedProjectWatchedFiles: readonly string[];
expectedWatchedDirectoriesRecursive: readonly string[];
orphanInfos?: readonly string[];
}
function verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos }: VerifyServerState) {
checkProjectActualFiles(service, cTsconfig.path, expectedProjectFiles.concat(cTsconfig.path));
const watchedFiles = expectedProjectWatchedFiles.filter(f => f !== cTs.path.toLowerCase());
const actualOrphan = arrayFrom(mapDefinedIterator(
service.filenameToScriptInfo.values(),
v => v.containingProjects.length === 0 ? v.fileName : undefined
));
assert.equal(actualOrphan.length, orphanInfos ? orphanInfos.length : 0, `Orphans found: ${JSON.stringify(actualOrphan, /*replacer*/ undefined, " ")}`);
if (orphanInfos && orphanInfos.length) {
for (const orphan of orphanInfos) {
const info = service.getScriptInfoForPath(orphan as Path);
assert.isDefined(info);
assert.isDefined(info, `${orphan} expected to be present. Actual: ${JSON.stringify(actualOrphan, /*replacer*/ undefined, " ")}`);
assert.equal(info!.containingProjects.length, 0);
watchedFiles.push(orphan);
}
@@ -949,16 +959,20 @@ export function gfoo() {
verifyWatchesOfProject(host, watchedFiles, expectedWatchedDirectoriesRecursive, expectedWatchedDirectories);
}
function verifyScenario(
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void,
expectedEditErrors: readonly string[],
expectedProgramFiles: readonly string[],
expectedWatchedFiles: readonly string[],
expectedWatchedDirectoriesRecursive: readonly string[],
dependencies: readonly [string, readonly string[]][],
revert?: (host: TsBuildWatchSystem) => void,
orphanInfosAfterEdit?: readonly string[],
orphanInfosAfterRevert?: readonly string[]) {
interface VerifyScenario {
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void;
expectedEditErrors: readonly string[];
expectedProgramFiles: readonly string[];
expectedProjectFiles: readonly string[];
expectedWatchedFiles: readonly string[];
expectedProjectWatchedFiles: readonly string[];
expectedWatchedDirectoriesRecursive: readonly string[];
dependencies: readonly [string, readonly string[]][];
revert?: (host: TsBuildWatchSystem) => void;
orphanInfosAfterEdit?: readonly string[];
orphanInfosAfterRevert?: readonly string[];
}
function verifyScenario({ edit, expectedEditErrors, expectedProgramFiles, expectedProjectFiles, expectedWatchedFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, dependencies, revert, orphanInfosAfterEdit, orphanInfosAfterRevert }: VerifyScenario) {
it("with tsc-watch", () => {
const { host, solutionBuilder, watch } = createSolutionAndWatchMode();
@@ -985,7 +999,7 @@ export function gfoo() {
edit(host, solutionBuilder);
host.checkTimeoutQueueLengthAndRun(2);
verifyServerState(host, service, expectedProgramFiles, expectedWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfosAfterEdit);
verifyServerState({ host, service, expectedProjectFiles, expectedProjectWatchedFiles, expectedWatchedDirectoriesRecursive, orphanInfos: orphanInfosAfterEdit });
if (revert) {
revert(host);
@@ -1010,20 +1024,21 @@ export function gfoo() {
});
describe("non local edit updates the program and watch correctly", () => {
verifyScenario(
(host, solutionBuilder) => {
verifyScenario({
edit: (host, solutionBuilder) => {
// edit
host.writeFile(bTs.path, `${bTs.content}
export function gfoo() {
}`);
solutionBuilder.invalidateProject(bTsconfig.path.toLowerCase() as ResolvedConfigFilePath);
host.writeFile(bTs.path, `${bTs.content}\nexport function gfoo() {\n}`);
solutionBuilder.invalidateProject((bTsconfig.path.toLowerCase() as ResolvedConfigFilePath));
solutionBuilder.buildNextInvalidatedProject();
},
emptyArray,
expectedEditErrors: emptyArray,
expectedProgramFiles,
expectedProjectFiles,
expectedWatchedFiles,
expectedProjectWatchedFiles,
expectedWatchedDirectoriesRecursive,
defaultDependencies);
dependencies: defaultDependencies
});
});
describe("edit on config file", () => {
@@ -1032,30 +1047,32 @@ export function gfoo() {
path: getFilePathInProject(project, "nrefs/a.d.ts"),
content: refs.content
};
verifyScenario(
host => {
verifyScenario({
edit: host => {
const cTsConfigJson = JSON.parse(cTsconfig.content);
host.ensureFileOrFolder(nrefs);
cTsConfigJson.compilerOptions.paths = { "@ref/*": nrefsPath };
host.writeFile(cTsconfig.path, JSON.stringify(cTsConfigJson));
},
emptyArray,
expectedProgramFiles.map(nrefReplacer),
expectedWatchedFiles.map(nrefReplacer),
expectedWatchedDirectoriesRecursive.map(nrefReplacer),
[
expectedEditErrors: emptyArray,
expectedProgramFiles: expectedProgramFiles.map(nrefReplacer),
expectedProjectFiles: expectedProjectFiles.map(nrefReplacer),
expectedWatchedFiles: expectedWatchedFiles.map(nrefReplacer),
expectedProjectWatchedFiles: expectedProjectWatchedFiles.map(nrefReplacer),
expectedWatchedDirectoriesRecursive: expectedWatchedDirectoriesRecursive.map(nrefReplacer),
dependencies: [
[aDts, [aDts]],
[bDts, [bDts, aDts]],
[nrefs.path, [nrefs.path]],
[cTs.path, [cTs.path, nrefs.path, bDts]]
],
// revert the update
host => host.writeFile(cTsconfig.path, cTsconfig.content),
revert: host => host.writeFile(cTsconfig.path, cTsconfig.content),
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
[refs.path.toLowerCase()],
orphanInfosAfterEdit: [refs.path.toLowerCase()],
// AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
[nrefs.path.toLowerCase()]
);
orphanInfosAfterRevert: [nrefs.path.toLowerCase()]
});
});
describe("edit in referenced config file", () => {
@@ -1064,82 +1081,84 @@ export function gfoo() {
content: "export declare class A {}"
};
const expectedProgramFiles = [cTs.path, bDts, nrefs.path, refs.path, libFile.path];
const expectedProjectFiles = [cTs.path, bTs.path, nrefs.path, refs.path, libFile.path];
const [, ...expectedWatchedDirectoriesRecursiveWithoutA] = expectedWatchedDirectoriesRecursive; // Not looking in a folder for resolution in multi folder scenario
verifyScenario(
host => {
verifyScenario({
edit: host => {
const bTsConfigJson = JSON.parse(bTsconfig.content);
host.ensureFileOrFolder(nrefs);
bTsConfigJson.compilerOptions.paths = { "@ref/*": nrefsPath };
host.writeFile(bTsconfig.path, JSON.stringify(bTsConfigJson));
},
emptyArray,
expectedEditErrors: emptyArray,
expectedProgramFiles,
expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()),
(multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive).concat(getFilePathInProject(project, "nrefs").toLowerCase()),
[
expectedProjectFiles,
expectedWatchedFiles: expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()),
expectedProjectWatchedFiles: expectedProjectFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()),
expectedWatchedDirectoriesRecursive: (multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive).concat(getFilePathInProject(project, "nrefs").toLowerCase()),
dependencies: [
[nrefs.path, [nrefs.path]],
[bDts, [bDts, nrefs.path]],
[refs.path, [refs.path]],
[cTs.path, [cTs.path, refs.path, bDts]],
],
// revert the update
host => host.writeFile(bTsconfig.path, bTsconfig.content),
revert: host => host.writeFile(bTsconfig.path, bTsconfig.content),
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
[aDts.toLowerCase()],
orphanInfosAfterEdit: [aTs.path.toLowerCase()],
// AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
[nrefs.path.toLowerCase()]
);
orphanInfosAfterRevert: [nrefs.path.toLowerCase()]
});
});
describe("deleting referenced config file", () => {
const expectedProgramFiles = [cTs.path, bTs.path, refs.path, libFile.path];
const expectedWatchedFiles = expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path).map(s => s.toLowerCase());
const [, ...expectedWatchedDirectoriesRecursiveWithoutA] = expectedWatchedDirectoriesRecursive; // Not looking in a folder for resolution in multi folder scenario
// Resolutions should change now
// Should map to b.ts instead with options from our own config
verifyScenario(
host => host.deleteFile(bTsconfig.path),
[
verifyScenario({
edit: host => host.deleteFile(bTsconfig.path),
expectedEditErrors: [
`${multiFolder ? "c/tsconfig.json" : "tsconfig.c.json"}(9,21): error TS6053: File '/user/username/projects/transitiveReferences/${multiFolder ? "b" : "tsconfig.b.json"}' not found.\n`
],
expectedProgramFiles,
expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path).map(s => s.toLowerCase()),
multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive,
[
expectedProjectFiles: expectedProgramFiles,
expectedWatchedFiles,
expectedProjectWatchedFiles: expectedWatchedFiles,
expectedWatchedDirectoriesRecursive: multiFolder ? expectedWatchedDirectoriesRecursiveWithoutA : expectedWatchedDirectoriesRecursive,
dependencies: [
[bTs.path, [bTs.path, refs.path]],
[refs.path, [refs.path]],
[cTs.path, [cTs.path, refs.path, bTs.path]],
],
// revert the update
host => host.writeFile(bTsconfig.path, bTsconfig.content),
revert: host => host.writeFile(bTsconfig.path, bTsconfig.content),
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
[bDts.toLowerCase(), aDts.toLowerCase(), aTsconfig.path.toLowerCase()],
// AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
[bTs.path.toLowerCase()]
);
orphanInfosAfterEdit: [aTs.path.toLowerCase(), aTsconfig.path.toLowerCase()],
});
});
describe("deleting transitively referenced config file", () => {
verifyScenario(
host => host.deleteFile(aTsconfig.path),
[
verifyScenario({
edit: host => host.deleteFile(aTsconfig.path),
expectedEditErrors: [
`${multiFolder ? "b/tsconfig.json" : "tsconfig.b.json"}(10,21): error TS6053: File '/user/username/projects/transitiveReferences/${multiFolder ? "a" : "tsconfig.a.json"}' not found.\n`
],
expectedProgramFiles.map(s => s.replace(aDts, aTs.path)),
expectedWatchedFiles.map(s => s.replace(aDts.toLowerCase(), aTs.path.toLocaleLowerCase())),
expectedProgramFiles: expectedProgramFiles.map(s => s.replace(aDts, aTs.path)),
expectedProjectFiles,
expectedWatchedFiles: expectedWatchedFiles.map(s => s.replace(aDts.toLowerCase(), aTs.path.toLocaleLowerCase())),
expectedProjectWatchedFiles,
expectedWatchedDirectoriesRecursive,
[
dependencies: [
[aTs.path, [aTs.path]],
[bDts, [bDts, aTs.path]],
[refs.path, [refs.path]],
[cTs.path, [cTs.path, refs.path, bDts]],
],
// revert the update
host => host.writeFile(aTsconfig.path, aTsconfig.content),
// AfterEdit:: Extra watched files on server since the script infos arent deleted till next file open
[aDts.toLowerCase()],
// AfterRevert:: Extra watched files on server since the script infos arent deleted till next file open
[aTs.path.toLowerCase()]
);
revert: host => host.writeFile(aTsconfig.path, aTsconfig.content),
});
});
}
+243
View File
@@ -0,0 +1,243 @@
namespace ts {
export type TscCompileSystem = fakes.System & {
writtenFiles: Map<true>;
baseLine(): void;
};
function executeCommandLine(sys: TscCompileSystem, commandLineArgs: readonly string[]) {
if (isBuild(commandLineArgs)) {
return performBuild(sys, commandLineArgs.slice(1));
}
const reportDiagnostic = createDiagnosticReporter(sys);
const commandLine = parseCommandLine(commandLineArgs, path => sys.readFile(path));
if (commandLine.options.build) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_build_must_be_the_first_command_line_argument));
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
if (commandLine.errors.length > 0) {
commandLine.errors.forEach(reportDiagnostic);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
let configFileName: string | undefined;
if (commandLine.options.project) {
if (commandLine.fileNames.length !== 0) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line));
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
const fileOrDirectory = normalizePath(commandLine.options.project);
if (!fileOrDirectory /* current directory "." */ || sys.directoryExists(fileOrDirectory)) {
configFileName = combinePaths(fileOrDirectory, "tsconfig.json");
if (!sys.fileExists(configFileName)) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Cannot_find_a_tsconfig_json_file_at_the_specified_directory_Colon_0, commandLine.options.project));
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
}
else {
configFileName = fileOrDirectory;
if (!sys.fileExists(configFileName)) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_specified_path_does_not_exist_Colon_0, commandLine.options.project));
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
}
}
else if (commandLine.fileNames.length === 0) {
const searchPath = normalizePath(sys.getCurrentDirectory());
configFileName = findConfigFile(searchPath, sys.fileExists);
}
Debug.assert(commandLine.fileNames.length !== 0 || !!configFileName);
if (configFileName) {
const configParseResult = Debug.assertDefined(parseConfigFileWithSystem(configFileName, commandLine.options, sys, reportDiagnostic));
if (isIncrementalCompilation(configParseResult.options)) {
performIncrementalCompilation(sys, configParseResult);
}
else {
performCompilation(sys, configParseResult);
}
}
else {
if (isIncrementalCompilation(commandLine.options)) {
performIncrementalCompilation(sys, commandLine);
}
else {
performCompilation(sys, commandLine);
}
}
}
function createReportErrorSummary(sys: TscCompileSystem, options: CompilerOptions): ReportEmitErrorSummary | undefined {
return options.pretty ?
errorCount => sys.write(getErrorSummaryText(errorCount, sys.newLine)) :
undefined;
}
function performCompilation(sys: TscCompileSystem, config: ParsedCommandLine) {
const { fileNames, options, projectReferences } = config;
const reportDiagnostic = createDiagnosticReporter(sys, options.pretty);
const host = createCompilerHostWorker(options, /*setParentPos*/ undefined, sys);
const currentDirectory = host.getCurrentDirectory();
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, currentDirectory, getCanonicalFileName));
const program = createProgram({
rootNames: fileNames,
options,
projectReferences,
host,
configFileParsingDiagnostics: getConfigFileParsingDiagnostics(config)
});
const exitStatus = emitFilesAndReportErrorsAndGetExitStatus(
program,
reportDiagnostic,
s => sys.write(s + sys.newLine),
createReportErrorSummary(sys, options)
);
baselineBuildInfo([config], sys.vfs, sys.writtenFiles);
return sys.exit(exitStatus);
}
function performIncrementalCompilation(sys: TscCompileSystem, config: ParsedCommandLine) {
const reportDiagnostic = createDiagnosticReporter(sys, config.options.pretty);
const { options, fileNames, projectReferences } = config;
const exitCode = ts.performIncrementalCompilation({
system: sys,
rootNames: fileNames,
options,
configFileParsingDiagnostics: getConfigFileParsingDiagnostics(config),
projectReferences,
reportDiagnostic,
reportErrorSummary: createReportErrorSummary(sys, options),
});
baselineBuildInfo([config], sys.vfs, sys.writtenFiles);
return sys.exit(exitCode);
}
function performBuild(sys: TscCompileSystem, args: string[]) {
const { buildOptions, projects, errors } = parseBuildCommand(args);
const reportDiagnostic = createDiagnosticReporter(sys, buildOptions.pretty);
if (errors.length > 0) {
errors.forEach(reportDiagnostic);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
Debug.assert(projects.length !== 0);
const buildHost = createSolutionBuilderHost(
sys,
/*createProgram*/ undefined,
reportDiagnostic,
createBuilderStatusReporter(sys, buildOptions.pretty),
createReportErrorSummary(sys, buildOptions)
);
fakes.patchSolutionBuilderHost(buildHost, sys);
const builder = createSolutionBuilder(buildHost, projects, buildOptions);
const exitCode = buildOptions.clean ? builder.clean() : builder.build();
baselineBuildInfo(builder.getAllParsedConfigs(), sys.vfs, sys.writtenFiles);
return sys.exit(exitCode);
}
function isBuild(commandLineArgs: readonly string[]) {
if (commandLineArgs.length > 0 && commandLineArgs[0].charCodeAt(0) === CharacterCodes.minus) {
const firstOption = commandLineArgs[0].slice(commandLineArgs[0].charCodeAt(1) === CharacterCodes.minus ? 2 : 1).toLowerCase();
return firstOption === "build" || firstOption === "b";
}
return false;
}
export enum BuildKind {
Initial = "initial-build",
IncrementalDtsChange = "incremental-declaration-changes",
IncrementalDtsUnchanged = "incremental-declaration-doesnt-change",
IncrementalHeadersChange = "incremental-headers-change-without-dts-changes"
}
export interface TscCompile {
scenario: string;
subScenario: string;
buildKind?: BuildKind; // Should be defined for tsc --b
fs: () => vfs.FileSystem;
commandLineArgs: readonly string[];
modifyFs?: (fs: vfs.FileSystem) => void;
baselineSourceMap?: boolean;
baselineReadFileCalls?: boolean;
}
export function tscCompile(input: TscCompile) {
const baseFs = input.fs();
const fs = baseFs.shadow();
const {
scenario, subScenario, buildKind,
commandLineArgs, modifyFs,
baselineSourceMap, baselineReadFileCalls
} = input;
if (modifyFs) modifyFs(fs);
// Create system
const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc" }) as TscCompileSystem;
const writtenFiles = sys.writtenFiles = createMap<true>();
const originalWriteFile = sys.writeFile;
sys.writeFile = (fileName, content, writeByteOrderMark) => {
assert.isFalse(writtenFiles.has(fileName));
writtenFiles.set(fileName, true);
return originalWriteFile.call(sys, fileName, content, writeByteOrderMark);
};
const actualReadFileMap: MapLike<number> = {};
const originalReadFile = sys.readFile;
sys.readFile = path => {
// Dont record libs
if (path.startsWith("/src/")) {
actualReadFileMap[path] = (getProperty(actualReadFileMap, path) || 0) + 1;
}
return originalReadFile.call(sys, path);
};
sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`);
sys.exit = exitCode => sys.exitCode = exitCode;
executeCommandLine(sys, commandLineArgs);
sys.write(`exitCode:: ${sys.exitCode}\n`);
if (baselineReadFileCalls) {
sys.write(`readFiles:: ${JSON.stringify(actualReadFileMap, /*replacer*/ undefined, " ")} `);
}
if (baselineSourceMap) generateSourceMapBaselineFiles(fs, mapDefinedIterator(writtenFiles.keys(), f => f.endsWith(".map") ? f : undefined));
// Baseline the errors
fs.writeFileSync(`/lib/${buildKind || BuildKind.Initial}Output.txt`, sys.output.join(""));
fs.makeReadonly();
sys.baseLine = () => {
const patch = fs.diff(baseFs, { includeChangedFileWithSameContent: true });
// eslint-disable-next-line no-null/no-null
Harness.Baseline.runBaseline(`${isBuild(commandLineArgs) ? "tsbuild" : "tsc"}/${scenario}/${buildKind || BuildKind.Initial}/${subScenario.split(" ").join("-")}.js`, patch ? vfs.formatPatch(patch) : null);
};
return sys;
}
export function verifyTscBaseline(sys: () => TscCompileSystem) {
it(`Generates files matching the baseline`, () => {
sys().baseLine();
});
}
export function verifyTsc(input: TscCompile) {
describe(input.scenario, () => {
describe(input.subScenario, () => {
let sys: TscCompileSystem;
before(() => {
sys = tscCompile({
...input,
fs: () => getFsWithTime(input.fs()).fs.makeReadonly()
});
});
after(() => {
sys = undefined!;
});
verifyTscBaseline(() => sys);
});
});
}
}
@@ -12,7 +12,8 @@ namespace ts.tscWatch {
file,
fileStamp: host.getModifiedTime(file.path.replace(".ts", ".js")),
errors: builderProgram.getSemanticDiagnostics(watch().getSourceFileByPath(file.path as Path)),
errorsFromOldState: !!state.semanticDiagnosticsFromOldState && state.semanticDiagnosticsFromOldState.has(file.path)
errorsFromOldState: !!state.semanticDiagnosticsFromOldState && state.semanticDiagnosticsFromOldState.has(file.path),
dtsStamp: host.getModifiedTime(file.path.replace(".ts", ".d.ts"))
};
}
@@ -24,21 +25,36 @@ namespace ts.tscWatch {
return find(stampsAndErrors, info => info.file === file)!;
}
function verifyOutputFileStampsAndErrors(
file: File,
emitExpected: boolean,
errorRefershExpected: boolean,
beforeChangeFileStampsAndErrors: readonly ReturnType<typeof getOutputFileStampAndError>[],
afterChangeFileStampsAndErrors: readonly ReturnType<typeof getOutputFileStampAndError>[]
) {
interface VerifyOutputFileStampAndErrors {
file: File;
jsEmitExpected: boolean;
dtsEmitExpected: boolean;
errorRefershExpected: boolean;
beforeChangeFileStampsAndErrors: readonly ReturnType<typeof getOutputFileStampAndError>[];
afterChangeFileStampsAndErrors: readonly ReturnType<typeof getOutputFileStampAndError>[];
}
function verifyOutputFileStampsAndErrors({
file,
jsEmitExpected,
dtsEmitExpected,
errorRefershExpected,
beforeChangeFileStampsAndErrors,
afterChangeFileStampsAndErrors
}: VerifyOutputFileStampAndErrors) {
const beforeChange = findStampAndErrors(beforeChangeFileStampsAndErrors, file);
const afterChange = findStampAndErrors(afterChangeFileStampsAndErrors, file);
if (emitExpected) {
if (jsEmitExpected) {
assert.notStrictEqual(afterChange.fileStamp, beforeChange.fileStamp, `Expected emit for file ${file.path}`);
}
else {
assert.strictEqual(afterChange.fileStamp, beforeChange.fileStamp, `Did not expect new emit for file ${file.path}`);
}
if (dtsEmitExpected) {
assert.notStrictEqual(afterChange.dtsStamp, beforeChange.dtsStamp, `Expected emit for file ${file.path}`);
}
else {
assert.strictEqual(afterChange.dtsStamp, beforeChange.dtsStamp, `Did not expect new emit for file ${file.path}`);
}
if (errorRefershExpected) {
if (afterChange.errors !== emptyArray || beforeChange.errors !== emptyArray) {
assert.notStrictEqual(afterChange.errors, beforeChange.errors, `Expected new errors for file ${file.path}`);
@@ -51,19 +67,22 @@ namespace ts.tscWatch {
}
}
interface VerifyEmitAndErrorUpdates {
change: (host: WatchedSystem) => void;
getInitialErrors: (watch: Watch) => readonly Diagnostic[] | readonly string[];
getIncrementalErrors: (watch: Watch) => readonly Diagnostic[] | readonly string[];
filesWithNewEmit: readonly File[];
filesWithOnlyErrorRefresh: readonly File[];
filesNotTouched: readonly File[];
configFile?: File;
interface VerifyEmitAndErrorUpdatesWorker extends VerifyEmitAndErrorUpdates {
configFile: File;
}
function verifyEmitAndErrorUpdates({ filesWithNewEmit, filesWithOnlyErrorRefresh, filesNotTouched, configFile = config, change, getInitialErrors, getIncrementalErrors }: VerifyEmitAndErrorUpdates) {
function verifyEmitAndErrorUpdatesWorker({
fileWithChange,
filesWithNewEmit,
filesWithOnlyErrorRefresh,
filesNotTouched,
configFile,
change,
getInitialErrors,
getIncrementalErrors
}: VerifyEmitAndErrorUpdatesWorker) {
const nonLibFiles = [...filesWithNewEmit, ...filesWithOnlyErrorRefresh, ...filesNotTouched];
const files = [...nonLibFiles, configFile, libFile];
const compilerOptions = (JSON.parse(configFile.content).compilerOptions || {}) as CompilerOptions;
const host = createWatchedSystem(files, { currentDirectory });
const watch = createWatchOfConfigFile("tsconfig.json", host);
checkProgramActualFiles(watch(), [...nonLibFiles.map(f => f.path), libFile.path]);
@@ -73,9 +92,77 @@ namespace ts.tscWatch {
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, getIncrementalErrors(watch));
const afterChange = getOutputFileStampsAndErrors(host, watch, nonLibFiles);
filesWithNewEmit.forEach(file => verifyOutputFileStampsAndErrors(file, /*emitExpected*/ true, /*errorRefershExpected*/ true, beforeChange, afterChange));
filesWithOnlyErrorRefresh.forEach(file => verifyOutputFileStampsAndErrors(file, /*emitExpected*/ false, /*errorRefershExpected*/ true, beforeChange, afterChange));
filesNotTouched.forEach(file => verifyOutputFileStampsAndErrors(file, /*emitExpected*/ false, /*errorRefershExpected*/ false, beforeChange, afterChange));
filesWithNewEmit.forEach(file => verifyOutputFileStampsAndErrors({
file,
jsEmitExpected: !compilerOptions.isolatedModules || fileWithChange === file,
dtsEmitExpected: getEmitDeclarations(compilerOptions),
errorRefershExpected: true,
beforeChangeFileStampsAndErrors: beforeChange,
afterChangeFileStampsAndErrors: afterChange
}));
filesWithOnlyErrorRefresh.forEach(file => verifyOutputFileStampsAndErrors({
file,
jsEmitExpected: false,
dtsEmitExpected: getEmitDeclarations(compilerOptions) && !file.path.endsWith(".d.ts"),
errorRefershExpected: true,
beforeChangeFileStampsAndErrors: beforeChange,
afterChangeFileStampsAndErrors: afterChange
}));
filesNotTouched.forEach(file => verifyOutputFileStampsAndErrors({
file,
jsEmitExpected: false,
dtsEmitExpected: false,
errorRefershExpected: false,
beforeChangeFileStampsAndErrors: beforeChange,
afterChangeFileStampsAndErrors: afterChange
}));
}
function changeCompilerOptions(input: VerifyEmitAndErrorUpdates, additionalOptions: CompilerOptions): File {
const configFile = input.configFile || config;
const content = JSON.parse(configFile.content);
content.compilerOptions = { ...content.compilerOptions, ...additionalOptions };
return { path: configFile.path, content: JSON.stringify(content) };
}
interface VerifyEmitAndErrorUpdates {
change: (host: WatchedSystem) => void;
getInitialErrors: (watch: Watch) => readonly Diagnostic[] | readonly string[];
getIncrementalErrors: (watch: Watch) => readonly Diagnostic[] | readonly string[];
fileWithChange: File;
filesWithNewEmit: readonly File[];
filesWithOnlyErrorRefresh: readonly File[];
filesNotTouched: readonly File[];
configFile?: File;
}
function verifyEmitAndErrorUpdates(input: VerifyEmitAndErrorUpdates) {
it("with default config", () => {
verifyEmitAndErrorUpdatesWorker({
...input,
configFile: input.configFile || config
});
});
it("with default config and --declaration", () => {
verifyEmitAndErrorUpdatesWorker({
...input,
configFile: changeCompilerOptions(input, { declaration: true })
});
});
it("config with --isolatedModules", () => {
verifyEmitAndErrorUpdatesWorker({
...input,
configFile: changeCompilerOptions(input, { isolatedModules: true })
});
});
it("config with --isolatedModules and --declaration", () => {
verifyEmitAndErrorUpdatesWorker({
...input,
configFile: changeCompilerOptions(input, { isolatedModules: true, declaration: true })
});
});
}
describe("deep import changes", () => {
@@ -93,6 +180,7 @@ console.log(b.c.d);`
addImportedModule(bFile);
addImportedModule(cFile);
verifyEmitAndErrorUpdates({
fileWithChange: cFile,
filesWithNewEmit,
filesWithOnlyErrorRefresh,
filesNotTouched: emptyArray,
@@ -113,7 +201,7 @@ console.log(b.c.d);`
}
}
it("updates errors when deep import file changes", () => {
describe("updates errors when deep import file changes", () => {
const bFile: File = {
path: `${currentDirectory}/b.ts`,
content: `import {C} from './c';
@@ -132,7 +220,7 @@ export class B
verifyDeepImportChange(bFile, cFile);
});
it("updates errors when deep import through declaration file changes", () => {
describe("updates errors when deep import through declaration file changes", () => {
const bFile: File = {
path: `${currentDirectory}/b.d.ts`,
content: `import {C} from './c';
@@ -152,7 +240,7 @@ export class B
});
});
it("updates errors in file not exporting a deep multilevel import that changes", () => {
describe("updates errors in file not exporting a deep multilevel import that changes", () => {
const aFile: File = {
path: `${currentDirectory}/a.ts`,
content: `export interface Point {
@@ -193,6 +281,7 @@ getPoint().c.x;`
content: `import "./d";`
};
verifyEmitAndErrorUpdates({
fileWithChange: aFile,
filesWithNewEmit: [aFile, bFile],
filesWithOnlyErrorRefresh: [cFile, dFile],
filesNotTouched: [eFile],
@@ -265,6 +354,7 @@ export class Data {
filesWithOnlyErrorRefresh.push(lib2Data2);
}
verifyEmitAndErrorUpdates({
fileWithChange: lib1ToolsInterface,
filesWithNewEmit,
filesWithOnlyErrorRefresh,
filesNotTouched: emptyArray,
@@ -276,11 +366,11 @@ export class Data {
]
});
}
it("when there are no circular import and exports", () => {
describe("when there are no circular import and exports", () => {
verifyTransitiveExports(lib2Data);
});
it("when there are circular import and exports", () => {
describe("when there are circular import and exports", () => {
const lib2Data: File = {
path: `${currentDirectory}/lib2/data.ts`,
content: `import { ITest } from "lib1/public"; import { Data2 } from "./data2";
@@ -179,7 +179,7 @@ namespace ts.projectSystem {
}
function verifyUserTsConfigProject(session: TestSession) {
checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aDts.path, userTsconfig.path]);
checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aTs.path, userTsconfig.path]);
}
it("goToDefinition", () => {
@@ -450,6 +450,13 @@ namespace ts.projectSystem {
name: "function f(): void",
},
references: [
makeReferenceEntry({
file: aTs,
text: "f",
options: { index: 1 },
contextText: "function f() {}",
isDefinition: true
}),
{
fileName: bTs.path,
isDefinition: false,
@@ -457,13 +464,6 @@ namespace ts.projectSystem {
isWriteAccess: false,
textSpan: { start: 0, length: 1 },
},
makeReferenceEntry({
file: aTs,
text: "f",
options: { index: 1 },
contextText: "function f() {}",
isDefinition: true
})
],
}
]);
@@ -73,44 +73,64 @@ namespace ts.projectSystem {
verifyEvent(project, `Change in config file detected`);
});
it("when opening original location project", () => {
const aDTs: File = {
path: `${projectRoot}/a/a.d.ts`,
content: `export declare class A {
describe("when opening original location project", () => {
it("with project references", () => {
verify();
});
it("when disableSourceOfProjectReferenceRedirect is true", () => {
verify(/*disableSourceOfProjectReferenceRedirect*/ true);
});
function verify(disableSourceOfProjectReferenceRedirect?: true) {
const aDTs: File = {
path: `${projectRoot}/a/a.d.ts`,
content: `export declare class A {
}
//# sourceMappingURL=a.d.ts.map
`
};
const aDTsMap: File = {
path: `${projectRoot}/a/a.d.ts.map`,
content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}`
};
const bTs: File = {
path: bTsPath,
content: `import {A} from "../a/a"; new A();`
};
const configB: File = {
path: configBPath,
content: JSON.stringify({
references: [{ path: "../a" }]
})
};
};
const aDTsMap: File = {
path: `${projectRoot}/a/a.d.ts.map`,
content: `{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["./a.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;CAAI"}`
};
const bTs: File = {
path: bTsPath,
content: `import {A} from "../a/a"; new A();`
};
const configB: File = {
path: configBPath,
content: JSON.stringify({
...(disableSourceOfProjectReferenceRedirect && {
compilerOptions: {
disableSourceOfProjectReferenceRedirect
}
}),
references: [{ path: "../a" }]
})
};
const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionToVerifyEvent(files.concat(aDTs, aDTsMap, bTs, configB));
verifyEventWithOpenTs(bTs, configB.path, 1);
const { service, session, verifyEventWithOpenTs, verifyEvent } = createSessionToVerifyEvent(files.concat(aDTs, aDTsMap, bTs, configB));
verifyEventWithOpenTs(bTs, configB.path, 1);
session.executeCommandSeq<protocol.ReferencesRequest>({
command: protocol.CommandTypes.References,
arguments: {
file: bTs.path,
...protocolLocationFromSubstring(bTs.content, "A()")
}
});
session.executeCommandSeq<protocol.ReferencesRequest>({
command: protocol.CommandTypes.References,
arguments: {
file: bTs.path,
...protocolLocationFromSubstring(bTs.content, "A()")
}
});
checkNumberOfProjects(service, { configuredProjects: 2 });
const project = service.configuredProjects.get(configA.path)!;
assert.isDefined(project);
verifyEvent(project, `Creating project for original file: ${aTs.path} for location: ${aDTs.path}`);
checkNumberOfProjects(service, { configuredProjects: 2 });
const project = service.configuredProjects.get(configA.path)!;
assert.isDefined(project);
verifyEvent(
project,
disableSourceOfProjectReferenceRedirect ?
`Creating project for original file: ${aTs.path} for location: ${aDTs.path}` :
`Creating project for original file: ${aTs.path}`
);
}
});
describe("with external projects and config files ", () => {
+3 -3
View File
@@ -491,8 +491,8 @@ namespace ts.projectSystem {
checkArray("Open files", arrayFrom(projectService.openFiles.keys(), path => projectService.getScriptInfoForPath(path as Path)!.fileName), expectedFiles.map(file => file.path));
}
export function checkScriptInfos(projectService: server.ProjectService, expectedFiles: readonly string[]) {
checkArray("ScriptInfos files", arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles);
export function checkScriptInfos(projectService: server.ProjectService, expectedFiles: readonly string[], additionInfo?: string) {
checkArray(`ScriptInfos files: ${additionInfo || ""}`, arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles);
}
export function protocolLocationFromSubstring(str: string, substring: string): protocol.Location {
@@ -501,7 +501,7 @@ namespace ts.projectSystem {
return protocolToLocation(str)(start);
}
function protocolToLocation(text: string): (pos: number) => protocol.Location {
export function protocolToLocation(text: string): (pos: number) => protocol.Location {
const lineStarts = computeLineStarts(text);
return pos => {
const x = computeLineAndCharacterOfPosition(lineStarts, pos);
@@ -0,0 +1,410 @@
namespace ts.projectSystem {
describe("unittests:: tsserver:: with project references and compile on save", () => {
const projectLocation = "/user/username/projects/myproject";
const dependecyLocation = `${projectLocation}/dependency`;
const usageLocation = `${projectLocation}/usage`;
const dependencyTs: File = {
path: `${dependecyLocation}/fns.ts`,
content: `export function fn1() { }
export function fn2() { }
`
};
const dependencyConfig: File = {
path: `${dependecyLocation}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { composite: true, declarationDir: "../decls" },
compileOnSave: true
})
};
const usageTs: File = {
path: `${usageLocation}/usage.ts`,
content: `import {
fn1,
fn2,
} from '../decls/fns'
fn1();
fn2();
`
};
const usageConfig: File = {
path: `${usageLocation}/tsconfig.json`,
content: JSON.stringify({
compileOnSave: true,
references: [{ path: "../dependency" }]
})
};
interface VerifySingleScenarioWorker extends VerifySingleScenario {
withProject: boolean;
}
function verifySingleScenarioWorker({
withProject, scenario, openFiles, requestArgs, change, expectedResult
}: VerifySingleScenarioWorker) {
it(scenario, () => {
const host = TestFSWithWatch.changeToHostTrackingWrittenFiles(
createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile])
);
const session = createSession(host);
openFilesForSession(openFiles(), session);
const reqArgs = requestArgs();
const {
expectedAffected,
expectedEmit: { expectedEmitSuccess, expectedFiles },
expectedEmitOutput
} = expectedResult(withProject);
if (change) {
session.executeCommandSeq<protocol.CompileOnSaveAffectedFileListRequest>({
command: protocol.CommandTypes.CompileOnSaveAffectedFileList,
arguments: { file: dependencyTs.path }
});
const { file, insertString } = change();
if (session.getProjectService().openFiles.has(file.path)) {
const toLocation = protocolToLocation(file.content);
const location = toLocation(file.content.length);
session.executeCommandSeq<protocol.ChangeRequest>({
command: protocol.CommandTypes.Change,
arguments: {
file: file.path,
...location,
endLine: location.line,
endOffset: location.offset,
insertString
}
});
}
else {
host.writeFile(file.path, `${file.content}${insertString}`);
}
host.writtenFiles.clear();
}
const args = withProject ? reqArgs : { file: reqArgs.file };
// Verify CompileOnSaveAffectedFileList
const actualAffectedFiles = session.executeCommandSeq<protocol.CompileOnSaveAffectedFileListRequest>({
command: protocol.CommandTypes.CompileOnSaveAffectedFileList,
arguments: args
}).response as protocol.CompileOnSaveAffectedFileListSingleProject[];
assert.deepEqual(actualAffectedFiles, expectedAffected, "Affected files");
// Verify CompileOnSaveEmit
const actualEmit = session.executeCommandSeq<protocol.CompileOnSaveEmitFileRequest>({
command: protocol.CommandTypes.CompileOnSaveEmitFile,
arguments: args
}).response;
assert.deepEqual(actualEmit, expectedEmitSuccess, "Emit files");
assert.equal(host.writtenFiles.size, expectedFiles.length);
for (const file of expectedFiles) {
assert.equal(host.readFile(file.path), file.content, `Expected to write ${file.path}`);
assert.isTrue(host.writtenFiles.has(file.path), `${file.path} is newly written`);
}
// Verify EmitOutput
const { exportedModulesFromDeclarationEmit: _1, ...actualEmitOutput } = session.executeCommandSeq<protocol.EmitOutputRequest>({
command: protocol.CommandTypes.EmitOutput,
arguments: args
}).response as EmitOutput;
assert.deepEqual(actualEmitOutput, expectedEmitOutput, "Emit output");
});
}
interface VerifySingleScenario {
scenario: string;
openFiles: () => readonly File[];
requestArgs: () => protocol.FileRequestArgs;
skipWithoutProject?: boolean;
change?: () => SingleScenarioChange;
expectedResult: GetSingleScenarioResult;
}
function verifySingleScenario(scenario: VerifySingleScenario) {
if (!scenario.skipWithoutProject) {
describe("without specifying project file", () => {
verifySingleScenarioWorker({
withProject: false,
...scenario
});
});
}
describe("with specifying project file", () => {
verifySingleScenarioWorker({
withProject: true,
...scenario
});
});
}
interface SingleScenarioExpectedEmit {
expectedEmitSuccess: boolean;
expectedFiles: readonly File[];
}
interface SingleScenarioResult {
expectedAffected: protocol.CompileOnSaveAffectedFileListSingleProject[];
expectedEmit: SingleScenarioExpectedEmit;
expectedEmitOutput: EmitOutput;
}
type GetSingleScenarioResult = (withProject: boolean) => SingleScenarioResult;
interface SingleScenarioChange {
file: File;
insertString: string;
}
interface ScenarioDetails {
scenarioName: string;
requestArgs: () => protocol.FileRequestArgs;
skipWithoutProject?: boolean;
initial: GetSingleScenarioResult;
localChangeToDependency: GetSingleScenarioResult;
localChangeToUsage: GetSingleScenarioResult;
changeToDependency: GetSingleScenarioResult;
changeToUsage: GetSingleScenarioResult;
}
interface VerifyScenario {
openFiles: () => readonly File[];
scenarios: readonly ScenarioDetails[];
}
const localChange = "function fn3() { }";
const change = `export ${localChange}`;
const changeJs = `function fn3() { }
exports.fn3 = fn3;`;
const changeDts = "export declare function fn3(): void;";
function verifyScenario({ openFiles, scenarios }: VerifyScenario) {
for (const {
scenarioName, requestArgs, skipWithoutProject, initial,
localChangeToDependency, localChangeToUsage,
changeToDependency, changeToUsage
} of scenarios) {
describe(scenarioName, () => {
verifySingleScenario({
scenario: "with initial file open",
openFiles,
requestArgs,
skipWithoutProject,
expectedResult: initial
});
verifySingleScenario({
scenario: "with local change to dependency",
openFiles,
requestArgs,
skipWithoutProject,
change: () => ({ file: dependencyTs, insertString: localChange }),
expectedResult: localChangeToDependency
});
verifySingleScenario({
scenario: "with local change to usage",
openFiles,
requestArgs,
skipWithoutProject,
change: () => ({ file: usageTs, insertString: localChange }),
expectedResult: localChangeToUsage
});
verifySingleScenario({
scenario: "with change to dependency",
openFiles,
requestArgs,
skipWithoutProject,
change: () => ({ file: dependencyTs, insertString: change }),
expectedResult: changeToDependency
});
verifySingleScenario({
scenario: "with change to usage",
openFiles,
requestArgs,
skipWithoutProject,
change: () => ({ file: usageTs, insertString: change }),
expectedResult: changeToUsage
});
});
}
}
function expectedAffectedFiles(config: File, fileNames: File[]): protocol.CompileOnSaveAffectedFileListSingleProject {
return {
projectFileName: config.path,
fileNames: fileNames.map(f => f.path),
projectUsesOutFile: false
};
}
function expectedUsageEmit(appendJsText?: string): SingleScenarioExpectedEmit {
const appendJs = appendJsText ? `${appendJsText}
` : "";
return {
expectedEmitSuccess: true,
expectedFiles: [{
path: `${usageLocation}/usage.js`,
content: `"use strict";
exports.__esModule = true;
var fns_1 = require("../decls/fns");
fns_1.fn1();
fns_1.fn2();
${appendJs}`
}]
};
}
function expectedEmitOutput({ expectedFiles }: SingleScenarioExpectedEmit): EmitOutput {
return {
outputFiles: expectedFiles.map(({ path, content }) => ({
name: path,
text: content,
writeByteOrderMark: false
})),
emitSkipped: false
};
}
function expectedUsageEmitOutput(appendJsText?: string): EmitOutput {
return expectedEmitOutput(expectedUsageEmit(appendJsText));
}
function noEmit(): SingleScenarioExpectedEmit {
return {
expectedEmitSuccess: false,
expectedFiles: emptyArray
};
}
function noEmitOutput(): EmitOutput {
return {
emitSkipped: true,
outputFiles: []
};
}
function expectedDependencyEmit(appendJsText?: string, appendDtsText?: string): SingleScenarioExpectedEmit {
const appendJs = appendJsText ? `${appendJsText}
` : "";
const appendDts = appendDtsText ? `${appendDtsText}
` : "";
return {
expectedEmitSuccess: true,
expectedFiles: [
{
path: `${dependecyLocation}/fns.js`,
content: `"use strict";
exports.__esModule = true;
function fn1() { }
exports.fn1 = fn1;
function fn2() { }
exports.fn2 = fn2;
${appendJs}`
},
{
path: `${projectLocation}/decls/fns.d.ts`,
content: `export declare function fn1(): void;
export declare function fn2(): void;
${appendDts}`
}
]
};
}
function expectedDependencyEmitOutput(appendJsText?: string, appendDtsText?: string): EmitOutput {
return expectedEmitOutput(expectedDependencyEmit(appendJsText, appendDtsText));
}
function scenarioDetailsOfUsage(isDependencyOpen?: boolean): ScenarioDetails[] {
return [
{
scenarioName: "Of usageTs",
requestArgs: () => ({ file: usageTs.path, projectFileName: usageConfig.path }),
initial: () => initialUsageTs(),
// no change to usage so same as initial only usage file
localChangeToDependency: () => initialUsageTs(),
localChangeToUsage: () => initialUsageTs(localChange),
changeToDependency: () => initialUsageTs(),
changeToUsage: () => initialUsageTs(changeJs)
},
{
scenarioName: "Of dependencyTs in usage project",
requestArgs: () => ({ file: dependencyTs.path, projectFileName: usageConfig.path }),
skipWithoutProject: !!isDependencyOpen,
initial: () => initialDependencyTs(),
localChangeToDependency: () => initialDependencyTs(/*noUsageFiles*/ true),
localChangeToUsage: () => initialDependencyTs(/*noUsageFiles*/ true),
changeToDependency: () => initialDependencyTs(),
changeToUsage: () => initialDependencyTs(/*noUsageFiles*/ true)
}
];
function initialUsageTs(jsText?: string) {
return {
expectedAffected: [
expectedAffectedFiles(usageConfig, [usageTs])
],
expectedEmit: expectedUsageEmit(jsText),
expectedEmitOutput: expectedUsageEmitOutput(jsText)
};
}
function initialDependencyTs(noUsageFiles?: true) {
return {
expectedAffected: [
expectedAffectedFiles(usageConfig, noUsageFiles ? [] : [usageTs])
],
expectedEmit: noEmit(),
expectedEmitOutput: noEmitOutput()
};
}
}
function scenarioDetailsOfDependencyWhenOpen(): ScenarioDetails {
return {
scenarioName: "Of dependencyTs",
requestArgs: () => ({ file: dependencyTs.path, projectFileName: dependencyConfig.path }),
initial,
localChangeToDependency: withProject => ({
expectedAffected: withProject ?
[
expectedAffectedFiles(dependencyConfig, [dependencyTs])
] :
[
expectedAffectedFiles(usageConfig, []),
expectedAffectedFiles(dependencyConfig, [dependencyTs])
],
expectedEmit: expectedDependencyEmit(localChange),
expectedEmitOutput: expectedDependencyEmitOutput(localChange)
}),
localChangeToUsage: withProject => initial(withProject, /*noUsageFiles*/ true),
changeToDependency: withProject => initial(withProject, /*noUsageFiles*/ undefined, changeJs, changeDts),
changeToUsage: withProject => initial(withProject, /*noUsageFiles*/ true)
};
function initial(withProject: boolean, noUsageFiles?: true, appendJs?: string, appendDts?: string): SingleScenarioResult {
return {
expectedAffected: withProject ?
[
expectedAffectedFiles(dependencyConfig, [dependencyTs])
] :
[
expectedAffectedFiles(usageConfig, noUsageFiles ? [] : [usageTs]),
expectedAffectedFiles(dependencyConfig, [dependencyTs])
],
expectedEmit: expectedDependencyEmit(appendJs, appendDts),
expectedEmitOutput: expectedDependencyEmitOutput(appendJs, appendDts)
};
}
}
describe("when dependency project is not open", () => {
verifyScenario({
openFiles: () => [usageTs],
scenarios: scenarioDetailsOfUsage()
});
});
describe("when the depedency file is open", () => {
verifyScenario({
openFiles: () => [usageTs, dependencyTs],
scenarios: [
...scenarioDetailsOfUsage(/*isDependencyOpen*/ true),
scenarioDetailsOfDependencyWhenOpen(),
]
});
});
});
}
@@ -0,0 +1,430 @@
namespace ts.projectSystem {
describe("unittests:: tsserver:: with project references and error reporting", () => {
const projectLocation = "/user/username/projects/myproject";
const dependecyLocation = `${projectLocation}/dependency`;
const usageLocation = `${projectLocation}/usage`;
interface CheckErrorsInFile {
session: TestSession;
host: TestServerHost;
expected: GetErrDiagnostics;
expectedSequenceId?: number;
}
function checkErrorsInFile({ session, host, expected: { file, syntax, semantic, suggestion }, expectedSequenceId }: CheckErrorsInFile) {
host.checkTimeoutQueueLengthAndRun(1);
checkErrorMessage(session, "syntaxDiag", { file: file.path, diagnostics: syntax });
session.clearMessages();
host.runQueuedImmediateCallbacks(1);
checkErrorMessage(session, "semanticDiag", { file: file.path, diagnostics: semantic });
session.clearMessages();
host.runQueuedImmediateCallbacks(1);
checkErrorMessage(session, "suggestionDiag", { file: file.path, diagnostics: suggestion });
if (expectedSequenceId !== undefined) {
checkCompleteEvent(session, 2, expectedSequenceId);
}
session.clearMessages();
}
interface CheckAllErrors {
session: TestSession;
host: TestServerHost;
expected: readonly GetErrDiagnostics[];
expectedSequenceId: number;
}
function checkAllErrors({ session, host, expected, expectedSequenceId }: CheckAllErrors) {
for (let i = 0; i < expected.length; i++) {
checkErrorsInFile({
session,
host,
expected: expected[i],
expectedSequenceId: i === expected.length - 1 ? expectedSequenceId : undefined
});
}
}
function verifyErrorsUsingGeterr({ allFiles, openFiles, expectedGetErr }: VerifyScenario) {
it("verifies the errors in open file", () => {
const host = createServerHost([...allFiles(), libFile]);
const session = createSession(host, { canUseEvents: true, });
openFilesForSession(openFiles(), session);
session.clearMessages();
const expectedSequenceId = session.getNextSeq();
const expected = expectedGetErr();
session.executeCommandSeq<protocol.GeterrRequest>({
command: protocol.CommandTypes.Geterr,
arguments: {
delay: 0,
files: expected.map(f => f.file.path)
}
});
checkAllErrors({ session, host, expected, expectedSequenceId });
});
}
function verifyErrorsUsingGeterrForProject({ allFiles, openFiles, expectedGetErrForProject }: VerifyScenario) {
it("verifies the errors in projects", () => {
const host = createServerHost([...allFiles(), libFile]);
const session = createSession(host, { canUseEvents: true, });
openFilesForSession(openFiles(), session);
session.clearMessages();
for (const expected of expectedGetErrForProject()) {
const expectedSequenceId = session.getNextSeq();
session.executeCommandSeq<protocol.GeterrForProjectRequest>({
command: protocol.CommandTypes.GeterrForProject,
arguments: {
delay: 0,
file: expected.project
}
});
checkAllErrors({ session, host, expected: expected.errors, expectedSequenceId });
}
});
}
function verifyErrorsUsingSyncMethods({ allFiles, openFiles, expectedSyncDiagnostics }: VerifyScenario) {
it("verifies the errors using sync commands", () => {
const host = createServerHost([...allFiles(), libFile]);
const session = createSession(host);
openFilesForSession(openFiles(), session);
for (const { file, project, syntax, semantic, suggestion } of expectedSyncDiagnostics()) {
const actualSyntax = session.executeCommandSeq<protocol.SyntacticDiagnosticsSyncRequest>({
command: protocol.CommandTypes.SyntacticDiagnosticsSync,
arguments: {
file: file.path,
projectFileName: project
}
}).response as protocol.Diagnostic[];
assert.deepEqual(actualSyntax, syntax, `Syntax diagnostics for file: ${file.path}, project: ${project}`);
const actualSemantic = session.executeCommandSeq<protocol.SemanticDiagnosticsSyncRequest>({
command: protocol.CommandTypes.SemanticDiagnosticsSync,
arguments: {
file: file.path,
projectFileName: project
}
}).response as protocol.Diagnostic[];
assert.deepEqual(actualSemantic, semantic, `Semantic diagnostics for file: ${file.path}, project: ${project}`);
const actualSuggestion = session.executeCommandSeq<protocol.SuggestionDiagnosticsSyncRequest>({
command: protocol.CommandTypes.SuggestionDiagnosticsSync,
arguments: {
file: file.path,
projectFileName: project
}
}).response as protocol.Diagnostic[];
assert.deepEqual(actualSuggestion, suggestion, `Suggestion diagnostics for file: ${file.path}, project: ${project}`);
}
});
}
function verifyConfigFileErrors({ allFiles, openFiles, expectedConfigFileDiagEvents }: VerifyScenario) {
it("verify config file errors", () => {
const host = createServerHost([...allFiles(), libFile]);
const { session, events } = createSessionWithEventTracking<server.ConfigFileDiagEvent>(host, server.ConfigFileDiagEvent);
for (const file of openFiles()) {
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
arguments: { file: file.path }
});
}
assert.deepEqual(events, expectedConfigFileDiagEvents().map(data => ({
eventName: server.ConfigFileDiagEvent,
data
})));
});
}
interface GetErrDiagnostics {
file: File;
syntax: protocol.Diagnostic[];
semantic: protocol.Diagnostic[];
suggestion: protocol.Diagnostic[];
}
interface GetErrForProjectDiagnostics {
project: string;
errors: readonly GetErrDiagnostics[];
}
interface SyncDiagnostics extends GetErrDiagnostics {
project?: string;
}
interface VerifyScenario {
allFiles: () => readonly File[];
openFiles: () => readonly File[];
expectedGetErr: () => readonly GetErrDiagnostics[];
expectedGetErrForProject: () => readonly GetErrForProjectDiagnostics[];
expectedSyncDiagnostics: () => readonly SyncDiagnostics[];
expectedConfigFileDiagEvents: () => readonly server.ConfigFileDiagEvent["data"][];
}
function verifyScenario(scenario: VerifyScenario) {
verifyErrorsUsingGeterr(scenario);
verifyErrorsUsingGeterrForProject(scenario);
verifyErrorsUsingSyncMethods(scenario);
verifyConfigFileErrors(scenario);
}
function emptyDiagnostics(file: File): GetErrDiagnostics {
return {
file,
syntax: emptyArray,
semantic: emptyArray,
suggestion: emptyArray
};
}
function syncDiagnostics(diagnostics: GetErrDiagnostics, project: string): SyncDiagnostics {
return { project, ...diagnostics };
}
interface VerifyUsageAndDependency {
allFiles: readonly [File, File, File, File]; // dependencyTs, dependencyConfig, usageTs, usageConfig
usageDiagnostics(): GetErrDiagnostics;
dependencyDiagnostics(): GetErrDiagnostics;
}
function verifyUsageAndDependency({ allFiles, usageDiagnostics, dependencyDiagnostics }: VerifyUsageAndDependency) {
const [dependencyTs, dependencyConfig, usageTs, usageConfig] = allFiles;
function usageProjectDiagnostics(): GetErrForProjectDiagnostics {
return {
project: usageTs.path,
errors: [
usageDiagnostics(),
emptyDiagnostics(dependencyTs)
]
};
}
function dependencyProjectDiagnostics(): GetErrForProjectDiagnostics {
return {
project: dependencyTs.path,
errors: [
dependencyDiagnostics()
]
};
}
function usageConfigDiag(): server.ConfigFileDiagEvent["data"] {
return {
triggerFile: usageTs.path,
configFileName: usageConfig.path,
diagnostics: emptyArray
};
}
function dependencyConfigDiag(): server.ConfigFileDiagEvent["data"] {
return {
triggerFile: dependencyTs.path,
configFileName: dependencyConfig.path,
diagnostics: emptyArray
};
}
describe("when dependency project is not open", () => {
verifyScenario({
allFiles: () => allFiles,
openFiles: () => [usageTs],
expectedGetErr: () => [
usageDiagnostics()
],
expectedGetErrForProject: () => [
usageProjectDiagnostics(),
{
project: dependencyTs.path,
errors: [
emptyDiagnostics(dependencyTs),
usageDiagnostics()
]
}
],
expectedSyncDiagnostics: () => [
// Without project
usageDiagnostics(),
emptyDiagnostics(dependencyTs),
// With project
syncDiagnostics(usageDiagnostics(), usageConfig.path),
syncDiagnostics(emptyDiagnostics(dependencyTs), usageConfig.path),
],
expectedConfigFileDiagEvents: () => [
usageConfigDiag()
],
});
});
describe("when the depedency file is open", () => {
verifyScenario({
allFiles: () => allFiles,
openFiles: () => [usageTs, dependencyTs],
expectedGetErr: () => [
usageDiagnostics(),
dependencyDiagnostics(),
],
expectedGetErrForProject: () => [
usageProjectDiagnostics(),
dependencyProjectDiagnostics()
],
expectedSyncDiagnostics: () => [
// Without project
usageDiagnostics(),
dependencyDiagnostics(),
// With project
syncDiagnostics(usageDiagnostics(), usageConfig.path),
syncDiagnostics(emptyDiagnostics(dependencyTs), usageConfig.path),
syncDiagnostics(dependencyDiagnostics(), dependencyConfig.path),
],
expectedConfigFileDiagEvents: () => [
usageConfigDiag(),
dependencyConfigDiag()
],
});
});
}
describe("with module scenario", () => {
const dependencyTs: File = {
path: `${dependecyLocation}/fns.ts`,
content: `export function fn1() { }
export function fn2() { }
// Introduce error for fnErr import in main
// export function fnErr() { }
// Error in dependency ts file
export let x: string = 10;`
};
const dependencyConfig: File = {
path: `${dependecyLocation}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { composite: true, declarationDir: "../decls" } })
};
const usageTs: File = {
path: `${usageLocation}/usage.ts`,
content: `import {
fn1,
fn2,
fnErr
} from '../decls/fns'
fn1();
fn2();
fnErr();
`
};
const usageConfig: File = {
path: `${usageLocation}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { composite: true },
references: [{ path: "../dependency" }]
})
};
function usageDiagnostics(): GetErrDiagnostics {
return {
file: usageTs,
syntax: emptyArray,
semantic: [
createDiagnostic(
{ line: 4, offset: 5 },
{ line: 4, offset: 10 },
Diagnostics.Module_0_has_no_exported_member_1,
[`"../dependency/fns"`, "fnErr"],
"error",
)
],
suggestion: emptyArray
};
}
function dependencyDiagnostics(): GetErrDiagnostics {
return {
file: dependencyTs,
syntax: emptyArray,
semantic: [
createDiagnostic(
{ line: 6, offset: 12 },
{ line: 6, offset: 13 },
Diagnostics.Type_0_is_not_assignable_to_type_1,
["10", "string"],
"error",
)
],
suggestion: emptyArray
};
}
verifyUsageAndDependency({
allFiles: [dependencyTs, dependencyConfig, usageTs, usageConfig],
usageDiagnostics,
dependencyDiagnostics
});
});
describe("with non module --out", () => {
const dependencyTs: File = {
path: `${dependecyLocation}/fns.ts`,
content: `function fn1() { }
function fn2() { }
// Introduce error for fnErr import in main
// function fnErr() { }
// Error in dependency ts file
let x: string = 10;`
};
const dependencyConfig: File = {
path: `${dependecyLocation}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { composite: true, outFile: "../dependency.js" } })
};
const usageTs: File = {
path: `${usageLocation}/usage.ts`,
content: `fn1();
fn2();
fnErr();
`
};
const usageConfig: File = {
path: `${usageLocation}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: { composite: true, outFile: "../usage.js" },
references: [{ path: "../dependency" }]
})
};
function usageDiagnostics(): GetErrDiagnostics {
return {
file: usageTs,
syntax: emptyArray,
semantic: [
createDiagnostic(
{ line: 3, offset: 1 },
{ line: 3, offset: 6 },
Diagnostics.Cannot_find_name_0,
["fnErr"],
"error",
)
],
suggestion: emptyArray
};
}
function dependencyDiagnostics(): GetErrDiagnostics {
return {
file: dependencyTs,
syntax: emptyArray,
semantic: [
createDiagnostic(
{ line: 6, offset: 5 },
{ line: 6, offset: 6 },
Diagnostics.Type_0_is_not_assignable_to_type_1,
["10", "string"],
"error",
)
],
suggestion: emptyArray
};
}
verifyUsageAndDependency({
allFiles: [dependencyTs, dependencyConfig, usageTs, usageConfig],
usageDiagnostics,
dependencyDiagnostics
});
});
});
}
File diff suppressed because it is too large Load Diff