Merge branch 'master' into infer-from-usage/similarity-to-builtins

This commit is contained in:
Nathan Shively-Sanders
2019-09-03 12:59:43 -07:00
94 changed files with 3532 additions and 1160 deletions
+18 -8
View File
@@ -137,7 +137,7 @@ namespace ts {
*/
emittedBuildInfo?: boolean;
/**
* Already seen affected files
* Already seen emitted files
*/
seenEmittedFiles: Map<true> | undefined;
/**
@@ -329,7 +329,6 @@ namespace ts {
handleDtsMayChangeOfAffectedFile(state, affectedFile, cancellationToken, computeHash);
return affectedFile;
}
seenAffectedFiles.set(affectedFile.path, true);
affectedFilesIndex++;
}
@@ -549,7 +548,7 @@ 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) {
function doneWithAffectedFile(state: BuilderProgramState, affected: SourceFile | Program, isPendingEmit?: boolean, isBuildInfoEmit?: boolean, isEmitResult?: boolean) {
if (isBuildInfoEmit) {
state.emittedBuildInfo = true;
}
@@ -559,6 +558,9 @@ 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 (isPendingEmit) {
state.affectedFilesPendingEmitIndex!++;
}
@@ -576,6 +578,14 @@ namespace ts {
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);
return { result, affected };
}
/**
* Gets the semantic diagnostics either from cache if present, or otherwise from program and caches it
* Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files/changed file set
@@ -849,7 +859,7 @@ namespace ts {
}
const affected = Debug.assertDefined(state.program);
return toAffectedFileResult(
return toAffectedFileEmitResult(
state,
// When whole program is affected, do emit only once (eg when --out or --outFile is specified)
// Otherwise just affected file
@@ -872,14 +882,14 @@ namespace ts {
}
}
return toAffectedFileResult(
return toAffectedFileEmitResult(
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),
affected,
isPendingEmitFile
);
isPendingEmitFile,
);
}
/**
@@ -1036,7 +1046,7 @@ namespace ts {
compilerOptions: convertFromReusableCompilerOptions(program.options, toAbsolutePath),
referencedMap: getMapOfReferencedSet(program.referencedMap, toPath),
exportedModulesMap: getMapOfReferencedSet(program.exportedModulesMap, toPath),
semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => isString(value) ? value : value[0], value => isString(value) ? emptyArray : value[1]),
semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toPath(isString(value) ? value : value[0]), value => isString(value) ? emptyArray : value[1]),
hasReusableDiagnostic: true
};
return {
+7 -2
View File
@@ -345,8 +345,13 @@ namespace ts.BuilderState {
}
else {
const emitOutput = getFileEmitOutput(programOfThisState, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken);
if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) {
latestSignature = computeHash(emitOutput.outputFiles[0].text);
const firstDts = emitOutput.outputFiles &&
programOfThisState.getCompilerOptions().declarationMap ?
emitOutput.outputFiles.length > 1 ? emitOutput.outputFiles[1] : undefined :
emitOutput.outputFiles.length > 0 ? emitOutput.outputFiles[0] : undefined;
if (firstDts) {
Debug.assert(fileExtensionIs(firstDts.name, Extension.Dts), "File extension for signature expected to be dts", () => `Found: ${getAnyExtensionFromPath(firstDts.name)} for ${firstDts.name}:: All output files: ${JSON.stringify(emitOutput.outputFiles.map(f => f.name))}`);
latestSignature = computeHash(firstDts.text);
if (exportedModulesMapCache && latestSignature !== prevSignature) {
updateExportedModules(sourceFile, emitOutput.exportedModulesFromDeclarationEmit, exportedModulesMapCache);
}
+1 -1
View File
@@ -3525,7 +3525,7 @@ namespace ts {
const sig = nodeBuilder.signatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName);
const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true });
const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration);
printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonOmittingWriter(writer)); // TODO: GH#18217
printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonDeferringWriter(writer)); // TODO: GH#18217
return writer;
}
}
+4
View File
@@ -5120,6 +5120,10 @@
"category": "Message",
"code": 95089
},
"Extract to interface": {
"category": "Message",
"code": 95090
},
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
+8 -3
View File
@@ -155,6 +155,7 @@ namespace ts {
}
function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) {
if (configFile.options.emitDeclarationOnly) return undefined;
const isJsonFile = fileExtensionIs(inputFileName, Extension.Json);
const outputFileName = changeExtension(
getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir),
@@ -187,7 +188,7 @@ namespace ts {
const js = getOutputJSFileName(inputFileName, configFile, ignoreCase);
addOutput(js);
if (fileExtensionIs(inputFileName, Extension.Json)) continue;
if (configFile.options.sourceMap) {
if (js && configFile.options.sourceMap) {
addOutput(`${js}.map`);
}
if (getEmitDeclarations(configFile.options) && hasTSFileExtension(inputFileName)) {
@@ -214,6 +215,10 @@ namespace ts {
if (fileExtensionIs(inputFileName, Extension.Dts)) continue;
const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase);
if (jsFilePath) return jsFilePath;
if (fileExtensionIs(inputFileName, Extension.Json)) continue;
if (getEmitDeclarations(configFile.options) && hasTSFileExtension(inputFileName)) {
return getOutputDeclarationFileName(inputFileName, configFile, ignoreCase);
}
}
const buildInfoPath = getOutputPathForBuildInfo(configFile.options);
if (buildInfoPath) return buildInfoPath;
@@ -1053,7 +1058,7 @@ namespace ts {
function setWriter(_writer: EmitTextWriter | undefined, _sourceMapGenerator: SourceMapGenerator | undefined) {
if (_writer && printerOptions.omitTrailingSemicolon) {
_writer = getTrailingSemicolonOmittingWriter(_writer);
_writer = getTrailingSemicolonDeferringWriter(_writer);
}
writer = _writer!; // TODO: GH#18217
@@ -2511,7 +2516,7 @@ namespace ts {
}
emitWhileClause(node, node.statement.end);
writePunctuation(";");
writeTrailingSemicolon();
}
function emitWhileStatement(node: WhileStatement) {
+19 -1
View File
@@ -3374,7 +3374,11 @@ namespace ts {
};
}
export function getTrailingSemicolonOmittingWriter(writer: EmitTextWriter): EmitTextWriter {
export interface TrailingSemicolonDeferringWriter extends EmitTextWriter {
resetPendingTrailingSemicolon(): void;
}
export function getTrailingSemicolonDeferringWriter(writer: EmitTextWriter): TrailingSemicolonDeferringWriter {
let pendingTrailingSemicolon = false;
function commitPendingTrailingSemicolon() {
@@ -3440,10 +3444,24 @@ namespace ts {
decreaseIndent() {
commitPendingTrailingSemicolon();
writer.decreaseIndent();
},
resetPendingTrailingSemicolon() {
pendingTrailingSemicolon = false;
}
};
}
export function getTrailingSemicolonOmittingWriter(writer: EmitTextWriter): EmitTextWriter {
const deferringWriter = getTrailingSemicolonDeferringWriter(writer);
return {
...deferringWriter,
writeLine() {
deferringWriter.resetPendingTrailingSemicolon();
writer.writeLine();
},
};
}
export function getResolvedExternalModuleName(host: EmitHost, file: SourceFile, referenceFile?: SourceFile): string {
return file.moduleName || getExternalModuleNameFromPath(host, file.fileName, referenceFile && referenceFile.fileName);
}
+13
View File
@@ -545,6 +545,10 @@ ${indentText}${text}`;
super.writeFile(fileName, ts.getBuildInfoText(buildInfo), writeByteOrderMark);
}
createHash(data: string) {
return `${ts.generateDjb2Hash(data)}-${data}`;
}
now() {
return new Date(this.sys.vfs.time());
}
@@ -571,6 +575,15 @@ Actual: ${JSON.stringify(actual, /*replacer*/ undefined, " ")}
Expected: ${JSON.stringify(expected, /*replacer*/ undefined, " ")}`);
}
assertErrors(...expectedDiagnostics: ExpectedErrorDiagnostic[]) {
const actual = this.diagnostics.filter(d => d.kind === DiagnosticKind.Error).map(diagnosticToText);
const expected = expectedDiagnostics.map(expectedDiagnosticToText);
assert.deepEqual(actual, expected, `Diagnostics arrays did not match:
Actual: ${JSON.stringify(actual, /*replacer*/ undefined, " ")}
Expected: ${JSON.stringify(expected, /*replacer*/ undefined, " ")}
Actual All:: ${JSON.stringify(this.diagnostics.slice().map(diagnosticToText), /*replacer*/ undefined, " ")}`);
}
printDiagnostics(header = "== Diagnostics ==") {
const out = ts.createDiagnosticReporter(ts.sys);
ts.sys.write(header + "\r\n");
@@ -8197,7 +8197,7 @@
<Str Cat="Text">
<Val><![CDATA[The type argument for type parameter '{0}' cannot be inferred from the usage. Consider specifying the type arguments explicitly.]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[형식 매개 변수 '{0}'의 형식 인수를 유추할 수 없습니다. 형식 인수를 명시적으로 지정하세요.]]></Val>
<Val><![CDATA[사용량에서 형식 매개 변수 '{0}'의 형식 인수를 유추할 수 없습니다. 형식 인수를 명시적으로 지정하세요.]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
+5 -1
View File
@@ -539,10 +539,14 @@ namespace ts.formatting {
return true;
case SyntaxKind.VariableDeclaration:
case SyntaxKind.PropertyAssignment:
case SyntaxKind.BinaryExpression:
if (!settings.indentMultiLineObjectLiteralBeginningOnBlankLine && sourceFile && childKind === SyntaxKind.ObjectLiteralExpression) { // TODO: GH#18217
return rangeIsOnOneLine(sourceFile, child!);
}
return true;
if (parent.kind !== SyntaxKind.BinaryExpression) {
return true;
}
break;
case SyntaxKind.DoStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.ForInStatement:
+11 -5
View File
@@ -39,7 +39,7 @@ namespace ts.GoToDefinition {
return [sigInfo];
}
else {
const defs = getDefinitionFromSymbol(typeChecker, symbol, node) || emptyArray;
const defs = getDefinitionFromSymbol(typeChecker, symbol, node, calledDeclaration) || emptyArray;
// For a 'super()' call, put the signature first, else put the variable first.
return node.kind === SyntaxKind.SuperKeyword ? [sigInfo, ...defs] : [...defs, sigInfo];
}
@@ -232,10 +232,11 @@ namespace ts.GoToDefinition {
}
}
function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node): DefinitionInfo[] | undefined {
function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, declarationNode?: Node): DefinitionInfo[] | undefined {
// There are cases when you extend a function by adding properties to it afterwards,
// we want to strip those extra properties
const filteredDeclarations = filter(symbol.declarations, d => !isAssignmentDeclaration(d) || d === symbol.valueDeclaration) || undefined;
// we want to strip those extra properties.
// For deduping purposes, we also want to exclude any declarationNodes if provided.
const filteredDeclarations = filter(symbol.declarations, d => d !== declarationNode && (!isAssignmentDeclaration(d) || d === symbol.valueDeclaration)) || undefined;
return getConstructSignatureDefinition() || getCallSignatureDefinition() || map(filteredDeclarations, declaration => createDefinitionInfo(declaration, typeChecker, symbol, node));
function getConstructSignatureDefinition(): DefinitionInfo[] | undefined {
@@ -258,8 +259,13 @@ namespace ts.GoToDefinition {
return undefined;
}
const declarations = signatureDeclarations.filter(selectConstructors ? isConstructorDeclaration : isFunctionLike);
const declarationsWithBody = declarations.filter(d => !!(<FunctionLikeDeclaration>d).body);
// declarations defined on the global scope can be defined on multiple files. Get all of them.
return declarations.length
? [createDefinitionInfo(find(declarations, d => !!(<FunctionLikeDeclaration>d).body) || last(declarations), typeChecker, symbol, node)]
? declarationsWithBody.length !== 0
? declarationsWithBody.map(x => createDefinitionInfo(x, typeChecker, symbol, node))
: [createDefinitionInfo(last(declarations), typeChecker, symbol, node)]
: undefined;
}
}
+237 -19
View File
@@ -33,6 +33,9 @@ namespace ts.NavigationBar {
let parentsStack: NavigationBarNode[] = [];
let parent: NavigationBarNode;
const trackedEs5ClassesStack: (Map<boolean> | undefined)[] = [];
let trackedEs5Classes: Map<boolean> | undefined;
// NavigationBarItem requires an array, but will not mutate it, so just give it this for performance.
let emptyChildItemArray: NavigationBarItem[] = [];
@@ -112,10 +115,10 @@ namespace ts.NavigationBar {
pushChild(parent, emptyNavigationBarNode(node));
}
function emptyNavigationBarNode(node: Node): NavigationBarNode {
function emptyNavigationBarNode(node: Node, name?: DeclarationName): NavigationBarNode {
return {
node,
name: isDeclaration(node) || isExpression(node) ? getNameOfDeclaration(node) : undefined,
name: name || (isDeclaration(node) || isExpression(node) ? getNameOfDeclaration(node) : undefined),
additionalNodes: undefined,
parent,
children: undefined,
@@ -123,16 +126,42 @@ namespace ts.NavigationBar {
};
}
function addTrackedEs5Class(name: string) {
if (!trackedEs5Classes) {
trackedEs5Classes = createMap();
}
trackedEs5Classes.set(name, true);
}
function endNestedNodes(depth: number): void {
for (let i = 0; i < depth; i++) endNode();
}
function startNestedNodes(targetNode: Node, entityName: EntityNameExpression) {
const names: Identifier[] = [];
while (!isIdentifier(entityName)) {
const name = entityName.name;
entityName = entityName.expression;
if (name.escapedText === "prototype") continue;
names.push(name);
}
names.push(entityName);
for (let i = names.length - 1; i > 0; i--) {
const name = names[i];
startNode(targetNode, name);
}
return [names.length - 1, names[0]] as const;
}
/**
* Add a new level of NavigationBarNodes.
* This pushes to the stack, so you must call `endNode` when you are done adding to this node.
*/
function startNode(node: Node): void {
const navNode: NavigationBarNode = emptyNavigationBarNode(node);
function startNode(node: Node, name?: DeclarationName): void {
const navNode: NavigationBarNode = emptyNavigationBarNode(node, name);
pushChild(parent, navNode);
// Save the old parent
parentsStack.push(parent);
trackedEs5ClassesStack.push(trackedEs5Classes);
parent = navNode;
}
@@ -143,10 +172,11 @@ namespace ts.NavigationBar {
sortChildren(parent.children);
}
parent = parentsStack.pop()!;
trackedEs5Classes = trackedEs5ClassesStack.pop();
}
function addNodeWithRecursiveChild(node: Node, child: Node | undefined): void {
startNode(node);
function addNodeWithRecursiveChild(node: Node, child: Node | undefined, name?: DeclarationName): void {
startNode(node, name);
addChildrenRecursively(child);
endNode();
}
@@ -236,8 +266,15 @@ namespace ts.NavigationBar {
}
break;
case SyntaxKind.ArrowFunction:
case SyntaxKind.FunctionDeclaration:
const nameNode = (<FunctionLikeDeclaration>node).name;
// If we see a function declaration track as a possible ES5 class
if (nameNode && isIdentifier(nameNode)) {
addTrackedEs5Class(nameNode.text);
}
addNodeWithRecursiveChild(node, (<FunctionLikeDeclaration>node).body);
break;
case SyntaxKind.ArrowFunction:
case SyntaxKind.FunctionExpression:
addNodeWithRecursiveChild(node, (<FunctionLikeDeclaration>node).body);
break;
@@ -275,21 +312,94 @@ namespace ts.NavigationBar {
addLeafNode(node);
break;
case SyntaxKind.CallExpression:
case SyntaxKind.BinaryExpression: {
const special = getAssignmentDeclarationKind(node as BinaryExpression);
switch (special) {
case AssignmentDeclarationKind.ExportsProperty:
case AssignmentDeclarationKind.ModuleExports:
case AssignmentDeclarationKind.PrototypeProperty:
case AssignmentDeclarationKind.Prototype:
addNodeWithRecursiveChild(node, (node as BinaryExpression).right);
return;
case AssignmentDeclarationKind.ThisProperty:
case AssignmentDeclarationKind.Property:
case AssignmentDeclarationKind.None:
case AssignmentDeclarationKind.Prototype:
case AssignmentDeclarationKind.PrototypeProperty: {
const binaryExpression = (node as BinaryExpression);
const assignmentTarget = binaryExpression.left as PropertyAccessExpression;
const prototypeAccess = special === AssignmentDeclarationKind.PrototypeProperty ?
assignmentTarget.expression as PropertyAccessExpression :
assignmentTarget;
let depth = 0;
let className: Identifier;
// If we see a prototype assignment, start tracking the target as a class
// This is only done for simple classes not nested assignments.
if (isIdentifier(prototypeAccess.expression)) {
addTrackedEs5Class(prototypeAccess.expression.text);
className = prototypeAccess.expression;
}
else {
[depth, className] = startNestedNodes(binaryExpression, prototypeAccess.expression as EntityNameExpression);
}
if (special === AssignmentDeclarationKind.Prototype) {
if (isObjectLiteralExpression(binaryExpression.right)) {
if (binaryExpression.right.properties.length > 0) {
startNode(binaryExpression, className);
forEachChild(binaryExpression.right, addChildrenRecursively);
endNode();
}
}
}
else if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) {
addNodeWithRecursiveChild(node,
binaryExpression.right,
className);
}
else {
startNode(binaryExpression, className);
addNodeWithRecursiveChild(node, binaryExpression.right, assignmentTarget.name);
endNode();
}
endNestedNodes(depth);
return;
}
case AssignmentDeclarationKind.ObjectDefinePropertyValue:
case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: {
const defineCall = node as BindableObjectDefinePropertyCall;
const className = special === AssignmentDeclarationKind.ObjectDefinePropertyValue ?
defineCall.arguments[0] :
(defineCall.arguments[0] as PropertyAccessExpression).expression as EntityNameExpression;
const memberName = defineCall.arguments[1];
const [depth, classNameIdentifier] = startNestedNodes(node, className);
startNode(node, classNameIdentifier);
startNode(node, setTextRange(createIdentifier(memberName.text), memberName));
addChildrenRecursively((node as CallExpression).arguments[2]);
endNode();
endNode();
endNestedNodes(depth);
return;
}
case AssignmentDeclarationKind.Property: {
const binaryExpression = (node as BinaryExpression);
const assignmentTarget = binaryExpression.left as PropertyAccessExpression;
const targetFunction = assignmentTarget.expression;
if (isIdentifier(targetFunction) && assignmentTarget.name.escapedText !== "prototype" &&
trackedEs5Classes && trackedEs5Classes.has(targetFunction.text)) {
if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) {
addNodeWithRecursiveChild(node, binaryExpression.right, targetFunction);
}
else {
startNode(binaryExpression, targetFunction);
addNodeWithRecursiveChild(binaryExpression.left, binaryExpression.right, assignmentTarget.name);
endNode();
}
return;
}
break;
}
case AssignmentDeclarationKind.ThisProperty:
case AssignmentDeclarationKind.None:
case AssignmentDeclarationKind.ObjectDefinePropertyExports:
case AssignmentDeclarationKind.ObjectDefinePrototypeProperty:
break;
default:
Debug.assertNever(special);
@@ -315,8 +425,8 @@ namespace ts.NavigationBar {
/** Merge declarations of the same kind. */
function mergeChildren(children: NavigationBarNode[], node: NavigationBarNode): void {
const nameToItems = createMap<NavigationBarNode | NavigationBarNode[]>();
filterMutate(children, child => {
const declName = getNameOfDeclaration(<Declaration>child.node);
filterMutate(children, (child, index) => {
const declName = child.name || getNameOfDeclaration(<Declaration>child.node);
const name = declName && nodeText(declName);
if (!name) {
// Anonymous items are never merged.
@@ -331,7 +441,7 @@ namespace ts.NavigationBar {
if (itemsWithSameName instanceof Array) {
for (const itemWithSameName of itemsWithSameName) {
if (tryMerge(itemWithSameName, child, node)) {
if (tryMerge(itemWithSameName, child, index, node)) {
return false;
}
}
@@ -340,7 +450,7 @@ namespace ts.NavigationBar {
}
else {
const itemWithSameName = itemsWithSameName;
if (tryMerge(itemWithSameName, child, node)) {
if (tryMerge(itemWithSameName, child, index, node)) {
return false;
}
nameToItems.set(name, [itemWithSameName, child]);
@@ -348,8 +458,116 @@ namespace ts.NavigationBar {
}
});
}
const isEs5ClassMember: Record<AssignmentDeclarationKind, boolean> = {
[AssignmentDeclarationKind.Property]: true,
[AssignmentDeclarationKind.PrototypeProperty]: true,
[AssignmentDeclarationKind.ObjectDefinePropertyValue]: true,
[AssignmentDeclarationKind.ObjectDefinePrototypeProperty]: true,
[AssignmentDeclarationKind.None]: false,
[AssignmentDeclarationKind.ExportsProperty]: false,
[AssignmentDeclarationKind.ModuleExports]: false,
[AssignmentDeclarationKind.ObjectDefinePropertyExports]: false,
[AssignmentDeclarationKind.Prototype]: true,
[AssignmentDeclarationKind.ThisProperty]: false,
};
function tryMergeEs5Class(a: NavigationBarNode, b: NavigationBarNode, bIndex: number, parent: NavigationBarNode): boolean | undefined {
function tryMerge(a: NavigationBarNode, b: NavigationBarNode, parent: NavigationBarNode): boolean {
function isPossibleConstructor(node: Node) {
return isFunctionExpression(node) || isFunctionDeclaration(node) || isVariableDeclaration(node);
}
const bAssignmentDeclarationKind = isBinaryExpression(b.node) || isCallExpression(b.node) ?
getAssignmentDeclarationKind(b.node) :
AssignmentDeclarationKind.None;
const aAssignmentDeclarationKind = isBinaryExpression(a.node) || isCallExpression(a.node) ?
getAssignmentDeclarationKind(a.node) :
AssignmentDeclarationKind.None;
// We treat this as an es5 class and merge the nodes in in one of several cases
if ((isEs5ClassMember[bAssignmentDeclarationKind] && isEs5ClassMember[aAssignmentDeclarationKind]) // merge two class elements
|| (isPossibleConstructor(a.node) && isEs5ClassMember[bAssignmentDeclarationKind]) // ctor function & member
|| (isPossibleConstructor(b.node) && isEs5ClassMember[aAssignmentDeclarationKind]) // member & ctor function
|| (isClassDeclaration(a.node) && isEs5ClassMember[bAssignmentDeclarationKind]) // class (generated) & member
|| (isClassDeclaration(b.node) && isEs5ClassMember[aAssignmentDeclarationKind]) // member & class (generated)
|| (isClassDeclaration(a.node) && isPossibleConstructor(b.node)) // class (generated) & ctor
|| (isClassDeclaration(b.node) && isPossibleConstructor(a.node)) // ctor & class (generated)
) {
let lastANode = a.additionalNodes && lastOrUndefined(a.additionalNodes) || a.node;
if ((!isClassDeclaration(a.node) && !isClassDeclaration(b.node)) // If neither outline node is a class
|| isPossibleConstructor(a.node) || isPossibleConstructor(b.node) // If either function is a constructor function
) {
const ctorFunction = isPossibleConstructor(a.node) ? a.node :
isPossibleConstructor(b.node) ? b.node :
undefined;
if (ctorFunction !== undefined) {
const ctorNode = setTextRange(
createConstructor(/* decorators */ undefined, /* modifiers */ undefined, [], /* body */ undefined),
ctorFunction);
const ctor = emptyNavigationBarNode(ctorNode);
ctor.indent = a.indent + 1;
ctor.children = a.node === ctorFunction ? a.children : b.children;
a.children = a.node === ctorFunction ? concatenate([ctor], b.children || [b]) : concatenate(a.children || [a], [ctor]);
}
else {
if (a.children || b.children) {
a.children = concatenate(a.children || [a], b.children || [b]);
if (a.children) {
mergeChildren(a.children, a);
sortChildren(a.children);
}
}
}
lastANode = a.node = setTextRange(createClassDeclaration(
/* decorators */ undefined,
/* modifiers */ undefined,
a.name as Identifier || createIdentifier("__class__"),
/* typeParameters */ undefined,
/* heritageClauses */ undefined,
[]
), a.node);
}
else {
a.children = concatenate(a.children, b.children);
if (a.children) {
mergeChildren(a.children, a);
}
}
const bNode = b.node;
// We merge if the outline node previous to b (bIndex - 1) is already part of the current class
// We do this so that statements between class members that do not generate outline nodes do not split up the class outline:
// Ex This should produce one outline node C:
// function C() {}; a = 1; C.prototype.m = function () {}
// Ex This will produce 3 outline nodes: C, a, C
// function C() {}; let a = 1; C.prototype.m = function () {}
if (parent.children![bIndex - 1].node.end === lastANode.end) {
setTextRange(lastANode, { pos: lastANode.pos, end: bNode.end });
}
else {
if (!a.additionalNodes) a.additionalNodes = [];
a.additionalNodes.push(setTextRange(createClassDeclaration(
/* decorators */ undefined,
/* modifiers */ undefined,
a.name as Identifier || createIdentifier("__class__"),
/* typeParameters */ undefined,
/* heritageClauses */ undefined,
[]
), b.node));
}
return true;
}
return bAssignmentDeclarationKind === AssignmentDeclarationKind.None ? false : true;
}
function tryMerge(a: NavigationBarNode, b: NavigationBarNode, bIndex: number, parent: NavigationBarNode): boolean {
// const v = false as boolean;
if (tryMergeEs5Class(a, b, bIndex, parent)) {
return true;
}
if (shouldReallyMerge(a.node, b.node, parent)) {
merge(a, b);
return true;
@@ -444,7 +662,7 @@ namespace ts.NavigationBar {
}
if (name) {
const text = nodeText(name);
const text = isIdentifier(name) ? name.text : nodeText(name);
if (text.length > 0) {
return cleanText(text);
}
+78 -13
View File
@@ -2,6 +2,7 @@
namespace ts.refactor {
const refactorName = "Extract type";
const extractToTypeAlias = "Extract to type alias";
const extractToInterface = "Extract to interface";
const extractToTypeDef = "Extract to typedef";
registerRefactor(refactorName, {
getAvailableActions(context): ReadonlyArray<ApplicableRefactorInfo> {
@@ -11,23 +12,35 @@ namespace ts.refactor {
return [{
name: refactorName,
description: getLocaleSpecificMessage(Diagnostics.Extract_type),
actions: [info.isJS ? {
actions: info.isJS ? [{
name: extractToTypeDef, description: getLocaleSpecificMessage(Diagnostics.Extract_to_typedef)
} : {
name: extractToTypeAlias, description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias)
}]
}] : append([{
name: extractToTypeAlias, description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias)
}], info.typeElements && {
name: extractToInterface, description: getLocaleSpecificMessage(Diagnostics.Extract_to_interface)
})
}];
},
getEditsForAction(context, actionName): RefactorEditInfo {
Debug.assert(actionName === extractToTypeAlias || actionName === extractToTypeDef, "Unexpected action name");
const { file } = context;
const info = Debug.assertDefined(getRangeToExtract(context), "Expected to find a range to extract");
Debug.assert(actionName === extractToTypeAlias && !info.isJS || actionName === extractToTypeDef && info.isJS, "Invalid actionName/JS combo");
const name = getUniqueName("NewType", file);
const edits = textChanges.ChangeTracker.with(context, changes => info.isJS ?
doTypedefChange(changes, file, name, info.firstStatement, info.selection, info.typeParameters) :
doTypeAliasChange(changes, file, name, info.firstStatement, info.selection, info.typeParameters));
const edits = textChanges.ChangeTracker.with(context, changes => {
switch (actionName) {
case extractToTypeAlias:
Debug.assert(!info.isJS, "Invalid actionName/JS combo");
return doTypeAliasChange(changes, file, name, info);
case extractToTypeDef:
Debug.assert(info.isJS, "Invalid actionName/JS combo");
return doTypedefChange(changes, file, name, info);
case extractToInterface:
Debug.assert(!info.isJS && !!info.typeElements, "Invalid actionName/JS combo");
return doInterfaceChange(changes, file, name, info as InterfaceInfo);
default:
Debug.fail("Unexpected action name");
}
});
const renameFilename = file.fileName;
const renameLocation = getRenameLocation(edits, renameFilename, name, /*preferLastLocation*/ false);
@@ -35,7 +48,15 @@ namespace ts.refactor {
}
});
interface Info { isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: ReadonlyArray<TypeParameterDeclaration>; }
interface TypeAliasInfo {
isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: ReadonlyArray<TypeParameterDeclaration>; typeElements?: ReadonlyArray<TypeElement>;
}
interface InterfaceInfo {
isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: ReadonlyArray<TypeParameterDeclaration>; typeElements: ReadonlyArray<TypeElement>;
}
type Info = TypeAliasInfo | InterfaceInfo;
function getRangeToExtract(context: RefactorContext): Info | undefined {
const { file, startPosition } = context;
@@ -51,7 +72,32 @@ namespace ts.refactor {
const typeParameters = collectTypeParameters(checker, selection, firstStatement, file);
if (!typeParameters) return undefined;
return { isJS, selection, firstStatement, typeParameters };
const typeElements = flattenTypeLiteralNodeReference(checker, selection);
return { isJS, selection, firstStatement, typeParameters, typeElements };
}
function flattenTypeLiteralNodeReference(checker: TypeChecker, node: TypeNode | undefined): ReadonlyArray<TypeElement> | undefined {
if (!node) return undefined;
if (isIntersectionTypeNode(node)) {
const result: TypeElement[] = [];
const seen = createMap<true>();
for (const type of node.types) {
const flattenedTypeMembers = flattenTypeLiteralNodeReference(checker, type);
if (!flattenedTypeMembers || !flattenedTypeMembers.every(type => type.name && addToSeen(seen, getNameFromPropertyName(type.name) as string))) {
return undefined;
}
addRange(result, flattenedTypeMembers);
}
return result;
}
else if (isParenthesizedTypeNode(node)) {
return flattenTypeLiteralNodeReference(checker, node.type);
}
else if (isTypeLiteralNode(node)) {
return node.members;
}
return undefined;
}
function isStatementAndHasJSDoc(n: Node): n is (Statement & HasJSDoc) {
@@ -107,7 +153,9 @@ namespace ts.refactor {
}
}
function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, firstStatement: Statement, selection: TypeNode, typeParameters: ReadonlyArray<TypeParameterDeclaration>) {
function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: TypeAliasInfo) {
const { firstStatement, selection, typeParameters } = info;
const newTypeNode = createTypeAliasDeclaration(
/* decorators */ undefined,
/* modifiers */ undefined,
@@ -119,7 +167,24 @@ namespace ts.refactor {
changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id.name, /* typeArguments */ undefined))));
}
function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, firstStatement: Statement, selection: TypeNode, typeParameters: ReadonlyArray<TypeParameterDeclaration>) {
function doInterfaceChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: InterfaceInfo) {
const { firstStatement, selection, typeParameters, typeElements } = info;
const newTypeNode = createInterfaceDeclaration(
/* decorators */ undefined,
/* modifiers */ undefined,
name,
typeParameters,
/* heritageClauses */ undefined,
typeElements
);
changes.insertNodeBefore(file, firstStatement, newTypeNode, /* blankLineBetween */ true);
changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id.name, /* typeArguments */ undefined))));
}
function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: Info) {
const { firstStatement, selection, typeParameters } = info;
const node = <JSDocTypedefTag>createNode(SyntaxKind.JSDocTypedefTag);
node.tagName = createIdentifier("typedef"); // TODO: jsdoc factory https://github.com/Microsoft/TypeScript/pull/29539
node.fullName = createIdentifier(name);
+1 -1
View File
@@ -854,7 +854,7 @@ namespace ts.textChanges {
const omitTrailingSemicolon = !!sourceFile && !probablyUsesSemicolons(sourceFile);
const writer = createWriter(newLineCharacter, omitTrailingSemicolon);
const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed;
createPrinter({ newLine, neverAsciiEscape: true, omitTrailingSemicolon }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
createPrinter({ newLine, neverAsciiEscape: true }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
return { text: writer.getText(), node: assignPositionsToNode(node) };
}
}
+1
View File
@@ -94,6 +94,7 @@
"unittests/tsbuild/amdModulesWithOut.ts",
"unittests/tsbuild/containerOnlyReferenced.ts",
"unittests/tsbuild/demo.ts",
"unittests/tsbuild/emitDeclarationOnly.ts",
"unittests/tsbuild/emptyFiles.ts",
"unittests/tsbuild/graphOrdering.ts",
"unittests/tsbuild/inferredTypeFromTransitiveModule.ts",
@@ -77,7 +77,7 @@ namespace ts {
[outputFiles[project.lib][ext.buildinfo], outputFiles[project.lib][ext.js], outputFiles[project.lib][ext.dts]],
[outputFiles[project.app][ext.buildinfo], outputFiles[project.app][ext.js], outputFiles[project.app][ext.dts]]
],
lastProjectOutputJs: outputFiles[project.app][ext.js],
lastProjectOutput: outputFiles[project.app][ext.js],
initialBuild: {
modifyFs
},
@@ -231,7 +231,7 @@ ${internal} export enum internalEnum { a, b, c }`);
[libOutputFile[ext.buildinfo], libOutputFile[ext.js], libOutputFile[ext.dts]],
[outputFiles[project.app][ext.buildinfo], outputFiles[project.app][ext.js], outputFiles[project.app][ext.dts]]
],
lastProjectOutputJs: outputFiles[project.app][ext.js],
lastProjectOutput: outputFiles[project.app][ext.js],
initialBuild: {
modifyFs,
expectedDiagnostics: [
@@ -0,0 +1,109 @@
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);
});
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: {
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"; }`);
},
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 { }
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
});
});
}
+52 -12
View File
@@ -102,7 +102,22 @@ namespace ts {
interface ReadonlyArray<T> {}
declare const console: { log(msg: any): void; };`;
export function loadProjectFromDisk(root: string, time?: vfs.FileSystemOptions["time"]): vfs.FileSystem {
export const symbolLibContent = `
interface SymbolConstructor {
readonly species: symbol;
readonly toStringTag: symbol;
}
declare var Symbol: SymbolConstructor;
interface Symbol {
readonly [Symbol.toStringTag]: string;
}
`;
export function loadProjectFromDisk(
root: string,
time?: vfs.FileSystemOptions["time"],
libContentToAppend?: string
): vfs.FileSystem {
const resolver = vfs.createResolver(Harness.IO);
const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
files: {
@@ -112,12 +127,31 @@ declare const console: { log(msg: any): void; };`;
meta: { defaultLibLocation: "/lib" },
time
});
fs.mkdirSync("/lib");
fs.writeFileSync("/lib/lib.d.ts", libContent);
fs.makeReadonly();
addLibAndMakeReadonly(fs, libContentToAppend);
return fs;
}
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;
}
function addLibAndMakeReadonly(fs: vfs.FileSystem, libContentToAppend?: string) {
fs.mkdirSync("/lib");
fs.writeFileSync("/lib/lib.d.ts", libContentToAppend ? `${libContent}${libContentToAppend}` : libContent);
fs.makeReadonly();
}
export function verifyOutputsPresent(fs: vfs.FileSystem, outputs: readonly string[]) {
for (const output of outputs) {
assert(fs.existsSync(output), `Expect file ${output} to exist`);
@@ -199,7 +233,7 @@ declare const console: { log(msg: any): void; };`;
fs: vfs.FileSystem;
tick: () => void;
rootNames: ReadonlyArray<string>;
expectedMapFileNames: ReadonlyArray<string>;
expectedMapFileNames?: ReadonlyArray<string>;
expectedBuildInfoFilesForSectionBaselines?: ReadonlyArray<BuildInfoSectionBaselineFiles>;
modifyFs: (fs: vfs.FileSystem) => void;
}
@@ -221,7 +255,7 @@ declare const console: { log(msg: any): void; };`;
return originalReadFile.call(host, path);
};
builder.build();
generateSourceMapBaselineFiles(fs, expectedMapFileNames);
if (expectedMapFileNames) generateSourceMapBaselineFiles(fs, expectedMapFileNames);
generateBuildInfoSectionBaselineFiles(fs, expectedBuildInfoFilesForSectionBaselines || emptyArray);
fs.makeReadonly();
return { fs, actualReadFileMap, host, builder };
@@ -268,9 +302,10 @@ Mismatch Actual(path, actual, expected): ${JSON.stringify(arrayFrom(mapDefinedIt
tick: () => void;
proj: string;
rootNames: ReadonlyArray<string>;
expectedMapFileNames: ReadonlyArray<string>;
/** map file names to generate baseline of */
expectedMapFileNames?: ReadonlyArray<string>;
expectedBuildInfoFilesForSectionBaselines?: ReadonlyArray<BuildInfoSectionBaselineFiles>;
lastProjectOutputJs: string;
lastProjectOutput: string;
initialBuild: BuildState;
outputFiles?: ReadonlyArray<string>;
incrementalDtsChangedBuild?: BuildState;
@@ -282,7 +317,7 @@ Mismatch Actual(path, actual, expected): ${JSON.stringify(arrayFrom(mapDefinedIt
export function verifyTsbuildOutput({
scenario, projFs, time, tick, proj, rootNames, outputFiles, baselineOnly, verifyDiagnostics,
expectedMapFileNames, expectedBuildInfoFilesForSectionBaselines, lastProjectOutputJs,
expectedMapFileNames, expectedBuildInfoFilesForSectionBaselines, lastProjectOutput,
initialBuild, incrementalDtsChangedBuild, incrementalDtsUnchangedBuild, incrementalHeaderChangedBuild
}: VerifyTsBuildInput) {
describe(`tsc --b ${proj}:: ${scenario}`, () => {
@@ -331,7 +366,7 @@ Mismatch Actual(path, actual, expected): ${JSON.stringify(arrayFrom(mapDefinedIt
let beforeBuildTime: number;
let afterBuildTime: number;
before(() => {
beforeBuildTime = fs.statSync(lastProjectOutputJs).mtimeMs;
beforeBuildTime = fs.statSync(lastProjectOutput).mtimeMs;
tick();
newFs = fs.shadow();
tick();
@@ -343,7 +378,7 @@ Mismatch Actual(path, actual, expected): ${JSON.stringify(arrayFrom(mapDefinedIt
expectedBuildInfoFilesForSectionBaselines,
modifyFs: incrementalModifyFs,
}));
afterBuildTime = newFs.statSync(lastProjectOutputJs).mtimeMs;
afterBuildTime = newFs.statSync(lastProjectOutput).mtimeMs;
});
after(() => {
newFs = undefined!;
@@ -359,6 +394,12 @@ Mismatch Actual(path, actual, expected): ${JSON.stringify(arrayFrom(mapDefinedIt
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);
});
@@ -373,7 +414,6 @@ Mismatch Actual(path, actual, expected): ${JSON.stringify(arrayFrom(mapDefinedIt
fs: newFs.shadow(),
tick,
rootNames,
expectedMapFileNames: emptyArray,
modifyFs: fs => {
// Delete output files
for (const outputFile of expectedOutputFiles) {
@@ -16,8 +16,7 @@ namespace ts {
tick,
proj: "inferredTypeFromTransitiveModule",
rootNames: ["/src"],
expectedMapFileNames: emptyArray,
lastProjectOutputJs: `/src/obj/index.js`,
lastProjectOutput: `/src/obj/index.js`,
outputFiles: [
"/src/obj/bar.js", "/src/obj/bar.d.ts",
"/src/obj/bundling.js", "/src/obj/bundling.d.ts",
@@ -16,8 +16,7 @@ namespace ts {
tick,
proj: "lateBoundSymbol",
rootNames: ["/src/tsconfig.json"],
expectedMapFileNames: emptyArray,
lastProjectOutputJs: "/src/src/main.js",
lastProjectOutput: "/src/src/main.js",
outputFiles: [
"/src/src/hkt.js",
"/src/src/main.js",
@@ -1,8 +1,10 @@
namespace ts {
// https://github.com/microsoft/TypeScript/issues/31696
it("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers to referenced projects resolve correctly", () => {
const baseFs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, {
files: {
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`
export declare type Nominal<T, Name extends string> = T & {
[Symbol.species]: Name;
@@ -71,7 +73,6 @@ namespace ts {
"skipLibCheck": true,
"rootDir": "./",
"outDir": "lib",
"lib": ["dom", "es2015", "es2015.symbol.wellknown"]
}
}`,
"/tsconfig.json": utils.dedent`{
@@ -83,16 +84,23 @@ namespace ts {
],
"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,
},
cwd: "/"
baselineOnly: true
});
const fs = baseFs.makeReadonly().shadow();
const sys = new fakes.System(fs, { executingFilePath: "/", newLine: "\n" });
const host = new fakes.SolutionBuilderHost(sys);
const builder = createSolutionBuilder(host, ["/tsconfig.json"], { dry: false, force: false, verbose: false });
builder.build();
// Prior to fixing GH31696 the import in `/lib/src/sub-project-2/index.d.ts` was `import("../../lib/src/common/nonterminal")`, which was invalid.
Harness.Baseline.runBaseline("tsbuild/moduleSpecifiers/initial-build/resolves-correctly.js", vfs.formatPatch(fs.diff(baseFs)));
});
}
}
+1 -1
View File
@@ -288,7 +288,7 @@ namespace ts {
rootNames: ["/src/third"],
expectedMapFileNames,
expectedBuildInfoFilesForSectionBaselines: expectedBuildInfoFilesForSectionBaselines || expectedTsbuildInfoFileNames,
lastProjectOutputJs: outputFiles[project.third][ext.js],
lastProjectOutput: outputFiles[project.third][ext.js],
initialBuild: {
modifyFs,
expectedDiagnostics: initialExpectedDiagnostics,
+7 -11
View File
@@ -617,7 +617,7 @@ export class cNew {}`);
"/src/core/index.d.ts.map",
"/src/logic/index.js.map"
],
lastProjectOutputJs: "/src/tests/index.js",
lastProjectOutput: "/src/tests/index.js",
initialBuild,
incrementalDtsChangedBuild: {
modifyFs: fs => appendText(fs, "/src/core/index.ts", `
@@ -727,7 +727,7 @@ class someClass { }`),
"/src/core/index.d.ts.map",
"/src/logic/index.js.map"
],
lastProjectOutputJs: "/src/tests/index.js",
lastProjectOutput: "/src/tests/index.js",
initialBuild,
incrementalDtsChangedBuild: {
modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"declaration": true,`, `"declaration": true,
@@ -795,7 +795,7 @@ class someClass { }`),
"/src/core/index.d.ts.map",
"/src/logic/index.js.map"
],
lastProjectOutputJs: "/src/tests/index.js",
lastProjectOutput: "/src/tests/index.js",
initialBuild: {
modifyFs: fs => replaceText(fs, "/src/logic/tsconfig.json", `"composite": true,`, `"composite": true,
"tsBuildInfoFile": "ownFile.tsbuildinfo",`),
@@ -851,8 +851,7 @@ class someClass { }`),
tick,
proj: "sample1",
rootNames: ["/src/core"],
expectedMapFileNames: emptyArray,
lastProjectOutputJs: "/src/core/index.js",
lastProjectOutput: "/src/core/index.js",
initialBuild: {
modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{
"compilerOptions": {
@@ -892,8 +891,7 @@ class someClass { }`),
tick,
proj: "sample1",
rootNames: ["/src/core"],
expectedMapFileNames: emptyArray,
lastProjectOutputJs: "/src/core/index.js",
lastProjectOutput: "/src/core/index.js",
initialBuild: {
modifyFs: fs => {
fs.writeFileSync("/lib/lib.esnext.full.d.ts", `/// <reference no-default-lib="true"/>
@@ -942,8 +940,7 @@ class someClass { }`),
tick,
proj: "sample1",
rootNames: ["/src/core"],
expectedMapFileNames: emptyArray,
lastProjectOutputJs: "/src/core/index.js",
lastProjectOutput: "/src/core/index.js",
initialBuild: {
modifyFs: fs => fs.writeFileSync("/src/core/tsconfig.json", `{
"compilerOptions": {
@@ -981,8 +978,7 @@ class someClass { }`),
tick,
proj: "sample1",
rootNames: ["/src/tests"],
expectedMapFileNames: emptyArray,
lastProjectOutputJs: "/src/tests/index.js",
lastProjectOutput: "/src/tests/index.js",
initialBuild: {
modifyFs: fs => fs.writeFileSync("/src/tests/tsconfig.json", `{
"references": [
+192 -16
View File
@@ -16,17 +16,17 @@ namespace ts.tscWatch {
expectedIncrementalEmit?: ReadonlyArray<File>;
expectedIncrementalErrors?: ReadonlyArray<string>;
}
function verifyIncrementalWatchEmit(input: VerifyIncrementalWatchEmitInput) {
function verifyIncrementalWatchEmit(input: () => VerifyIncrementalWatchEmitInput) {
it("with tsc --w", () => {
verifyIncrementalWatchEmitWorker({
input,
input: input(),
emitAndReportErrors: createWatchOfConfigFile,
verifyErrors: checkOutputErrorsInitial
});
});
it("with tsc", () => {
verifyIncrementalWatchEmitWorker({
input,
input: input(),
emitAndReportErrors: incrementalBuild,
verifyErrors: checkNormalBuildErrors
});
@@ -122,7 +122,7 @@ namespace ts.tscWatch {
function checkFileEmit(actual: Map<string>, expected: ReadonlyArray<File>) {
assert.equal(actual.size, expected.length, `Actual: ${JSON.stringify(arrayFrom(actual.entries()), /*replacer*/ undefined, " ")}\nExpected: ${JSON.stringify(expected, /*replacer*/ undefined, " ")}`);
expected.forEach(file => {
for (const file of expected) {
let expectedContent = file.content;
let actualContent = actual.get(file.path);
if (isBuildInfoFile(file.path)) {
@@ -130,7 +130,7 @@ namespace ts.tscWatch {
expectedContent = sanitizeBuildInfo(expectedContent);
}
assert.equal(actualContent, expectedContent, `Emit for ${file.path}`);
});
}
}
const libFileInfo: BuilderState.FileInfo = {
@@ -170,7 +170,7 @@ namespace ts.tscWatch {
describe("own file emit without errors", () => {
function verify(optionsToExtend?: CompilerOptions, expectedBuildinfoOptions?: CompilerOptions) {
const modifiedFile2Content = file2.content.replace("y", "z").replace("20", "10");
verifyIncrementalWatchEmit({
verifyIncrementalWatchEmit(() => ({
files: [libFile, file1, file2, configFile],
optionsToExtend,
expectedInitialEmit: [
@@ -226,7 +226,7 @@ namespace ts.tscWatch {
}
],
expectedIncrementalErrors: emptyArray,
});
}));
}
verify();
describe("with commandline parameters that are not relative", () => {
@@ -259,7 +259,7 @@ namespace ts.tscWatch {
"file2.ts(1,7): error TS2322: Type '20' is not assignable to type 'string'.\n"
];
const modifiedFile1Content = file1.content.replace("x", "z");
verifyIncrementalWatchEmit({
verifyIncrementalWatchEmit(() => ({
files: [libFile, file1, fileModified, configFile],
expectedInitialEmit: [
file1Js,
@@ -320,7 +320,7 @@ namespace ts.tscWatch {
}
],
expectedIncrementalErrors: file2Errors,
});
}));
});
describe("with --out", () => {
@@ -332,7 +332,7 @@ namespace ts.tscWatch {
path: `${project}/out.js`,
content: "var x = 10;\nvar y = 20;\n"
};
verifyIncrementalWatchEmit({
verifyIncrementalWatchEmit(() => ({
files: [libFile, file1, file2, config],
expectedInitialEmit: [
outFile,
@@ -353,7 +353,7 @@ namespace ts.tscWatch {
}
],
expectedInitialErrors: emptyArray
});
}));
});
});
@@ -397,7 +397,7 @@ namespace ts.tscWatch {
describe("own file emit without errors", () => {
const modifiedFile2Content = file2.content.replace("y", "z").replace("20", "10");
verifyIncrementalWatchEmit({
verifyIncrementalWatchEmit(() => ({
files: [libFile, file1, file2, config],
expectedInitialEmit: [
file1Js,
@@ -451,7 +451,7 @@ namespace ts.tscWatch {
}
],
expectedIncrementalErrors: emptyArray,
});
}));
});
describe("own file emit with errors", () => {
@@ -479,7 +479,7 @@ namespace ts.tscWatch {
"file2.ts(1,14): error TS2322: Type '20' is not assignable to type 'string'.\n"
];
const modifiedFile1Content = file1.content.replace("x = 10", "z = 10");
verifyIncrementalWatchEmit({
verifyIncrementalWatchEmit(() => ({
files: [libFile, file1, fileModified, config],
expectedInitialEmit: [
file1Js,
@@ -541,6 +541,49 @@ namespace ts.tscWatch {
}
],
expectedIncrementalErrors: file2Errors,
}));
it("verify that state is read correctly", () => {
const system = createWatchedSystem([libFile, file1, fileModified, config], { currentDirectory: project });
incrementalBuild("tsconfig.json", system);
const command = parseConfigFileWithSystem("tsconfig.json", {}, system, noop)!;
const builderProgram = createIncrementalProgram({
rootNames: command.fileNames,
options: command.options,
projectReferences: command.projectReferences,
configFileParsingDiagnostics: getConfigFileParsingDiagnostics(command),
host: createIncrementalCompilerHost(command.options, system)
});
const state = builderProgram.getState();
assert.equal(state.changedFilesSet!.size, 0, "changes");
assert.equal(state.fileInfos.size, 3, "FileInfo size");
assert.deepEqual(state.fileInfos.get(libFile.path), libFileInfo);
assert.deepEqual(state.fileInfos.get(file1.path), getFileInfo(file1.content));
assert.deepEqual(state.fileInfos.get(file2.path), file2FileInfo);
assert.deepEqual(state.compilerOptions, {
incremental: true,
module: ModuleKind.AMD,
configFilePath: config.path
});
assert.equal(state.referencedMap!.size, 0);
assert.equal(state.exportedModulesMap!.size, 0);
assert.equal(state.semanticDiagnosticsPerFile!.size, 3);
assert.deepEqual(state.semanticDiagnosticsPerFile!.get(libFile.path), emptyArray);
assert.deepEqual(state.semanticDiagnosticsPerFile!.get(file1.path), emptyArray);
const { file: _, relatedInformation: __, ...rest } = file2ReuasableError[1][0];
assert.deepEqual(state.semanticDiagnosticsPerFile!.get(file2.path), [{
...rest,
file: state.program!.getSourceFileByPath(file2.path as Path)!,
relatedInformation: undefined,
reportsUnnecessary: undefined,
source: undefined
}]);
});
});
@@ -561,7 +604,7 @@ namespace ts.tscWatch {
});
`;
}
verifyIncrementalWatchEmit({
verifyIncrementalWatchEmit(() => ({
files: [libFile, file1, file2, config],
expectedInitialEmit: [
outFile,
@@ -582,7 +625,140 @@ namespace ts.tscWatch {
}
],
expectedInitialErrors: emptyArray
});
}));
});
});
describe("incremental with circular references", () => {
function getFileInfo(content: string): BuilderState.FileInfo {
const signature = Harness.mockHash(content);
return { version: signature, signature };
}
const config: File = {
path: configFile.path,
content: JSON.stringify({
compilerOptions: {
incremental: true,
target: "es5",
module: "commonjs",
declaration: true,
emitDeclarationOnly: true
}
})
};
const aTs: File = {
path: `${project}/a.ts`,
content: `import { B } from "./b";
export interface A {
b: B;
}
`
};
const bTs: File = {
path: `${project}/b.ts`,
content: `import { C } from "./c";
export interface B {
b: C;
}
`
};
const cTs: File = {
path: `${project}/c.ts`,
content: `import { A } from "./a";
export interface C {
a: A;
}
`
};
const indexTs: File = {
path: `${project}/index.ts`,
content: `export { A } from "./a";
export { B } from "./b";
export { C } from "./c";
`
};
verifyIncrementalWatchEmit(() => {
const referencedMap: MapLike<string[]> = {
"./a.ts": ["./b.ts"],
"./b.ts": ["./c.ts"],
"./c.ts": ["./a.ts"],
"./index.ts": ["./a.ts", "./b.ts", "./c.ts"],
};
const initialProgram: ProgramBuildInfo = {
fileInfos: {
[libFilePath]: libFileInfo,
"./c.ts": getFileInfo(cTs.content),
"./b.ts": getFileInfo(bTs.content),
"./a.ts": getFileInfo(aTs.content),
"./index.ts": getFileInfo(indexTs.content)
},
options: {
incremental: true,
target: ScriptTarget.ES5,
module: ModuleKind.CommonJS,
declaration: true,
emitDeclarationOnly: true,
configFilePath: "./tsconfig.json"
},
referencedMap,
exportedModulesMap: referencedMap,
semanticDiagnosticsPerFile: [
libFilePath,
"./a.ts",
"./b.ts",
"./c.ts",
"./index.ts",
]
};
const { fileInfos, ...rest } = initialProgram;
const expectedADts: File = { path: `${project}/a.d.ts`, content: aTs.content };
const expectedBDts: File = { path: `${project}/b.d.ts`, content: bTs.content };
const expectedCDts: File = { path: `${project}/c.d.ts`, content: cTs.content };
const expectedIndexDts: File = { path: `${project}/index.d.ts`, content: indexTs.content };
const modifiedATsContent = aTs.content.replace("b: B;", `b: B;
foo: any;`);
return {
files: [libFile, aTs, bTs, cTs, indexTs, config],
expectedInitialEmit: [
expectedADts,
expectedBDts,
expectedCDts,
expectedIndexDts,
{
path: `${project}/tsconfig.tsbuildinfo`,
content: getBuildInfoText({
program: initialProgram,
version
})
}
],
expectedInitialErrors: emptyArray,
modifyFs: host => host.writeFile(aTs.path, modifiedATsContent),
expectedIncrementalEmit: [
{ path: expectedADts.path, content: modifiedATsContent },
expectedBDts,
expectedCDts,
expectedIndexDts,
{
path: `${project}/tsconfig.tsbuildinfo`,
content: getBuildInfoText({
program: {
fileInfos: {
[libFilePath]: libFileInfo,
"./c.ts": getFileInfo(cTs.content),
"./b.ts": getFileInfo(bTs.content),
"./a.ts": getFileInfo(modifiedATsContent),
"./index.ts": getFileInfo(indexTs.content)
},
...rest
},
version
})
}
],
expectedIncrementalErrors: emptyArray
};
});
});
});