Skip to content

Commit 17b1eb2

Browse files
committed
Refactoring support (squash)
1 parent 4cd20b1 commit 17b1eb2

26 files changed

+780
-79
lines changed

src/compiler/diagnosticMessages.json

+9
Original file line numberDiff line numberDiff line change
@@ -3584,6 +3584,15 @@
35843584
"code": 90022
35853585
},
35863586

3587+
"Convert function to an ES2015 class": {
3588+
"category": "Message",
3589+
"code": 95001
3590+
},
3591+
"Convert function '{0}' to class": {
3592+
"category": "Message",
3593+
"code": 95002
3594+
},
3595+
35873596
"Octal literal types must use ES2015 syntax. Use the syntax '{0}'.": {
35883597
"category": "Error",
35893598
"code": 8017

src/compiler/emitter.ts

+20-8
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,9 @@ namespace ts {
200200
onSetSourceFile,
201201
substituteNode,
202202
onBeforeEmitNodeArray,
203-
onAfterEmitNodeArray
203+
onAfterEmitNodeArray,
204+
onBeforeEmitToken,
205+
onAfterEmitToken
204206
} = handlers;
205207

206208
const newLine = getNewLineCharacter(printerOptions);
@@ -406,7 +408,7 @@ namespace ts {
406408
// Strict mode reserved words
407409
// Contextual keywords
408410
if (isKeyword(kind)) {
409-
writeTokenText(kind);
411+
writeTokenNode(node);
410412
return;
411413
}
412414

@@ -645,7 +647,7 @@ namespace ts {
645647
}
646648

647649
if (isToken(node)) {
648-
writeTokenText(kind);
650+
writeTokenNode(node);
649651
return;
650652
}
651653
}
@@ -672,7 +674,7 @@ namespace ts {
672674
case SyntaxKind.SuperKeyword:
673675
case SyntaxKind.TrueKeyword:
674676
case SyntaxKind.ThisKeyword:
675-
writeTokenText(kind);
677+
writeTokenNode(node);
676678
return;
677679

678680
// Expressions
@@ -1260,7 +1262,7 @@ namespace ts {
12601262
const operand = node.operand;
12611263
return operand.kind === SyntaxKind.PrefixUnaryExpression
12621264
&& ((node.operator === SyntaxKind.PlusToken && ((<PrefixUnaryExpression>operand).operator === SyntaxKind.PlusToken || (<PrefixUnaryExpression>operand).operator === SyntaxKind.PlusPlusToken))
1263-
|| (node.operator === SyntaxKind.MinusToken && ((<PrefixUnaryExpression>operand).operator === SyntaxKind.MinusToken || (<PrefixUnaryExpression>operand).operator === SyntaxKind.MinusMinusToken)));
1265+
|| (node.operator === SyntaxKind.MinusToken && ((<PrefixUnaryExpression>operand).operator === SyntaxKind.MinusToken || (<PrefixUnaryExpression>operand).operator === SyntaxKind.MinusMinusToken)));
12641266
}
12651267

12661268
function emitPostfixUnaryExpression(node: PostfixUnaryExpression) {
@@ -1275,7 +1277,7 @@ namespace ts {
12751277

12761278
emitExpression(node.left);
12771279
increaseIndentIf(indentBeforeOperator, isCommaOperator ? " " : undefined);
1278-
writeTokenText(node.operatorToken.kind);
1280+
writeTokenNode(node.operatorToken);
12791281
increaseIndentIf(indentAfterOperator, " ");
12801282
emitExpression(node.right);
12811283
decreaseIndentIf(indentBeforeOperator, indentAfterOperator);
@@ -2455,6 +2457,16 @@ namespace ts {
24552457
: writeTokenText(token, pos);
24562458
}
24572459

2460+
function writeTokenNode(node: Node) {
2461+
if (onBeforeEmitToken) {
2462+
onBeforeEmitToken(node);
2463+
}
2464+
writeTokenText(node.kind);
2465+
if (onAfterEmitToken) {
2466+
onAfterEmitToken(node);
2467+
}
2468+
}
2469+
24582470
function writeTokenText(token: SyntaxKind, pos?: number) {
24592471
const tokenString = tokenToString(token);
24602472
write(tokenString);
@@ -2928,9 +2940,9 @@ namespace ts {
29282940

29292941
// Flags enum to track count of temp variables and a few dedicated names
29302942
const enum TempFlags {
2931-
Auto = 0x00000000, // No preferred name
2943+
Auto = 0x00000000, // No preferred name
29322944
CountMask = 0x0FFFFFFF, // Temp variable counter
2933-
_i = 0x10000000, // Use/preference flag for '_i'
2945+
_i = 0x10000000, // Use/preference flag for '_i'
29342946
}
29352947

29362948
const enum ListFormat {

src/compiler/factory.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -984,10 +984,10 @@ namespace ts {
984984
return node;
985985
}
986986

987-
export function updateBinary(node: BinaryExpression, left: Expression, right: Expression) {
987+
export function updateBinary(node: BinaryExpression, left: Expression, right: Expression, operator?: BinaryOperator | BinaryOperatorToken) {
988988
return node.left !== left
989989
|| node.right !== right
990-
? updateNode(createBinary(left, node.operatorToken, right), node)
990+
? updateNode(createBinary(left, operator || node.operatorToken, right), node)
991991
: node;
992992
}
993993

src/compiler/types.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -3415,7 +3415,7 @@ namespace ts {
34153415
export enum DiagnosticCategory {
34163416
Warning,
34173417
Error,
3418-
Message,
3418+
Message
34193419
}
34203420

34213421
export enum ModuleResolutionKind {
@@ -4273,6 +4273,8 @@ namespace ts {
42734273
/*@internal*/ onSetSourceFile?: (node: SourceFile) => void;
42744274
/*@internal*/ onBeforeEmitNodeArray?: (nodes: NodeArray<any>) => void;
42754275
/*@internal*/ onAfterEmitNodeArray?: (nodes: NodeArray<any>) => void;
4276+
/*@internal*/ onBeforeEmitToken?: (node: Node) => void;
4277+
/*@internal*/ onAfterEmitToken?: (node: Node) => void;
42764278
}
42774279

42784280
export interface PrinterOptions {

src/compiler/visitor.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,8 @@ namespace ts {
516516
case SyntaxKind.BinaryExpression:
517517
return updateBinary(<BinaryExpression>node,
518518
visitNode((<BinaryExpression>node).left, visitor, isExpression),
519-
visitNode((<BinaryExpression>node).right, visitor, isExpression));
519+
visitNode((<BinaryExpression>node).right, visitor, isExpression),
520+
visitNode((<BinaryExpression>node).operatorToken, visitor, isToken));
520521

521522
case SyntaxKind.ConditionalExpression:
522523
return updateConditional(<ConditionalExpression>node,

src/harness/fourslash.ts

+68-1
Original file line numberDiff line numberDiff line change
@@ -2354,7 +2354,8 @@ namespace FourSlash {
23542354
private applyCodeAction(fileName: string, actions: ts.CodeAction[], index?: number): void {
23552355
if (index === undefined) {
23562356
if (!(actions && actions.length === 1)) {
2357-
this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found.`);
2357+
const actionText = (actions && actions.length) ? JSON.stringify(actions) : "none";
2358+
this.raiseError(`Should find exactly one codefix, but found ${actionText}`);
23582359
}
23592360
index = 0;
23602361
}
@@ -2708,6 +2709,60 @@ namespace FourSlash {
27082709
}
27092710
}
27102711

2712+
public verifyApplicableRefactorAvailableAtMarker(negative: boolean, markerName: string) {
2713+
const marker = this.getMarkerByName(markerName);
2714+
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, marker.position);
2715+
const isAvailable = applicableRefactors && applicableRefactors.length > 0;
2716+
if (negative && isAvailable) {
2717+
this.raiseError(`verifyApplicableRefactorAvailableAtMarker failed - expected no refactor at marker ${markerName} but found some.`);
2718+
}
2719+
if (!negative && !isAvailable) {
2720+
this.raiseError(`verifyApplicableRefactorAvailableAtMarker failed - expected a refactor at marker ${markerName} but found none.`);
2721+
}
2722+
}
2723+
2724+
public verifyApplicableRefactorAvailableForRange(negative: boolean) {
2725+
const ranges = this.getRanges();
2726+
if (!(ranges && ranges.length === 1)) {
2727+
throw new Error("Exactly one refactor range is allowed per test.");
2728+
}
2729+
2730+
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, { pos: ranges[0].start, end: ranges[0].end });
2731+
const isAvailable = applicableRefactors && applicableRefactors.length > 0;
2732+
if (negative && isAvailable) {
2733+
this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected no refactor but found some.`);
2734+
}
2735+
if (!negative && !isAvailable) {
2736+
this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected a refactor but found none.`);
2737+
}
2738+
}
2739+
2740+
public verifyFileAfterApplyingRefactorAtMarker(
2741+
markerName: string,
2742+
expectedContent: string,
2743+
refactorNameToApply: string,
2744+
formattingOptions?: ts.FormatCodeSettings) {
2745+
2746+
formattingOptions = formattingOptions || this.formatCodeSettings;
2747+
const markerPos = this.getMarkerByName(markerName).position;
2748+
2749+
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, markerPos);
2750+
const applicableRefactorToApply = ts.find(applicableRefactors, refactor => refactor.name === refactorNameToApply);
2751+
2752+
if (!applicableRefactorToApply) {
2753+
this.raiseError(`The expected refactor: ${refactorNameToApply} is not available at the marker location.`);
2754+
}
2755+
2756+
const codeActions = this.languageService.getRefactorCodeActions(this.activeFile.fileName, formattingOptions, markerPos, refactorNameToApply);
2757+
2758+
this.applyCodeAction(this.activeFile.fileName, codeActions);
2759+
const actualContent = this.getFileContent(this.activeFile.fileName);
2760+
2761+
if (this.normalizeNewlines(actualContent) !== this.normalizeNewlines(expectedContent)) {
2762+
this.raiseError(`verifyFileAfterApplyingRefactors failed: expected:\n${expectedContent}\nactual:\n${actualContent}`);
2763+
}
2764+
}
2765+
27112766
public printAvailableCodeFixes() {
27122767
const codeFixes = this.getCodeFixActions(this.activeFile.fileName);
27132768
Harness.IO.log(stringify(codeFixes));
@@ -3521,6 +3576,14 @@ namespace FourSlashInterface {
35213576
public codeFixAvailable() {
35223577
this.state.verifyCodeFixAvailable(this.negative);
35233578
}
3579+
3580+
public applicableRefactorAvailableAtMarker(markerName: string) {
3581+
this.state.verifyApplicableRefactorAvailableAtMarker(this.negative, markerName);
3582+
}
3583+
3584+
public applicableRefactorAvailableForRange() {
3585+
this.state.verifyApplicableRefactorAvailableForRange(this.negative);
3586+
}
35243587
}
35253588

35263589
export class Verify extends VerifyNegatable {
@@ -3735,6 +3798,10 @@ namespace FourSlashInterface {
37353798
this.state.verifyRangeAfterCodeFix(expectedText, includeWhiteSpace, errorCode, index);
37363799
}
37373800

3801+
public fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, formattingOptions?: ts.FormatCodeSettings): void {
3802+
this.state.verifyFileAfterApplyingRefactorAtMarker(markerName, expectedContent, refactorNameToApply, formattingOptions);
3803+
}
3804+
37383805
public importFixAtPosition(expectedTextArray: string[], errorCode?: number): void {
37393806
this.state.verifyImportFixAtPosition(expectedTextArray, errorCode);
37403807
}

src/harness/harness.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1983,5 +1983,5 @@ namespace Harness {
19831983
return { unitName: libFile, content: io.readFile(libFile) };
19841984
}
19851985

1986-
if (Error) (<any>Error).stackTraceLimit = 1;
1986+
if (Error) (<any>Error).stackTraceLimit = 100;
19871987
}

src/harness/harnessLanguageService.ts

+9
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,15 @@ namespace Harness.LanguageService {
489489
getCodeFixesAtPosition(): ts.CodeAction[] {
490490
throw new Error("Not supported on the shim.");
491491
}
492+
getCodeFixDiagnostics(): ts.Diagnostic[] {
493+
throw new Error("Not supported on the shim.");
494+
}
495+
getRefactorCodeActions(): ts.CodeAction[] {
496+
throw new Error("Not supported on the shim.");
497+
}
498+
getApplicableRefactors(): ts.ApplicableRefactorInfo[] {
499+
throw new Error("Not supported on the shim.");
500+
}
492501
getEmitOutput(fileName: string): ts.EmitOutput {
493502
return unwrapJSONCallResult(this.shim.getEmitOutput(fileName));
494503
}

src/harness/unittests/tsserverProjectSystem.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ namespace ts.projectSystem {
337337
this.map[timeoutId] = cb.bind(/*this*/ undefined, ...args);
338338
return timeoutId;
339339
}
340+
340341
unregister(id: any) {
341342
if (typeof id === "number") {
342343
delete this.map[id];
@@ -352,10 +353,13 @@ namespace ts.projectSystem {
352353
}
353354

354355
invoke() {
356+
// Note: invoking a callback may result in new callbacks been queued,
357+
// so do not clear the entire callback list regardless. Only remove the
358+
// ones we have invoked.
355359
for (const key in this.map) {
356360
this.map[key]();
361+
delete this.map[key];
357362
}
358-
this.map = [];
359363
}
360364
}
361365

@@ -3743,7 +3747,7 @@ namespace ts.projectSystem {
37433747

37443748
// run first step
37453749
host.runQueuedTimeoutCallbacks();
3746-
assert.equal(host.getOutput().length, 1, "expect 1 messages");
3750+
assert.equal(host.getOutput().length, 1, "expect 1 message");
37473751
const e1 = <protocol.Event>getMessage(0);
37483752
assert.equal(e1.event, "syntaxDiag");
37493753
host.clearOutput();
@@ -3765,11 +3769,12 @@ namespace ts.projectSystem {
37653769

37663770
// run first step
37673771
host.runQueuedTimeoutCallbacks();
3768-
assert.equal(host.getOutput().length, 1, "expect 1 messages");
3772+
assert.equal(host.getOutput().length, 1, "expect 1 message");
37693773
const e1 = <protocol.Event>getMessage(0);
37703774
assert.equal(e1.event, "syntaxDiag");
37713775
host.clearOutput();
37723776

3777+
// the semanticDiag message
37733778
host.runQueuedImmediateCallbacks();
37743779
assert.equal(host.getOutput().length, 2, "expect 2 messages");
37753780
const e2 = <protocol.Event>getMessage(0);
@@ -3787,7 +3792,7 @@ namespace ts.projectSystem {
37873792
assert.equal(host.getOutput().length, 0, "expect 0 messages");
37883793
// run first step
37893794
host.runQueuedTimeoutCallbacks();
3790-
assert.equal(host.getOutput().length, 1, "expect 1 messages");
3795+
assert.equal(host.getOutput().length, 1, "expect 1 message");
37913796
const e1 = <protocol.Event>getMessage(0);
37923797
assert.equal(e1.event, "syntaxDiag");
37933798
host.clearOutput();

src/server/client.ts

+40
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,46 @@ namespace ts.server {
695695
return response.body.map(entry => this.convertCodeActions(entry, fileName));
696696
}
697697

698+
private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs {
699+
if (typeof positionOrRange === "number") {
700+
const { line, offset } = this.positionToOneBasedLineOffset(fileName, positionOrRange);
701+
return <protocol.FileLocationRequestArgs>{ file: fileName, line, offset };
702+
}
703+
const { line: startLine, offset: startOffset } = this.positionToOneBasedLineOffset(fileName, positionOrRange.pos);
704+
const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(fileName, positionOrRange.end);
705+
return <protocol.FileRangeRequestArgs>{
706+
file: fileName,
707+
startLine,
708+
startOffset,
709+
endLine,
710+
endOffset
711+
};
712+
}
713+
714+
getApplicableRefactors(fileName: string, positionOrRange: number | TextRange): ApplicableRefactorInfo[] {
715+
const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName);
716+
717+
const request = this.processRequest<protocol.GetApplicableRefactorsRequest>(CommandNames.GetApplicableRefactors, args);
718+
const response = this.processResponse<protocol.GetApplicableRefactorsResponse>(request);
719+
return response.body;
720+
}
721+
722+
getRefactorCodeActions(
723+
fileName: string,
724+
_formatOptions: FormatCodeSettings,
725+
positionOrRange: number | TextRange,
726+
refactorName: string) {
727+
728+
const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetRefactorCodeActionsRequestArgs;
729+
args.refactorName = refactorName;
730+
731+
const request = this.processRequest<protocol.GetRefactorCodeActionsRequest>(CommandNames.GetRefactorCodeActions, args);
732+
const response = this.processResponse<protocol.GetRefactorCodeActionsResponse>(request);
733+
const codeActions = response.body.actions;
734+
735+
return map(codeActions, codeAction => this.convertCodeActions(codeAction, fileName));
736+
}
737+
698738
convertCodeActions(entry: protocol.CodeAction, fileName: string): CodeAction {
699739
return {
700740
description: entry.description,

0 commit comments

Comments
 (0)