Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring support #15569

Merged
merged 1 commit into from
May 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3584,6 +3584,15 @@
"code": 90022
},

"Convert function to an ES2015 class": {
"category": "Message",
"code": 95001
},
"Convert function '{0}' to class": {
"category": "Message",
"code": 95002
},

"Octal literal types must use ES2015 syntax. Use the syntax '{0}'.": {
"category": "Error",
"code": 8017
Expand Down
28 changes: 20 additions & 8 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@ namespace ts {
onSetSourceFile,
substituteNode,
onBeforeEmitNodeArray,
onAfterEmitNodeArray
onAfterEmitNodeArray,
onBeforeEmitToken,
onAfterEmitToken
} = handlers;

const newLine = getNewLineCharacter(printerOptions);
Expand Down Expand Up @@ -406,7 +408,7 @@ namespace ts {
// Strict mode reserved words
// Contextual keywords
if (isKeyword(kind)) {
writeTokenText(kind);
writeTokenNode(node);
return;
}

Expand Down Expand Up @@ -645,7 +647,7 @@ namespace ts {
}

if (isToken(node)) {
writeTokenText(kind);
writeTokenNode(node);
return;
}
}
Expand All @@ -672,7 +674,7 @@ namespace ts {
case SyntaxKind.SuperKeyword:
case SyntaxKind.TrueKeyword:
case SyntaxKind.ThisKeyword:
writeTokenText(kind);
writeTokenNode(node);
return;

// Expressions
Expand Down Expand Up @@ -1260,7 +1262,7 @@ namespace ts {
const operand = node.operand;
return operand.kind === SyntaxKind.PrefixUnaryExpression
&& ((node.operator === SyntaxKind.PlusToken && ((<PrefixUnaryExpression>operand).operator === SyntaxKind.PlusToken || (<PrefixUnaryExpression>operand).operator === SyntaxKind.PlusPlusToken))
|| (node.operator === SyntaxKind.MinusToken && ((<PrefixUnaryExpression>operand).operator === SyntaxKind.MinusToken || (<PrefixUnaryExpression>operand).operator === SyntaxKind.MinusMinusToken)));
|| (node.operator === SyntaxKind.MinusToken && ((<PrefixUnaryExpression>operand).operator === SyntaxKind.MinusToken || (<PrefixUnaryExpression>operand).operator === SyntaxKind.MinusMinusToken)));
}

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

emitExpression(node.left);
increaseIndentIf(indentBeforeOperator, isCommaOperator ? " " : undefined);
writeTokenText(node.operatorToken.kind);
writeTokenNode(node.operatorToken);
increaseIndentIf(indentAfterOperator, " ");
emitExpression(node.right);
decreaseIndentIf(indentBeforeOperator, indentAfterOperator);
Expand Down Expand Up @@ -2455,6 +2457,16 @@ namespace ts {
: writeTokenText(token, pos);
}

function writeTokenNode(node: Node) {
if (onBeforeEmitToken) {
onBeforeEmitToken(node);
}
writeTokenText(node.kind);
if (onAfterEmitToken) {
onAfterEmitToken(node);
}
}

function writeTokenText(token: SyntaxKind, pos?: number) {
const tokenString = tokenToString(token);
write(tokenString);
Expand Down Expand Up @@ -2928,9 +2940,9 @@ namespace ts {

// Flags enum to track count of temp variables and a few dedicated names
const enum TempFlags {
Auto = 0x00000000, // No preferred name
Auto = 0x00000000, // No preferred name
CountMask = 0x0FFFFFFF, // Temp variable counter
_i = 0x10000000, // Use/preference flag for '_i'
_i = 0x10000000, // Use/preference flag for '_i'
}

const enum ListFormat {
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -984,10 +984,10 @@ namespace ts {
return node;
}

export function updateBinary(node: BinaryExpression, left: Expression, right: Expression) {
export function updateBinary(node: BinaryExpression, left: Expression, right: Expression, operator?: BinaryOperator | BinaryOperatorToken) {
return node.left !== left
|| node.right !== right
? updateNode(createBinary(left, node.operatorToken, right), node)
? updateNode(createBinary(left, operator || node.operatorToken, right), node)
: node;
}

Expand Down
4 changes: 3 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3415,7 +3415,7 @@ namespace ts {
export enum DiagnosticCategory {
Warning,
Error,
Message,
Message
}

export enum ModuleResolutionKind {
Expand Down Expand Up @@ -4273,6 +4273,8 @@ namespace ts {
/*@internal*/ onSetSourceFile?: (node: SourceFile) => void;
/*@internal*/ onBeforeEmitNodeArray?: (nodes: NodeArray<any>) => void;
/*@internal*/ onAfterEmitNodeArray?: (nodes: NodeArray<any>) => void;
/*@internal*/ onBeforeEmitToken?: (node: Node) => void;
/*@internal*/ onAfterEmitToken?: (node: Node) => void;
}

export interface PrinterOptions {
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,8 @@ namespace ts {
case SyntaxKind.BinaryExpression:
return updateBinary(<BinaryExpression>node,
visitNode((<BinaryExpression>node).left, visitor, isExpression),
visitNode((<BinaryExpression>node).right, visitor, isExpression));
visitNode((<BinaryExpression>node).right, visitor, isExpression),
visitNode((<BinaryExpression>node).operatorToken, visitor, isToken));

case SyntaxKind.ConditionalExpression:
return updateConditional(<ConditionalExpression>node,
Expand Down
69 changes: 68 additions & 1 deletion src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2354,7 +2354,8 @@ namespace FourSlash {
private applyCodeAction(fileName: string, actions: ts.CodeAction[], index?: number): void {
if (index === undefined) {
if (!(actions && actions.length === 1)) {
this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found.`);
const actionText = (actions && actions.length) ? JSON.stringify(actions) : "none";
this.raiseError(`Should find exactly one codefix, but found ${actionText}`);
}
index = 0;
}
Expand Down Expand Up @@ -2708,6 +2709,60 @@ namespace FourSlash {
}
}

public verifyApplicableRefactorAvailableAtMarker(negative: boolean, markerName: string) {
const marker = this.getMarkerByName(markerName);
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, marker.position);
const isAvailable = applicableRefactors && applicableRefactors.length > 0;
if (negative && isAvailable) {
this.raiseError(`verifyApplicableRefactorAvailableAtMarker failed - expected no refactor at marker ${markerName} but found some.`);
}
if (!negative && !isAvailable) {
this.raiseError(`verifyApplicableRefactorAvailableAtMarker failed - expected a refactor at marker ${markerName} but found none.`);
}
}

public verifyApplicableRefactorAvailableForRange(negative: boolean) {
const ranges = this.getRanges();
if (!(ranges && ranges.length === 1)) {
throw new Error("Exactly one refactor range is allowed per test.");
}

const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, { pos: ranges[0].start, end: ranges[0].end });
const isAvailable = applicableRefactors && applicableRefactors.length > 0;
if (negative && isAvailable) {
this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected no refactor but found some.`);
}
if (!negative && !isAvailable) {
this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected a refactor but found none.`);
}
}

public verifyFileAfterApplyingRefactorAtMarker(
markerName: string,
expectedContent: string,
refactorNameToApply: string,
formattingOptions?: ts.FormatCodeSettings) {

formattingOptions = formattingOptions || this.formatCodeSettings;
const markerPos = this.getMarkerByName(markerName).position;

const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, markerPos);
const applicableRefactorToApply = ts.find(applicableRefactors, refactor => refactor.name === refactorNameToApply);

if (!applicableRefactorToApply) {
this.raiseError(`The expected refactor: ${refactorNameToApply} is not available at the marker location.`);
}

const codeActions = this.languageService.getRefactorCodeActions(this.activeFile.fileName, formattingOptions, markerPos, refactorNameToApply);

this.applyCodeAction(this.activeFile.fileName, codeActions);
const actualContent = this.getFileContent(this.activeFile.fileName);

if (this.normalizeNewlines(actualContent) !== this.normalizeNewlines(expectedContent)) {
this.raiseError(`verifyFileAfterApplyingRefactors failed: expected:\n${expectedContent}\nactual:\n${actualContent}`);
}
}

public printAvailableCodeFixes() {
const codeFixes = this.getCodeFixActions(this.activeFile.fileName);
Harness.IO.log(stringify(codeFixes));
Expand Down Expand Up @@ -3521,6 +3576,14 @@ namespace FourSlashInterface {
public codeFixAvailable() {
this.state.verifyCodeFixAvailable(this.negative);
}

public applicableRefactorAvailableAtMarker(markerName: string) {
this.state.verifyApplicableRefactorAvailableAtMarker(this.negative, markerName);
}

public applicableRefactorAvailableForRange() {
this.state.verifyApplicableRefactorAvailableForRange(this.negative);
}
}

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

public fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, formattingOptions?: ts.FormatCodeSettings): void {
this.state.verifyFileAfterApplyingRefactorAtMarker(markerName, expectedContent, refactorNameToApply, formattingOptions);
}

public importFixAtPosition(expectedTextArray: string[], errorCode?: number): void {
this.state.verifyImportFixAtPosition(expectedTextArray, errorCode);
}
Expand Down
2 changes: 1 addition & 1 deletion src/harness/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1983,5 +1983,5 @@ namespace Harness {
return { unitName: libFile, content: io.readFile(libFile) };
}

if (Error) (<any>Error).stackTraceLimit = 1;
if (Error) (<any>Error).stackTraceLimit = 100;
}
9 changes: 9 additions & 0 deletions src/harness/harnessLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,15 @@ namespace Harness.LanguageService {
getCodeFixesAtPosition(): ts.CodeAction[] {
throw new Error("Not supported on the shim.");
}
getCodeFixDiagnostics(): ts.Diagnostic[] {
throw new Error("Not supported on the shim.");
}
getRefactorCodeActions(): ts.CodeAction[] {
throw new Error("Not supported on the shim.");
}
getApplicableRefactors(): ts.ApplicableRefactorInfo[] {
throw new Error("Not supported on the shim.");
}
getEmitOutput(fileName: string): ts.EmitOutput {
return unwrapJSONCallResult(this.shim.getEmitOutput(fileName));
}
Expand Down
13 changes: 9 additions & 4 deletions src/harness/unittests/tsserverProjectSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ namespace ts.projectSystem {
this.map[timeoutId] = cb.bind(/*this*/ undefined, ...args);
return timeoutId;
}

unregister(id: any) {
if (typeof id === "number") {
delete this.map[id];
Expand All @@ -352,10 +353,13 @@ namespace ts.projectSystem {
}

invoke() {
// Note: invoking a callback may result in new callbacks been queued,
// so do not clear the entire callback list regardless. Only remove the
// ones we have invoked.
for (const key in this.map) {
this.map[key]();
delete this.map[key];
}
this.map = [];
}
}

Expand Down Expand Up @@ -3743,7 +3747,7 @@ namespace ts.projectSystem {

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

// run first step
host.runQueuedTimeoutCallbacks();
assert.equal(host.getOutput().length, 1, "expect 1 messages");
assert.equal(host.getOutput().length, 1, "expect 1 message");
const e1 = <protocol.Event>getMessage(0);
assert.equal(e1.event, "syntaxDiag");
host.clearOutput();

// the semanticDiag message
host.runQueuedImmediateCallbacks();
assert.equal(host.getOutput().length, 2, "expect 2 messages");
const e2 = <protocol.Event>getMessage(0);
Expand All @@ -3787,7 +3792,7 @@ namespace ts.projectSystem {
assert.equal(host.getOutput().length, 0, "expect 0 messages");
// run first step
host.runQueuedTimeoutCallbacks();
assert.equal(host.getOutput().length, 1, "expect 1 messages");
assert.equal(host.getOutput().length, 1, "expect 1 message");
const e1 = <protocol.Event>getMessage(0);
assert.equal(e1.event, "syntaxDiag");
host.clearOutput();
Expand Down
40 changes: 40 additions & 0 deletions src/server/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,46 @@ namespace ts.server {
return response.body.map(entry => this.convertCodeActions(entry, fileName));
}

private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs {
if (typeof positionOrRange === "number") {
const { line, offset } = this.positionToOneBasedLineOffset(fileName, positionOrRange);
return <protocol.FileLocationRequestArgs>{ file: fileName, line, offset };
}
const { line: startLine, offset: startOffset } = this.positionToOneBasedLineOffset(fileName, positionOrRange.pos);
const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(fileName, positionOrRange.end);
return <protocol.FileRangeRequestArgs>{
file: fileName,
startLine,
startOffset,
endLine,
endOffset
};
}

getApplicableRefactors(fileName: string, positionOrRange: number | TextRange): ApplicableRefactorInfo[] {
const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName);

const request = this.processRequest<protocol.GetApplicableRefactorsRequest>(CommandNames.GetApplicableRefactors, args);
const response = this.processResponse<protocol.GetApplicableRefactorsResponse>(request);
return response.body;
}

getRefactorCodeActions(
fileName: string,
_formatOptions: FormatCodeSettings,
positionOrRange: number | TextRange,
refactorName: string) {

const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetRefactorCodeActionsRequestArgs;
args.refactorName = refactorName;

const request = this.processRequest<protocol.GetRefactorCodeActionsRequest>(CommandNames.GetRefactorCodeActions, args);
const response = this.processResponse<protocol.GetRefactorCodeActionsResponse>(request);
const codeActions = response.body.actions;

return map(codeActions, codeAction => this.convertCodeActions(codeAction, fileName));
}

convertCodeActions(entry: protocol.CodeAction, fileName: string): CodeAction {
return {
description: entry.description,
Expand Down
Loading