Merge pull request #15569 from RyanCavanaugh/new_refactor
Refactoring support
This commit is contained in:
commit
f489f5af4d
|
@ -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
|
||||
|
|
|
@ -200,7 +200,9 @@ namespace ts {
|
|||
onSetSourceFile,
|
||||
substituteNode,
|
||||
onBeforeEmitNodeArray,
|
||||
onAfterEmitNodeArray
|
||||
onAfterEmitNodeArray,
|
||||
onBeforeEmitToken,
|
||||
onAfterEmitToken
|
||||
} = handlers;
|
||||
|
||||
const newLine = getNewLineCharacter(printerOptions);
|
||||
|
@ -406,7 +408,7 @@ namespace ts {
|
|||
// Strict mode reserved words
|
||||
// Contextual keywords
|
||||
if (isKeyword(kind)) {
|
||||
writeTokenText(kind);
|
||||
writeTokenNode(node);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -645,7 +647,7 @@ namespace ts {
|
|||
}
|
||||
|
||||
if (isToken(node)) {
|
||||
writeTokenText(kind);
|
||||
writeTokenNode(node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -672,7 +674,7 @@ namespace ts {
|
|||
case SyntaxKind.SuperKeyword:
|
||||
case SyntaxKind.TrueKeyword:
|
||||
case SyntaxKind.ThisKeyword:
|
||||
writeTokenText(kind);
|
||||
writeTokenNode(node);
|
||||
return;
|
||||
|
||||
// Expressions
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -3415,7 +3415,7 @@ namespace ts {
|
|||
export enum DiagnosticCategory {
|
||||
Warning,
|
||||
Error,
|
||||
Message,
|
||||
Message
|
||||
}
|
||||
|
||||
export enum ModuleResolutionKind {
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
@ -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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -95,6 +95,10 @@ namespace ts.server.protocol {
|
|||
/* @internal */
|
||||
export type GetCodeFixesFull = "getCodeFixes-full";
|
||||
export type GetSupportedCodeFixes = "getSupportedCodeFixes";
|
||||
|
||||
export type GetApplicableRefactors = "getApplicableRefactors";
|
||||
export type GetRefactorCodeActions = "getRefactorCodeActions";
|
||||
export type GetRefactorCodeActionsFull = "getRefactorCodeActions-full";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -394,6 +398,54 @@ namespace ts.server.protocol {
|
|||
position?: number;
|
||||
}
|
||||
|
||||
export type FileLocationOrRangeRequestArgs = FileLocationRequestArgs | FileRangeRequestArgs;
|
||||
|
||||
export interface GetApplicableRefactorsRequest extends Request {
|
||||
command: CommandTypes.GetApplicableRefactors;
|
||||
arguments: GetApplicableRefactorsRequestArgs;
|
||||
}
|
||||
|
||||
export type GetApplicableRefactorsRequestArgs = FileLocationOrRangeRequestArgs;
|
||||
|
||||
export interface ApplicableRefactorInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface GetApplicableRefactorsResponse extends Response {
|
||||
body?: ApplicableRefactorInfo[];
|
||||
}
|
||||
|
||||
export interface GetRefactorCodeActionsRequest extends Request {
|
||||
command: CommandTypes.GetRefactorCodeActions;
|
||||
arguments: GetRefactorCodeActionsRequestArgs;
|
||||
}
|
||||
|
||||
export type GetRefactorCodeActionsRequestArgs = FileLocationOrRangeRequestArgs & {
|
||||
/* The kind of the applicable refactor */
|
||||
refactorName: string;
|
||||
};
|
||||
|
||||
export type RefactorCodeActions = {
|
||||
actions: protocol.CodeAction[];
|
||||
renameLocation?: number
|
||||
};
|
||||
|
||||
/* @internal */
|
||||
export type RefactorCodeActionsFull = {
|
||||
actions: ts.CodeAction[];
|
||||
renameLocation?: number
|
||||
};
|
||||
|
||||
export interface GetRefactorCodeActionsResponse extends Response {
|
||||
body: RefactorCodeActions;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export interface GetRefactorCodeActionsFullResponse extends Response {
|
||||
body: RefactorCodeActionsFull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request for the available codefixes at a specific position.
|
||||
*/
|
||||
|
@ -402,10 +454,7 @@ namespace ts.server.protocol {
|
|||
arguments: CodeFixRequestArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instances of this interface specify errorcodes on a specific location in a sourcefile.
|
||||
*/
|
||||
export interface CodeFixRequestArgs extends FileRequestArgs {
|
||||
export interface FileRangeRequestArgs extends FileRequestArgs {
|
||||
/**
|
||||
* The line number for the request (1-based).
|
||||
*/
|
||||
|
@ -437,7 +486,12 @@ namespace ts.server.protocol {
|
|||
*/
|
||||
/* @internal */
|
||||
endPosition?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instances of this interface specify errorcodes on a specific location in a sourcefile.
|
||||
*/
|
||||
export interface CodeFixRequestArgs extends FileRangeRequestArgs {
|
||||
/**
|
||||
* Errorcodes we want to get the fixes for.
|
||||
*/
|
||||
|
|
|
@ -100,7 +100,7 @@ namespace ts.server {
|
|||
}
|
||||
|
||||
export interface EventSender {
|
||||
event(payload: any, eventName: string): void;
|
||||
event<T>(payload: T, eventName: string): void;
|
||||
}
|
||||
|
||||
function allEditsBeforePos(edits: ts.TextChange[], pos: number) {
|
||||
|
@ -205,6 +205,10 @@ namespace ts.server {
|
|||
/* @internal */
|
||||
export const GetCodeFixesFull: protocol.CommandTypes.GetCodeFixesFull = "getCodeFixes-full";
|
||||
export const GetSupportedCodeFixes: protocol.CommandTypes.GetSupportedCodeFixes = "getSupportedCodeFixes";
|
||||
|
||||
export const GetApplicableRefactors: protocol.CommandTypes.GetApplicableRefactors = "getApplicableRefactors";
|
||||
export const GetRefactorCodeActions: protocol.CommandTypes.GetRefactorCodeActions = "getRefactorCodeActions";
|
||||
export const GetRefactorCodeActionsFull: protocol.CommandTypes.GetRefactorCodeActionsFull = "getRefactorCodeActions-full";
|
||||
}
|
||||
|
||||
export function formatMessage<T extends protocol.Message>(msg: T, logger: server.Logger, byteLength: (s: string, encoding: string) => number, newLine: string): string {
|
||||
|
@ -432,7 +436,7 @@ namespace ts.server {
|
|||
break;
|
||||
case ProjectLanguageServiceStateEvent:
|
||||
const eventName: protocol.ProjectLanguageServiceStateEventName = "projectLanguageServiceState";
|
||||
this.event(<protocol.ProjectLanguageServiceStateEventBody>{
|
||||
this.event<protocol.ProjectLanguageServiceStateEventBody>({
|
||||
projectName: event.data.project.getProjectName(),
|
||||
languageServiceEnabled: event.data.languageServiceEnabled
|
||||
}, eventName);
|
||||
|
@ -476,7 +480,7 @@ namespace ts.server {
|
|||
this.send(ev);
|
||||
}
|
||||
|
||||
public event(info: any, eventName: string) {
|
||||
public event<T>(info: T, eventName: string) {
|
||||
const ev: protocol.Event = {
|
||||
seq: 0,
|
||||
type: "event",
|
||||
|
@ -511,7 +515,7 @@ namespace ts.server {
|
|||
}
|
||||
|
||||
const bakedDiags = diags.map((diag) => formatDiag(file, project, diag));
|
||||
this.event({ file: file, diagnostics: bakedDiags }, "semanticDiag");
|
||||
this.event<protocol.DiagnosticEventBody>({ file: file, diagnostics: bakedDiags }, "semanticDiag");
|
||||
}
|
||||
catch (err) {
|
||||
this.logError(err, "semantic check");
|
||||
|
@ -523,7 +527,7 @@ namespace ts.server {
|
|||
const diags = project.getLanguageService().getSyntacticDiagnostics(file);
|
||||
if (diags) {
|
||||
const bakedDiags = diags.map((diag) => formatDiag(file, project, diag));
|
||||
this.event({ file: file, diagnostics: bakedDiags }, "syntaxDiag");
|
||||
this.event<protocol.DiagnosticEventBody>({ file: file, diagnostics: bakedDiags }, "syntaxDiag");
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
|
@ -1366,8 +1370,8 @@ namespace ts.server {
|
|||
return !items
|
||||
? undefined
|
||||
: simplifiedResult
|
||||
? this.decorateNavigationBarItems(items, project.getScriptInfoForNormalizedPath(file))
|
||||
: items;
|
||||
? this.decorateNavigationBarItems(items, project.getScriptInfoForNormalizedPath(file))
|
||||
: items;
|
||||
}
|
||||
|
||||
private decorateNavigationTree(tree: ts.NavigationTree, scriptInfo: ScriptInfo): protocol.NavigationTree {
|
||||
|
@ -1393,8 +1397,8 @@ namespace ts.server {
|
|||
return !tree
|
||||
? undefined
|
||||
: simplifiedResult
|
||||
? this.decorateNavigationTree(tree, project.getScriptInfoForNormalizedPath(file))
|
||||
: tree;
|
||||
? this.decorateNavigationTree(tree, project.getScriptInfoForNormalizedPath(file))
|
||||
: tree;
|
||||
}
|
||||
|
||||
private getNavigateToItems(args: protocol.NavtoRequestArgs, simplifiedResult: boolean): protocol.NavtoItem[] | NavigateToItem[] {
|
||||
|
@ -1481,6 +1485,60 @@ namespace ts.server {
|
|||
return ts.getSupportedCodeFixes();
|
||||
}
|
||||
|
||||
private isLocation(locationOrSpan: protocol.FileLocationOrRangeRequestArgs): locationOrSpan is protocol.FileLocationRequestArgs {
|
||||
return (<protocol.FileLocationRequestArgs>locationOrSpan).line !== undefined;
|
||||
}
|
||||
|
||||
private extractPositionAndRange(args: protocol.FileLocationOrRangeRequestArgs, scriptInfo: ScriptInfo): { position: number, textRange: TextRange } {
|
||||
let position: number = undefined;
|
||||
let textRange: TextRange;
|
||||
if (this.isLocation(args)) {
|
||||
position = getPosition(args);
|
||||
}
|
||||
else {
|
||||
const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo);
|
||||
textRange = { pos: startPosition, end: endPosition };
|
||||
}
|
||||
return { position, textRange };
|
||||
|
||||
function getPosition(loc: protocol.FileLocationRequestArgs) {
|
||||
return loc.position !== undefined ? loc.position : scriptInfo.lineOffsetToPosition(loc.line, loc.offset);
|
||||
}
|
||||
}
|
||||
|
||||
private getApplicableRefactors(args: protocol.GetApplicableRefactorsRequestArgs): protocol.ApplicableRefactorInfo[] {
|
||||
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
|
||||
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
|
||||
const { position, textRange } = this.extractPositionAndRange(args, scriptInfo);
|
||||
return project.getLanguageService().getApplicableRefactors(file, position || textRange);
|
||||
}
|
||||
|
||||
private getRefactorCodeActions(args: protocol.GetRefactorCodeActionsRequestArgs, simplifiedResult: boolean): protocol.RefactorCodeActions | protocol.RefactorCodeActionsFull {
|
||||
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
|
||||
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
|
||||
const { position, textRange } = this.extractPositionAndRange(args, scriptInfo);
|
||||
|
||||
const result: ts.CodeAction[] = project.getLanguageService().getRefactorCodeActions(
|
||||
file,
|
||||
this.projectService.getFormatCodeOptions(),
|
||||
position || textRange,
|
||||
args.refactorName
|
||||
);
|
||||
|
||||
if (simplifiedResult) {
|
||||
// Not full
|
||||
return {
|
||||
actions: result.map(action => this.mapCodeAction(action, scriptInfo))
|
||||
};
|
||||
}
|
||||
else {
|
||||
// Full
|
||||
return {
|
||||
actions: result
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): protocol.CodeAction[] | CodeAction[] {
|
||||
if (args.errorCodes.length === 0) {
|
||||
return undefined;
|
||||
|
@ -1488,8 +1546,7 @@ namespace ts.server {
|
|||
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
|
||||
|
||||
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
|
||||
const startPosition = getStartPosition();
|
||||
const endPosition = getEndPosition();
|
||||
const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo);
|
||||
const formatOptions = this.projectService.getFormatCodeOptions(file);
|
||||
|
||||
const codeActions = project.getLanguageService().getCodeFixesAtPosition(file, startPosition, endPosition, args.errorCodes, formatOptions);
|
||||
|
@ -1502,14 +1559,28 @@ namespace ts.server {
|
|||
else {
|
||||
return codeActions;
|
||||
}
|
||||
}
|
||||
|
||||
function getStartPosition() {
|
||||
return args.startPosition !== undefined ? args.startPosition : scriptInfo.lineOffsetToPosition(args.startLine, args.startOffset);
|
||||
private getStartAndEndPosition(args: protocol.FileRangeRequestArgs, scriptInfo: ScriptInfo) {
|
||||
let startPosition: number = undefined, endPosition: number = undefined;
|
||||
if (args.startPosition !== undefined) {
|
||||
startPosition = args.startPosition;
|
||||
}
|
||||
else {
|
||||
startPosition = scriptInfo.lineOffsetToPosition(args.startLine, args.startOffset);
|
||||
// save the result so we don't always recompute
|
||||
args.startPosition = startPosition;
|
||||
}
|
||||
|
||||
function getEndPosition() {
|
||||
return args.endPosition !== undefined ? args.endPosition : scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset);
|
||||
if (args.endPosition !== undefined) {
|
||||
endPosition = args.endPosition;
|
||||
}
|
||||
else {
|
||||
endPosition = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset);
|
||||
args.endPosition = endPosition;
|
||||
}
|
||||
|
||||
return { startPosition, endPosition };
|
||||
}
|
||||
|
||||
private mapCodeAction(codeAction: CodeAction, scriptInfo: ScriptInfo): protocol.CodeAction {
|
||||
|
@ -1540,8 +1611,8 @@ namespace ts.server {
|
|||
return !spans
|
||||
? undefined
|
||||
: simplifiedResult
|
||||
? spans.map(span => this.decorateSpan(span, scriptInfo))
|
||||
: spans;
|
||||
? spans.map(span => this.decorateSpan(span, scriptInfo))
|
||||
: spans;
|
||||
}
|
||||
|
||||
private getDiagnosticsForProject(next: NextStep, delay: number, fileName: string): void {
|
||||
|
@ -1846,6 +1917,15 @@ namespace ts.server {
|
|||
},
|
||||
[CommandNames.GetSupportedCodeFixes]: () => {
|
||||
return this.requiredResponse(this.getSupportedCodeFixes());
|
||||
},
|
||||
[CommandNames.GetApplicableRefactors]: (request: protocol.GetApplicableRefactorsRequest) => {
|
||||
return this.requiredResponse(this.getApplicableRefactors(request.arguments));
|
||||
},
|
||||
[CommandNames.GetRefactorCodeActions]: (request: protocol.GetRefactorCodeActionsRequest) => {
|
||||
return this.requiredResponse(this.getRefactorCodeActions(request.arguments, /*simplifiedResult*/ true));
|
||||
},
|
||||
[CommandNames.GetRefactorCodeActionsFull]: (request: protocol.GetRefactorCodeActionsRequest) => {
|
||||
return this.requiredResponse(this.getRefactorCodeActions(request.arguments, /*simplifiedResult*/ false));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1903,7 +1983,7 @@ namespace ts.server {
|
|||
let request: protocol.Request;
|
||||
try {
|
||||
request = <protocol.Request>JSON.parse(message);
|
||||
const {response, responseRequired} = this.executeCommand(request);
|
||||
const { response, responseRequired } = this.executeCommand(request);
|
||||
|
||||
if (this.logger.hasLevel(LogLevel.requestTime)) {
|
||||
const elapsedTime = hrTimeToMilliseconds(this.hrtime(start)).toFixed(4);
|
||||
|
|
|
@ -19,14 +19,14 @@ namespace ts {
|
|||
export namespace codefix {
|
||||
const codeFixes: CodeFix[][] = [];
|
||||
|
||||
export function registerCodeFix(action: CodeFix) {
|
||||
forEach(action.errorCodes, error => {
|
||||
export function registerCodeFix(codeFix: CodeFix) {
|
||||
forEach(codeFix.errorCodes, error => {
|
||||
let fixes = codeFixes[error];
|
||||
if (!fixes) {
|
||||
fixes = [];
|
||||
codeFixes[error] = fixes;
|
||||
}
|
||||
fixes.push(action);
|
||||
fixes.push(codeFix);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
69
src/services/refactorProvider.ts
Normal file
69
src/services/refactorProvider.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
/* @internal */
|
||||
namespace ts {
|
||||
export interface Refactor {
|
||||
/** An unique code associated with each refactor */
|
||||
name: string;
|
||||
|
||||
/** Description of the refactor to display in the UI of the editor */
|
||||
description: string;
|
||||
|
||||
/** Compute the associated code actions */
|
||||
getCodeActions(context: RefactorContext): CodeAction[];
|
||||
|
||||
/** A fast syntactic check to see if the refactor is applicable at given position. */
|
||||
isApplicable(context: RefactorContext): boolean;
|
||||
}
|
||||
|
||||
export interface RefactorContext {
|
||||
file: SourceFile;
|
||||
startPosition: number;
|
||||
endPosition?: number;
|
||||
program: Program;
|
||||
newLineCharacter: string;
|
||||
rulesProvider?: formatting.RulesProvider;
|
||||
cancellationToken?: CancellationToken;
|
||||
}
|
||||
|
||||
export namespace refactor {
|
||||
// A map with the refactor code as key, the refactor itself as value
|
||||
// e.g. nonSuggestableRefactors[refactorCode] -> the refactor you want
|
||||
const refactors: Map<Refactor> = createMap<Refactor>();
|
||||
|
||||
export function registerRefactor(refactor: Refactor) {
|
||||
refactors.set(refactor.name, refactor);
|
||||
}
|
||||
|
||||
export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
|
||||
|
||||
let results: ApplicableRefactorInfo[];
|
||||
const refactorList: Refactor[] = [];
|
||||
refactors.forEach(refactor => {
|
||||
refactorList.push(refactor);
|
||||
});
|
||||
for (const refactor of refactorList) {
|
||||
if (context.cancellationToken && context.cancellationToken.isCancellationRequested()) {
|
||||
return results;
|
||||
}
|
||||
if (refactor.isApplicable(context)) {
|
||||
(results || (results = [])).push({ name: refactor.name, description: refactor.description });
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
export function getRefactorCodeActions(context: RefactorContext, refactorName: string): CodeAction[] | undefined {
|
||||
|
||||
let result: CodeAction[];
|
||||
const refactor = refactors.get(refactorName);
|
||||
if (!refactor) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const codeActions = refactor.getCodeActions(context);
|
||||
if (codeActions) {
|
||||
addRange((result || (result = [])), codeActions);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
209
src/services/refactors/convertFunctionToEs6Class.ts
Normal file
209
src/services/refactors/convertFunctionToEs6Class.ts
Normal file
|
@ -0,0 +1,209 @@
|
|||
/* @internal */
|
||||
|
||||
namespace ts.refactor {
|
||||
const convertFunctionToES6Class: Refactor = {
|
||||
name: "Convert to ES2015 class",
|
||||
description: Diagnostics.Convert_function_to_an_ES2015_class.message,
|
||||
getCodeActions,
|
||||
isApplicable
|
||||
};
|
||||
|
||||
registerRefactor(convertFunctionToES6Class);
|
||||
|
||||
function isApplicable(context: RefactorContext): boolean {
|
||||
const start = context.startPosition;
|
||||
const node = getTokenAtPosition(context.file, start);
|
||||
const checker = context.program.getTypeChecker();
|
||||
let symbol = checker.getSymbolAtLocation(node);
|
||||
|
||||
if (symbol && isDeclarationOfFunctionOrClassExpression(symbol)) {
|
||||
symbol = (symbol.valueDeclaration as VariableDeclaration).initializer.symbol;
|
||||
}
|
||||
|
||||
return symbol && symbol.flags & SymbolFlags.Function && symbol.members && symbol.members.size > 0;
|
||||
}
|
||||
|
||||
function getCodeActions(context: RefactorContext): CodeAction[] | undefined {
|
||||
const start = context.startPosition;
|
||||
const sourceFile = context.file;
|
||||
const checker = context.program.getTypeChecker();
|
||||
const token = getTokenAtPosition(sourceFile, start);
|
||||
const ctorSymbol = checker.getSymbolAtLocation(token);
|
||||
const newLine = context.rulesProvider.getFormatOptions().newLineCharacter;
|
||||
|
||||
const deletedNodes: Node[] = [];
|
||||
const deletes: (() => any)[] = [];
|
||||
|
||||
if (!(ctorSymbol.flags & (SymbolFlags.Function | SymbolFlags.Variable))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const ctorDeclaration = ctorSymbol.valueDeclaration;
|
||||
const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context as { newLineCharacter: string, rulesProvider: formatting.RulesProvider });
|
||||
|
||||
let precedingNode: Node;
|
||||
let newClassDeclaration: ClassDeclaration;
|
||||
switch (ctorDeclaration.kind) {
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
precedingNode = ctorDeclaration;
|
||||
deleteNode(ctorDeclaration);
|
||||
newClassDeclaration = createClassFromFunctionDeclaration(ctorDeclaration as FunctionDeclaration);
|
||||
break;
|
||||
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
precedingNode = ctorDeclaration.parent.parent;
|
||||
if ((<VariableDeclarationList>ctorDeclaration.parent).declarations.length === 1) {
|
||||
deleteNode(precedingNode);
|
||||
}
|
||||
else {
|
||||
deleteNode(ctorDeclaration, /*inList*/ true);
|
||||
}
|
||||
newClassDeclaration = createClassFromVariableDeclaration(ctorDeclaration as VariableDeclaration);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!newClassDeclaration) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Because the preceding node could be touched, we need to insert nodes before delete nodes.
|
||||
changeTracker.insertNodeAfter(sourceFile, precedingNode, newClassDeclaration, { suffix: newLine });
|
||||
for (const deleteCallback of deletes) {
|
||||
deleteCallback();
|
||||
}
|
||||
|
||||
return [{
|
||||
description: formatStringFromArgs(Diagnostics.Convert_function_0_to_class.message, [ctorSymbol.name]),
|
||||
changes: changeTracker.getChanges()
|
||||
}];
|
||||
|
||||
function deleteNode(node: Node, inList = false) {
|
||||
if (deletedNodes.some(n => isNodeDescendantOf(node, n))) {
|
||||
// Parent node has already been deleted; do nothing
|
||||
return;
|
||||
}
|
||||
deletedNodes.push(node);
|
||||
if (inList) {
|
||||
deletes.push(() => changeTracker.deleteNodeInList(sourceFile, node));
|
||||
}
|
||||
else {
|
||||
deletes.push(() => changeTracker.deleteNode(sourceFile, node));
|
||||
}
|
||||
}
|
||||
|
||||
function createClassElementsFromSymbol(symbol: Symbol) {
|
||||
const memberElements: ClassElement[] = [];
|
||||
// all instance members are stored in the "member" array of symbol
|
||||
if (symbol.members) {
|
||||
symbol.members.forEach(member => {
|
||||
const memberElement = createClassElement(member, /*modifiers*/ undefined);
|
||||
if (memberElement) {
|
||||
memberElements.push(memberElement);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// all static members are stored in the "exports" array of symbol
|
||||
if (symbol.exports) {
|
||||
symbol.exports.forEach(member => {
|
||||
const memberElement = createClassElement(member, [createToken(SyntaxKind.StaticKeyword)]);
|
||||
if (memberElement) {
|
||||
memberElements.push(memberElement);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return memberElements;
|
||||
|
||||
function shouldConvertDeclaration(_target: PropertyAccessExpression, source: Expression) {
|
||||
// Right now the only thing we can convert are function expressions - other values shouldn't get
|
||||
// transformed. We can update this once ES public class properties are available.
|
||||
return isFunctionLike(source);
|
||||
}
|
||||
|
||||
function createClassElement(symbol: Symbol, modifiers: Modifier[]): ClassElement {
|
||||
// both properties and methods are bound as property symbols
|
||||
if (!(symbol.flags & SymbolFlags.Property)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const memberDeclaration = symbol.valueDeclaration as PropertyAccessExpression;
|
||||
const assignmentBinaryExpression = memberDeclaration.parent as BinaryExpression;
|
||||
|
||||
if (!shouldConvertDeclaration(memberDeclaration, assignmentBinaryExpression.right)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// delete the entire statement if this expression is the sole expression to take care of the semicolon at the end
|
||||
const nodeToDelete = assignmentBinaryExpression.parent && assignmentBinaryExpression.parent.kind === SyntaxKind.ExpressionStatement
|
||||
? assignmentBinaryExpression.parent : assignmentBinaryExpression;
|
||||
deleteNode(nodeToDelete);
|
||||
|
||||
if (!assignmentBinaryExpression.right) {
|
||||
return createProperty([], modifiers, symbol.name, /*questionToken*/ undefined,
|
||||
/*type*/ undefined, /*initializer*/ undefined);
|
||||
}
|
||||
|
||||
switch (assignmentBinaryExpression.right.kind) {
|
||||
case SyntaxKind.FunctionExpression:
|
||||
const functionExpression = assignmentBinaryExpression.right as FunctionExpression;
|
||||
return createMethod(/*decorators*/ undefined, modifiers, /*asteriskToken*/ undefined, memberDeclaration.name, /*questionToken*/ undefined,
|
||||
/*typeParameters*/ undefined, functionExpression.parameters, /*type*/ undefined, functionExpression.body);
|
||||
|
||||
case SyntaxKind.ArrowFunction:
|
||||
const arrowFunction = assignmentBinaryExpression.right as ArrowFunction;
|
||||
const arrowFunctionBody = arrowFunction.body;
|
||||
let bodyBlock: Block;
|
||||
|
||||
// case 1: () => { return [1,2,3] }
|
||||
if (arrowFunctionBody.kind === SyntaxKind.Block) {
|
||||
bodyBlock = arrowFunctionBody as Block;
|
||||
}
|
||||
// case 2: () => [1,2,3]
|
||||
else {
|
||||
const expression = arrowFunctionBody as Expression;
|
||||
bodyBlock = createBlock([createReturn(expression)]);
|
||||
}
|
||||
return createMethod(/*decorators*/ undefined, modifiers, /*asteriskToken*/ undefined, memberDeclaration.name, /*questionToken*/ undefined,
|
||||
/*typeParameters*/ undefined, arrowFunction.parameters, /*type*/ undefined, bodyBlock);
|
||||
|
||||
default:
|
||||
// Don't try to declare members in JavaScript files
|
||||
if (isSourceFileJavaScript(sourceFile)) {
|
||||
return;
|
||||
}
|
||||
return createProperty(/*decorators*/ undefined, modifiers, memberDeclaration.name, /*questionToken*/ undefined,
|
||||
/*type*/ undefined, assignmentBinaryExpression.right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createClassFromVariableDeclaration(node: VariableDeclaration): ClassDeclaration {
|
||||
const initializer = node.initializer as FunctionExpression;
|
||||
if (!initializer || initializer.kind !== SyntaxKind.FunctionExpression) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (node.name.kind !== SyntaxKind.Identifier) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const memberElements = createClassElementsFromSymbol(initializer.symbol);
|
||||
if (initializer.body) {
|
||||
memberElements.unshift(createConstructor(/*decorators*/ undefined, /*modifiers*/ undefined, initializer.parameters, initializer.body));
|
||||
}
|
||||
|
||||
return createClassDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, node.name,
|
||||
/*typeParameters*/ undefined, /*heritageClauses*/ undefined, memberElements);
|
||||
}
|
||||
|
||||
function createClassFromFunctionDeclaration(node: FunctionDeclaration): ClassDeclaration {
|
||||
const memberElements = createClassElementsFromSymbol(ctorSymbol);
|
||||
if (node.body) {
|
||||
memberElements.unshift(createConstructor(/*decorators*/ undefined, /*modifiers*/ undefined, node.parameters, node.body));
|
||||
}
|
||||
return createClassDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, node.name,
|
||||
/*typeParameters*/ undefined, /*heritageClauses*/ undefined, memberElements);
|
||||
}
|
||||
}
|
||||
}
|
1
src/services/refactors/refactors.ts
Normal file
1
src/services/refactors/refactors.ts
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference path="convertFunctionToEs6Class.ts" />
|
|
@ -25,7 +25,9 @@
|
|||
/// <reference path='formatting\smartIndenter.ts' />
|
||||
/// <reference path='textChanges.ts' />
|
||||
/// <reference path='codeFixProvider.ts' />
|
||||
/// <reference path='refactorProvider.ts' />
|
||||
/// <reference path='codefixes\fixes.ts' />
|
||||
/// <reference path='refactors\refactors.ts' />
|
||||
|
||||
namespace ts {
|
||||
/** The version of the language service API */
|
||||
|
@ -1959,11 +1961,43 @@ namespace ts {
|
|||
return Rename.getRenameInfo(program.getTypeChecker(), defaultLibFileName, getCanonicalFileName, getValidSourceFile(fileName), position);
|
||||
}
|
||||
|
||||
function getRefactorContext(file: SourceFile, positionOrRange: number | TextRange, formatOptions?: FormatCodeSettings): RefactorContext {
|
||||
const [startPosition, endPosition] = typeof positionOrRange === "number" ? [positionOrRange, undefined] : [positionOrRange.pos, positionOrRange.end];
|
||||
return {
|
||||
file,
|
||||
startPosition,
|
||||
endPosition,
|
||||
program: getProgram(),
|
||||
newLineCharacter: host.getNewLine(),
|
||||
rulesProvider: getRuleProvider(formatOptions),
|
||||
cancellationToken
|
||||
};
|
||||
}
|
||||
|
||||
function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange): ApplicableRefactorInfo[] {
|
||||
synchronizeHostData();
|
||||
const file = getValidSourceFile(fileName);
|
||||
return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange));
|
||||
}
|
||||
|
||||
function getRefactorCodeActions(
|
||||
fileName: string,
|
||||
formatOptions: FormatCodeSettings,
|
||||
positionOrRange: number | TextRange,
|
||||
refactorName: string): CodeAction[] | undefined {
|
||||
|
||||
synchronizeHostData();
|
||||
const file = getValidSourceFile(fileName);
|
||||
return refactor.getRefactorCodeActions(getRefactorContext(file, positionOrRange, formatOptions), refactorName);
|
||||
}
|
||||
|
||||
return {
|
||||
dispose,
|
||||
cleanupSemanticCache,
|
||||
getSyntacticDiagnostics,
|
||||
getSemanticDiagnostics,
|
||||
getApplicableRefactors,
|
||||
getRefactorCodeActions,
|
||||
getCompilerOptionsDiagnostics,
|
||||
getSyntacticClassifications,
|
||||
getSemanticClassifications,
|
||||
|
|
|
@ -157,7 +157,7 @@ namespace ts.textChanges {
|
|||
private changes: Change[] = [];
|
||||
private readonly newLineCharacter: string;
|
||||
|
||||
public static fromCodeFixContext(context: CodeFixContext) {
|
||||
public static fromCodeFixContext(context: { newLineCharacter: string, rulesProvider: formatting.RulesProvider }) {
|
||||
return new ChangeTracker(context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.rulesProvider);
|
||||
}
|
||||
|
||||
|
@ -254,9 +254,9 @@ namespace ts.textChanges {
|
|||
|
||||
public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node, options: InsertNodeOptions & ConfigurableEnd = {}) {
|
||||
if ((isStatementButNotDeclaration(after)) ||
|
||||
after.kind === SyntaxKind.PropertyDeclaration ||
|
||||
after.kind === SyntaxKind.PropertySignature ||
|
||||
after.kind === SyntaxKind.MethodSignature) {
|
||||
after.kind === SyntaxKind.PropertyDeclaration ||
|
||||
after.kind === SyntaxKind.PropertySignature ||
|
||||
after.kind === SyntaxKind.MethodSignature) {
|
||||
// check if previous statement ends with semicolon
|
||||
// if not - insert semicolon to preserve the code from changing the meaning due to ASI
|
||||
if (sourceFile.text.charCodeAt(after.end - 1) !== CharacterCodes.semicolon) {
|
||||
|
@ -481,7 +481,7 @@ namespace ts.textChanges {
|
|||
return (options.prefix || "") + text + (options.suffix || "");
|
||||
}
|
||||
|
||||
private static normalize(changes: Change[]) {
|
||||
private static normalize(changes: Change[]): Change[] {
|
||||
// order changes by start position
|
||||
const normalized = stableSort(changes, (a, b) => a.range.pos - b.range.pos);
|
||||
// verify that change intervals do not overlap, except possibly at end points.
|
||||
|
@ -560,6 +560,8 @@ namespace ts.textChanges {
|
|||
public readonly onEmitNode: PrintHandlers["onEmitNode"];
|
||||
public readonly onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"];
|
||||
public readonly onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"];
|
||||
public readonly onBeforeEmitToken: PrintHandlers["onBeforeEmitToken"];
|
||||
public readonly onAfterEmitToken: PrintHandlers["onAfterEmitToken"];
|
||||
|
||||
constructor(newLine: string) {
|
||||
this.writer = createTextWriter(newLine);
|
||||
|
@ -582,6 +584,16 @@ namespace ts.textChanges {
|
|||
setEnd(nodes, this.lastNonTriviaPosition);
|
||||
}
|
||||
};
|
||||
this.onBeforeEmitToken = node => {
|
||||
if (node) {
|
||||
setPos(node, this.lastNonTriviaPosition);
|
||||
}
|
||||
};
|
||||
this.onAfterEmitToken = node => {
|
||||
if (node) {
|
||||
setEnd(node, this.lastNonTriviaPosition);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private setLastNonTriviaPosition(s: string, force: boolean) {
|
||||
|
|
|
@ -64,35 +64,12 @@
|
|||
"signatureHelp.ts",
|
||||
"symbolDisplay.ts",
|
||||
"textChanges.ts",
|
||||
"formatting/formatting.ts",
|
||||
"formatting/formattingContext.ts",
|
||||
"formatting/formattingRequestKind.ts",
|
||||
"formatting/formattingScanner.ts",
|
||||
"formatting/references.ts",
|
||||
"formatting/rule.ts",
|
||||
"formatting/ruleAction.ts",
|
||||
"formatting/ruleDescriptor.ts",
|
||||
"formatting/ruleFlag.ts",
|
||||
"formatting/ruleOperation.ts",
|
||||
"formatting/ruleOperationContext.ts",
|
||||
"formatting/rules.ts",
|
||||
"formatting/rulesMap.ts",
|
||||
"formatting/rulesProvider.ts",
|
||||
"formatting/smartIndenter.ts",
|
||||
"formatting/tokenRange.ts",
|
||||
"codeFixProvider.ts",
|
||||
"codefixes/fixAddMissingMember.ts",
|
||||
"codefixes/fixSpelling.ts",
|
||||
"codefixes/fixExtendsInterfaceBecomesImplements.ts",
|
||||
"codefixes/fixClassIncorrectlyImplementsInterface.ts",
|
||||
"codefixes/fixClassDoesntImplementInheritedAbstractMember.ts",
|
||||
"codefixes/fixClassSuperMustPrecedeThisAccess.ts",
|
||||
"codefixes/fixConstructorForDerivedNeedSuperCall.ts",
|
||||
"codefixes/fixForgottenThisPropertyAccess.ts",
|
||||
"codefixes/fixes.ts",
|
||||
"codefixes/helpers.ts",
|
||||
"codefixes/importFixes.ts",
|
||||
"codefixes/unusedIdentifierFixes.ts",
|
||||
"codefixes/disableJsDiagnostics.ts"
|
||||
"refactorProvider.ts",
|
||||
"codeFixProvider.ts"
|
||||
],
|
||||
"include": [
|
||||
"formatting/*",
|
||||
"codefixes/*",
|
||||
"refactors/*"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -261,6 +261,8 @@ namespace ts {
|
|||
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean;
|
||||
|
||||
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[];
|
||||
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
|
||||
getRefactorCodeActions(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string): CodeAction[] | undefined;
|
||||
|
||||
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
|
||||
|
||||
|
@ -352,6 +354,11 @@ namespace ts {
|
|||
changes: FileTextChanges[];
|
||||
}
|
||||
|
||||
export interface ApplicableRefactorInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface TextInsertion {
|
||||
newText: string;
|
||||
/** The position in newText the caret should point to after the insertion. */
|
||||
|
|
26
tests/cases/fourslash/convertFunctionToEs6Class1.ts
Normal file
26
tests/cases/fourslash/convertFunctionToEs6Class1.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @allowNonTsExtensions: true
|
||||
// @Filename: test123.js
|
||||
//// [|function /*1*/foo() { }
|
||||
//// /*2*/foo.prototype.instanceMethod1 = function() { return "this is name"; };
|
||||
//// /*3*/foo.prototype.instanceMethod2 = () => { return "this is name"; };
|
||||
//// /*4*/foo.prototype.instanceProp1 = "hello";
|
||||
//// /*5*/foo.prototype.instanceProp2 = undefined;
|
||||
//// /*6*/foo.staticProp = "world";
|
||||
//// /*7*/foo.staticMethod1 = function() { return "this is static name"; };
|
||||
//// /*8*/foo.staticMethod2 = () => "this is static name";|]
|
||||
|
||||
['1', '2', '3', '4', '5', '6', '7', '8'].forEach(m => verify.applicableRefactorAvailableAtMarker(m));
|
||||
verify.fileAfterApplyingRefactorAtMarker('1',
|
||||
`class foo {
|
||||
constructor() { }
|
||||
instanceMethod1() { return "this is name"; }
|
||||
instanceMethod2() { return "this is name"; }
|
||||
static staticMethod1() { return "this is static name"; }
|
||||
static staticMethod2() { return "this is static name"; }
|
||||
}
|
||||
foo.prototype.instanceProp1 = "hello";
|
||||
foo.prototype.instanceProp2 = undefined;
|
||||
foo.staticProp = "world";
|
||||
`, 'Convert to ES2015 class');
|
27
tests/cases/fourslash/convertFunctionToEs6Class2.ts
Normal file
27
tests/cases/fourslash/convertFunctionToEs6Class2.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @allowNonTsExtensions: true
|
||||
// @Filename: test123.js
|
||||
//// [|var /*1*/foo = function() { };
|
||||
//// /*2*/foo.prototype.instanceMethod1 = function() { return "this is name"; };
|
||||
//// /*3*/foo.prototype.instanceMethod2 = () => { return "this is name"; };
|
||||
//// /*4*/foo.instanceProp1 = "hello";
|
||||
//// /*5*/foo.instanceProp2 = undefined;
|
||||
//// /*6*/foo.staticProp = "world";
|
||||
//// /*7*/foo.staticMethod1 = function() { return "this is static name"; };
|
||||
//// /*8*/foo.staticMethod2 = () => "this is static name";|]
|
||||
|
||||
|
||||
['1', '2', '3', '4', '5', '6', '7', '8'].forEach(m => verify.applicableRefactorAvailableAtMarker(m));
|
||||
verify.fileAfterApplyingRefactorAtMarker('4',
|
||||
`class foo {
|
||||
constructor() { }
|
||||
instanceMethod1() { return "this is name"; }
|
||||
instanceMethod2() { return "this is name"; }
|
||||
static staticMethod1() { return "this is static name"; }
|
||||
static staticMethod2() { return "this is static name"; }
|
||||
}
|
||||
foo.instanceProp1 = "hello";
|
||||
foo.instanceProp2 = undefined;
|
||||
foo.staticProp = "world";
|
||||
`, 'Convert to ES2015 class');
|
28
tests/cases/fourslash/convertFunctionToEs6Class3.ts
Normal file
28
tests/cases/fourslash/convertFunctionToEs6Class3.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @allowNonTsExtensions: true
|
||||
// @Filename: test123.js
|
||||
//// [|var bar = 10, /*1*/foo = function() { };
|
||||
//// /*2*/foo.prototype.instanceMethod1 = function() { return "this is name"; };
|
||||
//// /*3*/foo.prototype.instanceMethod2 = () => { return "this is name"; };
|
||||
//// /*4*/foo.prototype.instanceProp1 = "hello";
|
||||
//// /*5*/foo.prototype.instanceProp2 = undefined;
|
||||
//// /*6*/foo.staticProp = "world";
|
||||
//// /*7*/foo.staticMethod1 = function() { return "this is static name"; };
|
||||
//// /*8*/foo.staticMethod2 = () => "this is static name";|]
|
||||
|
||||
|
||||
['1', '2', '3', '4', '5', '6', '7', '8'].forEach(m => verify.applicableRefactorAvailableAtMarker(m));
|
||||
verify.fileAfterApplyingRefactorAtMarker('7',
|
||||
`var bar = 10;
|
||||
class foo {
|
||||
constructor() { }
|
||||
instanceMethod1() { return "this is name"; }
|
||||
instanceMethod2() { return "this is name"; }
|
||||
static staticMethod1() { return "this is static name"; }
|
||||
static staticMethod2() { return "this is static name"; }
|
||||
}
|
||||
foo.prototype.instanceProp1 = "hello";
|
||||
foo.prototype.instanceProp2 = undefined;
|
||||
foo.staticProp = "world";
|
||||
`, 'Convert to ES2015 class');
|
|
@ -150,6 +150,9 @@ declare namespace FourSlashInterface {
|
|||
implementationListIsEmpty(): void;
|
||||
isValidBraceCompletionAtPosition(openingBrace?: string): void;
|
||||
codeFixAvailable(): void;
|
||||
applicableRefactorAvailableAtMarker(markerName: string): void;
|
||||
codeFixDiagnosticsAvailableAtMarkers(markerNames: string[], diagnosticCode?: number): void;
|
||||
applicableRefactorAvailableForRange(): void;
|
||||
}
|
||||
class verify extends verifyNegatable {
|
||||
assertHasRanges(ranges: Range[]): void;
|
||||
|
@ -234,6 +237,7 @@ declare namespace FourSlashInterface {
|
|||
DocCommentTemplate(expectedText: string, expectedOffset: number, empty?: boolean): void;
|
||||
noDocCommentTemplate(): void;
|
||||
rangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number): void;
|
||||
fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, formattingOptions?: FormatCodeOptions): void;
|
||||
importFixAtPosition(expectedTextArray: string[], errorCode?: number): void;
|
||||
|
||||
navigationBar(json: any): void;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// @allowNonTsExtensions: true
|
||||
// @Filename: test123.js
|
||||
|
||||
/// <reference path="../fourslash.ts" />
|
||||
|
||||
//// // Comment
|
||||
//// function fn() {
|
||||
//// this.baz = 10;
|
||||
//// }
|
||||
//// /*1*/fn.prototype.bar = function () {
|
||||
//// console.log('hello world');
|
||||
//// }
|
||||
|
||||
verify.applicableRefactorAvailableAtMarker('1');
|
||||
verify.fileAfterApplyingRefactorAtMarker('1',
|
||||
`// Comment
|
||||
class fn {
|
||||
constructor() {
|
||||
this.baz = 10;
|
||||
}
|
||||
bar() {
|
||||
console.log('hello world');
|
||||
}
|
||||
}
|
||||
`, 'Convert to ES2015 class');
|
Loading…
Reference in a new issue