Skip to content

Commit 49f1a79

Browse files
committed
Merge branch 'master' of https://github.com/Microsoft/TypeScript into feature/eslint
2 parents f8ab00b + cd371da commit 49f1a79

33 files changed

+645
-323
lines changed

src/compiler/checker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3519,7 +3519,7 @@ namespace ts {
35193519
const sig = nodeBuilder.signatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName);
35203520
const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true });
35213521
const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration);
3522-
printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonOmittingWriter(writer)); // TODO: GH#18217
3522+
printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonDeferringWriter(writer)); // TODO: GH#18217
35233523
return writer;
35243524
}
35253525
}

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5120,6 +5120,10 @@
51205120
"category": "Message",
51215121
"code": 95089
51225122
},
5123+
"Extract to interface": {
5124+
"category": "Message",
5125+
"code": 95090
5126+
},
51235127

51245128
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
51255129
"category": "Error",

src/compiler/emitter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,7 +1053,7 @@ namespace ts {
10531053

10541054
function setWriter(_writer: EmitTextWriter | undefined, _sourceMapGenerator: SourceMapGenerator | undefined) {
10551055
if (_writer && printerOptions.omitTrailingSemicolon) {
1056-
_writer = getTrailingSemicolonOmittingWriter(_writer);
1056+
_writer = getTrailingSemicolonDeferringWriter(_writer);
10571057
}
10581058

10591059
writer = _writer!; // TODO: GH#18217
@@ -2510,7 +2510,7 @@ namespace ts {
25102510
}
25112511

25122512
emitWhileClause(node, node.statement.end);
2513-
writePunctuation(";");
2513+
writeTrailingSemicolon();
25142514
}
25152515

25162516
function emitWhileStatement(node: WhileStatement) {

src/compiler/utilities.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3378,7 +3378,11 @@ namespace ts {
33783378
};
33793379
}
33803380

3381-
export function getTrailingSemicolonOmittingWriter(writer: EmitTextWriter): EmitTextWriter {
3381+
export interface TrailingSemicolonDeferringWriter extends EmitTextWriter {
3382+
resetPendingTrailingSemicolon(): void;
3383+
}
3384+
3385+
export function getTrailingSemicolonDeferringWriter(writer: EmitTextWriter): TrailingSemicolonDeferringWriter {
33823386
let pendingTrailingSemicolon = false;
33833387

33843388
function commitPendingTrailingSemicolon() {
@@ -3444,10 +3448,24 @@ namespace ts {
34443448
decreaseIndent() {
34453449
commitPendingTrailingSemicolon();
34463450
writer.decreaseIndent();
3451+
},
3452+
resetPendingTrailingSemicolon() {
3453+
pendingTrailingSemicolon = false;
34473454
}
34483455
};
34493456
}
34503457

3458+
export function getTrailingSemicolonOmittingWriter(writer: EmitTextWriter): EmitTextWriter {
3459+
const deferringWriter = getTrailingSemicolonDeferringWriter(writer);
3460+
return {
3461+
...deferringWriter,
3462+
writeLine() {
3463+
deferringWriter.resetPendingTrailingSemicolon();
3464+
writer.writeLine();
3465+
},
3466+
};
3467+
}
3468+
34513469
export function getResolvedExternalModuleName(host: EmitHost, file: SourceFile, referenceFile?: SourceFile): string {
34523470
return file.moduleName || getExternalModuleNameFromPath(host, file.fileName, referenceFile && referenceFile.fileName);
34533471
}

src/services/refactors/extractType.ts

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
namespace ts.refactor {
33
const refactorName = "Extract type";
44
const extractToTypeAlias = "Extract to type alias";
5+
const extractToInterface = "Extract to interface";
56
const extractToTypeDef = "Extract to typedef";
67
registerRefactor(refactorName, {
78
getAvailableActions(context): readonly ApplicableRefactorInfo[] {
@@ -11,31 +12,51 @@ namespace ts.refactor {
1112
return [{
1213
name: refactorName,
1314
description: getLocaleSpecificMessage(Diagnostics.Extract_type),
14-
actions: [info.isJS ? {
15+
actions: info.isJS ? [{
1516
name: extractToTypeDef, description: getLocaleSpecificMessage(Diagnostics.Extract_to_typedef)
16-
} : {
17+
}] : append([{
1718
name: extractToTypeAlias, description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias)
18-
}]
19+
}], info.typeElements && {
20+
name: extractToInterface, description: getLocaleSpecificMessage(Diagnostics.Extract_to_interface)
21+
})
1922
}];
2023
},
2124
getEditsForAction(context, actionName): RefactorEditInfo {
22-
Debug.assert(actionName === extractToTypeAlias || actionName === extractToTypeDef, "Unexpected action name");
2325
const { file } = context;
2426
const info = Debug.assertDefined(getRangeToExtract(context), "Expected to find a range to extract");
25-
Debug.assert(actionName === extractToTypeAlias && !info.isJS || actionName === extractToTypeDef && info.isJS, "Invalid actionName/JS combo");
2627

2728
const name = getUniqueName("NewType", file);
28-
const edits = textChanges.ChangeTracker.with(context, changes => info.isJS ?
29-
doTypedefChange(changes, file, name, info.firstStatement, info.selection, info.typeParameters) :
30-
doTypeAliasChange(changes, file, name, info.firstStatement, info.selection, info.typeParameters));
29+
const edits = textChanges.ChangeTracker.with(context, changes => {
30+
switch (actionName) {
31+
case extractToTypeAlias:
32+
Debug.assert(!info.isJS, "Invalid actionName/JS combo");
33+
return doTypeAliasChange(changes, file, name, info);
34+
case extractToTypeDef:
35+
Debug.assert(info.isJS, "Invalid actionName/JS combo");
36+
return doTypedefChange(changes, file, name, info);
37+
case extractToInterface:
38+
Debug.assert(!info.isJS && !!info.typeElements, "Invalid actionName/JS combo");
39+
return doInterfaceChange(changes, file, name, info as InterfaceInfo);
40+
default:
41+
Debug.fail("Unexpected action name");
42+
}
43+
});
3144

3245
const renameFilename = file.fileName;
3346
const renameLocation = getRenameLocation(edits, renameFilename, name, /*preferLastLocation*/ false);
3447
return { edits, renameFilename, renameLocation };
3548
}
3649
});
3750

38-
interface Info { isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: readonly TypeParameterDeclaration[]; }
51+
interface TypeAliasInfo {
52+
isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: readonly TypeParameterDeclaration[]; typeElements?: readonly TypeElement[];
53+
}
54+
55+
interface InterfaceInfo {
56+
isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: readonly TypeParameterDeclaration[]; typeElements: readonly TypeElement[];
57+
}
58+
59+
type Info = TypeAliasInfo | InterfaceInfo;
3960

4061
function getRangeToExtract(context: RefactorContext): Info | undefined {
4162
const { file, startPosition } = context;
@@ -51,7 +72,32 @@ namespace ts.refactor {
5172
const typeParameters = collectTypeParameters(checker, selection, firstStatement, file);
5273
if (!typeParameters) return undefined;
5374

54-
return { isJS, selection, firstStatement, typeParameters };
75+
const typeElements = flattenTypeLiteralNodeReference(checker, selection);
76+
return { isJS, selection, firstStatement, typeParameters, typeElements };
77+
}
78+
79+
function flattenTypeLiteralNodeReference(checker: TypeChecker, node: TypeNode | undefined): readonly TypeElement[] | undefined {
80+
if (!node) return undefined;
81+
if (isIntersectionTypeNode(node)) {
82+
const result: TypeElement[] = [];
83+
const seen = createMap<true>();
84+
for (const type of node.types) {
85+
const flattenedTypeMembers = flattenTypeLiteralNodeReference(checker, type);
86+
if (!flattenedTypeMembers || !flattenedTypeMembers.every(type => type.name && addToSeen(seen, getNameFromPropertyName(type.name) as string))) {
87+
return undefined;
88+
}
89+
90+
addRange(result, flattenedTypeMembers);
91+
}
92+
return result;
93+
}
94+
else if (isParenthesizedTypeNode(node)) {
95+
return flattenTypeLiteralNodeReference(checker, node.type);
96+
}
97+
else if (isTypeLiteralNode(node)) {
98+
return node.members;
99+
}
100+
return undefined;
55101
}
56102

57103
function isStatementAndHasJSDoc(n: Node): n is (Statement & HasJSDoc) {
@@ -107,7 +153,9 @@ namespace ts.refactor {
107153
}
108154
}
109155

110-
function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, firstStatement: Statement, selection: TypeNode, typeParameters: readonly TypeParameterDeclaration[]) {
156+
function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: TypeAliasInfo) {
157+
const { firstStatement, selection, typeParameters } = info;
158+
111159
const newTypeNode = createTypeAliasDeclaration(
112160
/* decorators */ undefined,
113161
/* modifiers */ undefined,
@@ -119,7 +167,24 @@ namespace ts.refactor {
119167
changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id.name, /* typeArguments */ undefined))));
120168
}
121169

122-
function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, firstStatement: Statement, selection: TypeNode, typeParameters: readonly TypeParameterDeclaration[]) {
170+
function doInterfaceChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: InterfaceInfo) {
171+
const { firstStatement, selection, typeParameters, typeElements } = info;
172+
173+
const newTypeNode = createInterfaceDeclaration(
174+
/* decorators */ undefined,
175+
/* modifiers */ undefined,
176+
name,
177+
typeParameters,
178+
/* heritageClauses */ undefined,
179+
typeElements
180+
);
181+
changes.insertNodeBefore(file, firstStatement, newTypeNode, /* blankLineBetween */ true);
182+
changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id.name, /* typeArguments */ undefined))));
183+
}
184+
185+
function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: Info) {
186+
const { firstStatement, selection, typeParameters } = info;
187+
123188
const node = <JSDocTypedefTag>createNode(SyntaxKind.JSDocTypedefTag);
124189
node.tagName = createIdentifier("typedef"); // TODO: jsdoc factory https://github.com/Microsoft/TypeScript/pull/29539
125190
node.fullName = createIdentifier(name);

src/services/textChanges.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@ namespace ts.textChanges {
854854
const omitTrailingSemicolon = !!sourceFile && !probablyUsesSemicolons(sourceFile);
855855
const writer = createWriter(newLineCharacter, omitTrailingSemicolon);
856856
const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed;
857-
createPrinter({ newLine, neverAsciiEscape: true, omitTrailingSemicolon }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
857+
createPrinter({ newLine, neverAsciiEscape: true }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
858858
return { text: writer.getText(), node: assignPositionsToNode(node) };
859859
}
860860
}

0 commit comments

Comments
 (0)