diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts
index d2c15b308ec4f..92cd4b2a5c62c 100644
--- a/src/server/protocol.d.ts
+++ b/src/server/protocol.d.ts
@@ -222,6 +222,20 @@ declare namespace ts.server.protocol {
file: string;
}
+ export interface SimpleTextSpan {
+ start: number;
+ length: number;
+ }
+
+ export class TextChange {
+ span: SimpleTextSpan;
+ newText: string;
+ }
+
+ export interface Refactoring extends TextChange {
+ filePath: string;
+ }
+
/**
* Definition response message. Gives text range for definition.
*/
@@ -343,6 +357,21 @@ declare namespace ts.server.protocol {
findInStrings?: boolean;
}
+ export interface QuickFixRequest extends FileLocationRequest {
+ }
+
+ export interface QuickFixResponse extends Response {
+ body?: QuickFixResponseBody;
+ }
+
+ export interface QuickFix {
+ display: string;
+ refactorings: Refactoring[];
+ }
+
+ export interface QuickFixResponseBody {
+ quickFixes?: QuickFix[];
+ }
/**
* Rename request; value of command field is "rename". Return
diff --git a/src/server/session.ts b/src/server/session.ts
index 0ec3d87e58c9a..44fcc5f802ac3 100644
--- a/src/server/session.ts
+++ b/src/server/session.ts
@@ -2,6 +2,7 @@
///
///
///
+///
namespace ts.server {
const spaceCache: string[] = [];
@@ -125,6 +126,7 @@ namespace ts.server {
export const TypeDefinition = "typeDefinition";
export const ProjectInfo = "projectInfo";
export const ReloadProjects = "reloadProjects";
+ export const QuickFixes = "quickfix";
export const Unknown = "unknown";
}
@@ -435,6 +437,44 @@ namespace ts.server {
return projectInfo;
}
+ private getQuickFixes(fileName: string, line: number, offset: number): protocol.QuickFixResponseBody {
+ fileName = ts.normalizePath(fileName);
+
+ const possibleFixes: protocol.QuickFix[] = [];
+ const project = this.projectService.getProjectForFile(fileName);
+ const syntacticDiags = project.compilerService.languageService.getSyntacticDiagnostics(fileName);
+ const semanticDiags = project.compilerService.languageService.getSemanticDiagnostics(fileName);
+ const sourceFile = project.compilerService.languageService.getProgram().getSourceFile(fileName);
+
+ const positionNode = getTokenAtPosition(sourceFile, project.compilerService.host.lineOffsetToPosition(fileName, line, offset));
+ const diagnostics: Diagnostic[] = syntacticDiags.concat(semanticDiags).filter(diag => {
+ const isSameNode = diag.start == positionNode.getStart();
+ return isSameNode;
+ });
+
+ const param: ts.quickFix.QuickFixQueryInformation = {
+ project: project,
+ service: project.compilerService.languageService,
+ program: project.program,
+ typeChecker: project.program.getTypeChecker(),
+ sourceFile: sourceFile,
+ positionErrors: diagnostics
+ };
+
+ quickFixRegistry.QuickFixes.allQuickFixes.forEach(possibleQuickfix => {
+ try {
+ possibleQuickfix.provideFix(param).forEach(fix => {
+ if (fix && fix.display && fix.refactorings && fix.refactorings.length > 0) {
+ possibleFixes.push({ display: fix.display, refactorings: fix.refactorings });
+ }
+ });
+ }
+ catch (error) {
+ }
+ });
+ return { quickFixes: possibleFixes };
+ }
+
private getRenameLocations(line: number, offset: number, fileName: string, findInComments: boolean, findInStrings: boolean): protocol.RenameResponseBody {
const file = ts.normalizePath(fileName);
const info = this.projectService.getScriptInfo(file);
@@ -1043,6 +1083,10 @@ namespace ts.server {
const renameArgs = request.arguments;
return { response: this.getRenameLocations(renameArgs.line, renameArgs.offset, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings), responseRequired: true };
},
+ [CommandNames.QuickFixes]: (request: protocol.Request) => {
+ const quickFixArgs = request.arguments;
+ return { response: this.getQuickFixes(quickFixArgs.file, quickFixArgs.line, quickFixArgs.offset), responseRequired: true };
+ },
[CommandNames.Open]: (request: protocol.Request) => {
const openArgs = request.arguments;
let scriptKind: ScriptKind;
diff --git a/src/services/quickfixes/quickFix.ts b/src/services/quickfixes/quickFix.ts
new file mode 100644
index 0000000000000..52d6847c9f974
--- /dev/null
+++ b/src/services/quickfixes/quickFix.ts
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0.
+// See LICENSE.txt in the project root for complete license information.
+
+///
+///
+
+namespace ts.quickFix {
+
+ export interface QuickFixQueryInformation {
+ project: server.Project;
+ service: LanguageService;
+ program: Program;
+ typeChecker: TypeChecker;
+ sourceFile: SourceFile;
+ positionErrors: Diagnostic[];
+ }
+
+ export interface QuickFix {
+ provideFix(info: QuickFixQueryInformation): ts.server.protocol.QuickFix[];
+ }
+
+}
\ No newline at end of file
diff --git a/src/services/quickfixes/quickFixRegistry.ts b/src/services/quickfixes/quickFixRegistry.ts
new file mode 100644
index 0000000000000..f00e26eca1cd7
--- /dev/null
+++ b/src/services/quickfixes/quickFixRegistry.ts
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0.
+// See LICENSE.txt in the project root for complete license information.
+
+///
+///
+///
+///
+///
+///
+///
+///
+
+namespace ts.quickFixRegistry {
+ export class QuickFixes{
+ public static allQuickFixes: quickFix.QuickFix[] = [
+ new quickFix.semantic.AddClassMethod(),
+ new quickFix.semantic.AddClassMember(),
+ new quickFix.semantic.AddImportFromStatement(),
+ new quickFix.semantic.AddImportStatement(),
+ new quickFix.semantic.TypeAssertPropertyAccessToAny(),
+ new quickFix.semantic.ImplementInterface(),
+ new quickFix.semantic.ImplementAbstractClass()
+ ];
+ }
+}
\ No newline at end of file
diff --git a/src/services/quickfixes/semantic/addClassMember.ts b/src/services/quickfixes/semantic/addClassMember.ts
new file mode 100644
index 0000000000000..d8e1eeb0a19a5
--- /dev/null
+++ b/src/services/quickfixes/semantic/addClassMember.ts
@@ -0,0 +1,99 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0.
+// See LICENSE.txt in the project root for complete license information.
+
+///
+namespace ts.quickFix.semantic {
+ export class AddClassMember extends MessageAndCodeBasedQuickFixBase {
+ protected getPattern(): RegExp {
+ return /Property \'(\w+)\' does not exist on type \'(\w+)\'./;
+ }
+ protected getSupportedErrorCode(): number {
+ return Diagnostics.Property_0_does_not_exist_on_type_1.code;
+ }
+ protected getIdentifierAndClassNameFromMatch(info: QuickFixQueryInformation, error: Diagnostic, match: RegExpMatchArray): IdentifierAndClassName[] {
+ return [{ identifierName: match[1], className: match[2] }];
+ }
+ protected getDisplay(context: QuickFixContext): string {
+ var {identifierName, className} = context.identifierAndClassName;
+ return `Add ${identifierName} to ${className}`
+ }
+
+ private getLastNameAfterDot(text: string) {
+ return text.substr(text.lastIndexOf('.') + 1);
+ }
+
+ private getTypeStringForNode(node: Node, typeChecker: TypeChecker) {
+ var type = typeChecker.getTypeAtLocation(node);
+ return displayPartsToString(ts.typeToDisplayParts(typeChecker, type)).replace(/\s+/g, ' ');
+ }
+ protected getRefactorings(context: QuickFixContext) {
+ var {identifierName, className} = context.identifierAndClassName;
+ var {info} = context;
+
+ // Get the type of the stuff on the right if its an assignment
+ var typeString = 'any';
+ var parentOfParent = context.identifier.parent.parent;
+ if (parentOfParent.kind === SyntaxKind.BinaryExpression
+ && (parentOfParent).operatorToken.getText().trim() === '=') {
+
+ let binaryExpression = parentOfParent;
+ typeString = this.getTypeStringForNode(binaryExpression.right, info.typeChecker);
+ }
+ else if (parentOfParent.kind === SyntaxKind.CallExpression) {
+ let callExp = parentOfParent;
+ let typeStringParts = ['('];
+
+ // Find the number of arguments
+ let args: string[] = [];
+ callExp.arguments.forEach(arg => {
+ var argName = (this.getLastNameAfterDot(arg.getText()));
+ var argType = this.getTypeStringForNode(arg, info.typeChecker);
+
+ args.push(`${argName}: ${argType}`);
+ });
+ typeStringParts.push(args.join(', '));
+
+ // TODO: infer the return type as well if the next parent is an assignment
+ // Currently its `any`
+ typeStringParts.push(') => any');
+ typeString = typeStringParts.join('');
+ }
+
+ // Find the containing class declaration
+ var memberTarget = getNodeByKindAndName([info.sourceFile], SyntaxKind.ClassDeclaration, className) ||
+ getNodeByKindAndName(info.program.getSourceFiles(), SyntaxKind.ClassDeclaration, className);
+ if (!memberTarget) {
+ // Find the containing interface declaration
+ memberTarget = getNodeByKindAndName([info.sourceFile], SyntaxKind.InterfaceDeclaration, className) ||
+ getNodeByKindAndName(info.program.getSourceFiles(), SyntaxKind.InterfaceDeclaration, className);
+ }
+ if (!memberTarget) {
+ return [];
+ }
+
+ // The following code will be same (and typesafe) for either class or interface
+ let targetDeclaration = memberTarget;
+
+ // Then the first brace
+ let firstBrace = targetDeclaration.getChildren().filter(x => x.kind === SyntaxKind.OpenBraceToken)[0];
+
+ let formatCodeOptions = info.project.projectService.getFormatCodeOptions();
+ var newLine = formatCodeOptions.NewLineCharacter;
+ // And the correct indent
+ var indentLength = info.service.getIndentationAtPosition(memberTarget.getSourceFile().fileName, firstBrace.end, formatCodeOptions);
+ var indent = formatting.getIndentationString(indentLength + formatCodeOptions.IndentSize, formatCodeOptions);
+
+ // And add stuff after the first brace
+ let refactoring: server.protocol.Refactoring = {
+ span: {
+ start: firstBrace.end,
+ length: 0
+ },
+ newText: `${newLine}${indent}${identifierName}: ${typeString};`,
+ filePath: targetDeclaration.getSourceFile().fileName
+ };
+
+ return [refactoring];
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/services/quickfixes/semantic/addClassMethod.ts b/src/services/quickfixes/semantic/addClassMethod.ts
new file mode 100644
index 0000000000000..9c802e4a30cc8
--- /dev/null
+++ b/src/services/quickfixes/semantic/addClassMethod.ts
@@ -0,0 +1,175 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0.
+// See LICENSE.txt in the project root for complete license information.
+
+///
+namespace ts.quickFix.semantic {
+ export class AddClassMethod extends MessageAndCodeBasedQuickFixBase {
+ protected getPattern(): RegExp {
+ return /Property \'(\w+)\' does not exist on type \'(\w+)\'./;
+ }
+ protected getSupportedErrorCode(): number {
+ return Diagnostics.Property_0_does_not_exist_on_type_1.code;
+ }
+ protected getIdentifierAndClassNameFromMatch(info: QuickFixQueryInformation, error: Diagnostic, match: RegExpMatchArray): IdentifierAndClassName[] {
+ return [{ identifierName: match[1], className: match[2] }];
+ }
+ protected getDisplay(context: QuickFixContext): string {
+ var {identifierName, className} = context.identifierAndClassName;
+ return `Add method '${identifierName}' to current class ${className}`
+ }
+
+ private getTypeStringForNode(node: Node, typeChecker: TypeChecker) {
+ var type = typeChecker.getTypeAtLocation(node);
+ return displayPartsToString(ts.typeToDisplayParts(typeChecker, type)).replace(/\s+/g, ' ');
+ }
+ protected getRefactorings(context: QuickFixContext) {
+ var {identifierName, className} = context.identifierAndClassName;
+ var {info} = context;
+ // Get the type of the stuff on the right if its an assignment
+ var typeString = 'any';
+ var parentOfParent = context.identifier.parent.parent;
+ if (parentOfParent.kind === SyntaxKind.BinaryExpression
+ && (parentOfParent).operatorToken.getText().trim() === '=') {
+
+ let binaryExpression = parentOfParent;
+ typeString = this.getTypeStringForNode(binaryExpression.right, info.typeChecker);
+
+ }
+ else if (parentOfParent.kind === SyntaxKind.CallExpression) {
+
+ let nativeTypes = ['string', 'number', 'boolean', 'object', 'null', 'undefined', 'RegExp'];
+ let abc = 'abcdefghijklmnopqrstuvwxyz';
+ let argsAlphabet = abc.split('');
+ let argsAlphabetPosition = 0;
+ let argName = '';
+ let argCount = 0;
+
+ let callExp = parentOfParent;
+ let typeStringParts = ['('];
+
+ // Find the number of arguments
+ let args: string[] = [];
+ callExp.arguments.forEach(arg => {
+ var argType = this.getTypeStringForNode(arg, info.typeChecker);
+
+ // determine argument output type
+ // use consecutive letters for native types
+ // or use decapitalized Class name + counter as argument name
+ if (nativeTypes.indexOf(argType) !== -1 //native types
+ || argType.indexOf('{') !== -1 //Casted inline argument declarations
+ || argType.indexOf('=>') !== -1 //Method references
+ || argType.indexOf('[]') !== -1 //Array references
+ ) {
+
+ var type: Type = info.typeChecker.getTypeAtLocation(arg);
+ var typeName: string = 'type';
+ if (type &&
+ type.symbol &&
+ type.symbol.name) {
+ typeName = type.symbol.name.replace(/[\[\]]/g, '');
+ };
+ var hasAnonymous = typeName.indexOf('__') === 0;
+ var isAnonymousTypedArgument = hasAnonymous && typeName.substring(2) === 'type';
+ var isAnonymousMethod = hasAnonymous && typeName.substring(2) === 'function';
+ var isAnonymousObject = hasAnonymous && typeName.substring(2) === 'object';
+
+ if (argType.indexOf('=>') !== -1 &&
+ !isAnonymousTypedArgument &&
+ !isAnonymousMethod &&
+ !isAnonymousObject) {
+ if (typeName === 'Array') { typeName = 'array'; };
+ argName = `${typeName}${argCount++}`;
+ }
+ else if (argType.indexOf('[]') !== -1) {
+ argName = `array${argCount++}`;
+ }
+ else {
+ if (isAnonymousMethod) {
+ typeName = 'function';
+ argName = `${typeName}${argCount++}`;
+ }
+ else if (isAnonymousObject) {
+ typeName = 'object';
+ argName = `${typeName}${argCount++}`;
+ }
+ else {
+ argName = argsAlphabet[argsAlphabetPosition];
+ argsAlphabet[argsAlphabetPosition] += argsAlphabet[argsAlphabetPosition].substring(1);
+ argsAlphabetPosition++;
+ argsAlphabetPosition %= abc.length;
+ }
+ }
+ }
+ else {
+ // replace 'typeof ' from name
+ argName = argType.replace('typeof ', '');
+ // decapitalize and concat
+ if (argType.indexOf('typeof ') === -1) {
+ var firstLower = argName[0].toLowerCase();
+
+ if (argName.length === 1) {
+ argName = firstLower;
+ }
+ else {
+ argName = firstLower + argName.substring(1);
+ }
+ }
+ // add counter value and increment it
+ argName += argCount.toString();
+ argCount++;
+ }
+
+ // cast null and undefined to any type
+ if (argType.indexOf('null') !== -1 || argType.indexOf('undefined') !== -1) {
+ argType = argType.replace(/null|undefined/g, 'any');
+ }
+ args.push(`${argName}: ${argType}`);
+
+ });
+ typeStringParts.push(args.join(', '));
+
+ // TODO: infer the return type as well if the next parent is an assignment
+ // Currently its `any`
+ typeStringParts.push(`): any { }`);
+ typeString = typeStringParts.join('');
+ }
+
+ // Find the containing class declaration
+ var memberTarget = getNodeByKindAndName([info.sourceFile], SyntaxKind.ClassDeclaration, className) ||
+ getNodeByKindAndName(info.program.getSourceFiles(), SyntaxKind.ClassDeclaration, className);
+ if (!memberTarget) {
+ // Find the containing interface declaration
+ memberTarget = getNodeByKindAndName([info.sourceFile], SyntaxKind.InterfaceDeclaration, className) ||
+ getNodeByKindAndName(info.program.getSourceFiles(), SyntaxKind.InterfaceDeclaration, className);
+ }
+ if (!memberTarget) {
+ return [];
+ }
+
+ // The following code will be same (and typesafe) for either class or interface
+ let targetDeclaration = memberTarget;
+
+ // Then the first brace
+ let firstBrace = targetDeclaration.getChildren().filter(x => x.kind === SyntaxKind.OpenBraceToken)[0];
+
+ let formatCodeOptions = info.project.projectService.getFormatCodeOptions();
+ var newLine = formatCodeOptions.NewLineCharacter;
+ // And the correct indent
+ var indentLength = info.service.getIndentationAtPosition(
+ memberTarget.getSourceFile().fileName, firstBrace.end, formatCodeOptions);
+ var indent = formatting.getIndentationString(indentLength + formatCodeOptions.IndentSize, formatCodeOptions);
+
+ // And add stuff after the first brace
+ let refactoring: server.protocol.Refactoring = {
+ span: {
+ start: firstBrace.end,
+ length: 0
+ },
+ newText: `${newLine}${indent}public ${identifierName}${typeString}`,
+ filePath: targetDeclaration.getSourceFile().fileName
+ };
+
+ return [refactoring];
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/services/quickfixes/semantic/addImportFromStatement.ts b/src/services/quickfixes/semantic/addImportFromStatement.ts
new file mode 100644
index 0000000000000..ad8ec90a8517e
--- /dev/null
+++ b/src/services/quickfixes/semantic/addImportFromStatement.ts
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0.
+// See LICENSE.txt in the project root for complete license information.
+
+///
+namespace ts.quickFix.semantic {
+ let path = require('path');
+ export class AddImportFromStatement extends MessageAndCodeBasedQuickFixBase {
+ protected getPattern(): RegExp {
+ return /Cannot find name \'(\w+)\'./;
+ }
+ protected getSupportedErrorCode(): number {
+ return Diagnostics.Cannot_find_name_0.code;
+ }
+ protected getIdentifierAndClassNameFromMatch(info: QuickFixQueryInformation, error: Diagnostic, match: RegExpMatchArray): IdentifierAndClassName[] {
+ var [, identifierName] = match;
+ let importCandidates = getClassesOrInterfacesBasedOnName(info.program.getSourceFiles(), identifierName);
+ return importCandidates.map(importCandidate => {
+ if (importCandidate) {
+ let file = removeExt(makeRelativePath(path.dirname(error.file.path), importCandidate.getSourceFile().path));
+ return { identifierName, file };
+ }
+ });
+ }
+ protected getDisplay(context: QuickFixContext): string {
+ var {identifierName, file} = context.identifierAndClassName;
+ return file ? `import {${identifierName}} from \"${file}\"` : undefined;
+ }
+
+ protected getRefactorings(context: QuickFixContext) {
+ var {identifierName, file} = context.identifierAndClassName;
+ let formatCodeOptions = context.info.project.projectService.getFormatCodeOptions();
+ var newLine = formatCodeOptions.NewLineCharacter;
+ let refactorings: ts.server.protocol.Refactoring[] = [{
+ span: {
+ start: 0,
+ length: 0
+ },
+ newText: `import {${identifierName}} from \"${file}\";${newLine}`,
+ filePath: context.info.sourceFile.fileName
+ }];
+
+ return refactorings;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/services/quickfixes/semantic/addImportStatement.ts b/src/services/quickfixes/semantic/addImportStatement.ts
new file mode 100644
index 0000000000000..c569a38b75ae4
--- /dev/null
+++ b/src/services/quickfixes/semantic/addImportStatement.ts
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0.
+// See LICENSE.txt in the project root for complete license information.
+
+///
+namespace ts.quickFix.semantic {
+ let path = require('path');
+ export class AddImportStatement extends MessageAndCodeBasedQuickFixBase {
+ protected getPattern(): RegExp {
+ return /Cannot find name \'(\w+)\'./;
+ }
+ protected getSupportedErrorCode(): number {
+ return Diagnostics.Cannot_find_name_0.code;
+ }
+ protected getIdentifierAndClassNameFromMatch(info: QuickFixQueryInformation, error: Diagnostic, match: RegExpMatchArray): IdentifierAndClassName[] {
+ var [, identifierName] = match;
+ let importCandidates = getClassesOrInterfacesBasedOnName(info.program.getSourceFiles(), identifierName);
+ return importCandidates.map(importCandidate => {
+ if (importCandidate) {
+ let file = removeExt(makeRelativePath(path.dirname(error.file.path), importCandidate.getSourceFile().path));
+ return { identifierName, file };
+ }
+ });
+ }
+ protected getDisplay(context: QuickFixContext): string {
+ var {identifierName, file} = context.identifierAndClassName;
+ return file ? `import ${identifierName} = require(\"${file}\")` : undefined;
+ }
+
+ protected getRefactorings(context: QuickFixContext) {
+ var {identifierName, file} = context.identifierAndClassName;
+ let formatCodeOptions = context.info.project.projectService.getFormatCodeOptions();
+ var newLine = formatCodeOptions.NewLineCharacter;
+ let refactorings: ts.server.protocol.Refactoring[] = [{
+ span: {
+ start: 0,
+ length: 0
+ },
+ newText: `import ${identifierName} = require(\"${file}\");${newLine}`,
+ filePath: context.info.sourceFile.fileName
+ }];
+
+ return refactorings;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/services/quickfixes/semantic/implementAbstractClass.ts b/src/services/quickfixes/semantic/implementAbstractClass.ts
new file mode 100644
index 0000000000000..e9c053406af31
--- /dev/null
+++ b/src/services/quickfixes/semantic/implementAbstractClass.ts
@@ -0,0 +1,69 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0.
+// See LICENSE.txt in the project root for complete license information.
+
+///
+namespace ts.quickFix.semantic {
+ export class ImplementAbstractClass extends MessageAndCodeBasedQuickFixBase {
+ protected getPattern(): RegExp {
+ return /Non-abstract class \'(\w+)\' does not implement inherited abstract member \'(\w+)\' from class \'(\w+)\'./;
+ }
+ protected getSupportedErrorCode(): number {
+ return Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2.code;
+ }
+ protected getIdentifierAndClassNameFromMatch(info: QuickFixQueryInformation, error: Diagnostic, match: RegExpMatchArray): IdentifierAndClassName[] {
+ var [, className, identifierName, abstractClassName] = match;
+ return [{ className, abstractClassName, identifierName }];
+ }
+ protected getDisplay(context: QuickFixContext): string {
+ var {abstractClassName, className, identifierName} = context.identifierAndClassName;
+ return `Implement ${identifierName} from ${abstractClassName} in ${className}`;
+ }
+
+ protected getRefactorings(context: QuickFixContext) {
+ var {className, abstractClassName, identifierName} = context.identifierAndClassName;
+ var {info} = context;
+
+ let abstractClassTarget = ts.quickFix.getNodeByKindAndName(info.program.getSourceFiles(), ts.SyntaxKind.ClassDeclaration, abstractClassName);
+
+ let classTarget = ts.quickFix.getNodeByKindAndName([info.sourceFile], ts.SyntaxKind.ClassDeclaration, className);
+
+ let firstBrace = classTarget.getChildren().filter(x => x.kind === ts.SyntaxKind.OpenBraceToken)[0];
+
+ let formatCodeOptions = info.project.projectService.getFormatCodeOptions();
+ var newLine = formatCodeOptions.NewLineCharacter;
+
+ var indentLength = info.service.getIndentationAtPosition(
+ classTarget.getSourceFile().fileName, firstBrace.end, formatCodeOptions);
+ var indent = formatting.getIndentationString(indentLength + formatCodeOptions.IndentSize, formatCodeOptions);
+ var indentForContent = formatting.getIndentationString(indentLength + 2 * formatCodeOptions.IndentSize, formatCodeOptions);
+
+ let refactorings: ts.server.protocol.Refactoring[] = [];
+
+ abstractClassTarget.members.forEach(function (member) {
+ let name = member.name && (member.name).text;
+ if (name && identifierName === name) {
+ var memberAsText = member.getFullText();
+ if (memberAsText.indexOf('abstract') > -1) {
+ var content = memberAsText.replace('abstract', '');
+ if (content.lastIndexOf(';') === content.length - 1) {
+ content = content.substring(0, content.length - 1);
+ }
+
+ content += ' {' + newLine + indentForContent + 'return null;' + newLine + indent + '}';
+ var refactoring = {
+ span: {
+ start: firstBrace.end,
+ length: 0
+ },
+ newText: (newLine + indent) + content,
+ filePath: classTarget.getSourceFile().fileName
+ };
+ refactorings.push(refactoring);
+ }
+ }
+ });
+
+ return refactorings;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/services/quickfixes/semantic/implementInterface.ts b/src/services/quickfixes/semantic/implementInterface.ts
new file mode 100644
index 0000000000000..43a301425025b
--- /dev/null
+++ b/src/services/quickfixes/semantic/implementInterface.ts
@@ -0,0 +1,68 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0.
+// See LICENSE.txt in the project root for complete license information.
+
+///
+namespace ts.quickFix.semantic {
+ export class ImplementInterface extends MessageAndCodeBasedQuickFixBase {
+ protected getPattern(): RegExp {
+ return /Class \'(\w+)\' incorrectly implements interface \'(\w+)\'.*Property \'(\w+)\' is missing in type \'(\w+)\'./;
+ }
+ protected getSupportedErrorCode(): number {
+ return Diagnostics.Class_0_incorrectly_implements_interface_1.code;
+ }
+ protected getIdentifierAndClassNameFromMatch(info: QuickFixQueryInformation, error: Diagnostic, match: RegExpMatchArray): IdentifierAndClassName[] {
+ var [, className, interfaceName, identifierName] = match;
+ return [{ className, interfaceName, identifierName }];
+ }
+ protected getDisplay(context: QuickFixContext): string {
+ var {interfaceName, className, identifierName} = context.identifierAndClassName;
+ return `Implement ${identifierName} from ${interfaceName} in ${className}`;
+ }
+
+ protected getRefactorings(context: QuickFixContext) {
+ let info = context.info;
+ var {className, interfaceName, identifierName} = context.identifierAndClassName;
+
+ let interfaceTarget = getNodeByKindAndName(info.program.getSourceFiles(), ts.SyntaxKind.InterfaceDeclaration, interfaceName);
+ let classTarget = getNodeByKindAndName([info.sourceFile], ts.SyntaxKind.ClassDeclaration, className);
+ let firstBrace = classTarget.getChildren().filter(x => x.kind === ts.SyntaxKind.OpenBraceToken)[0];
+
+ let formatCodeOptions = info.project.projectService.getFormatCodeOptions();
+
+ var newLine = formatCodeOptions.NewLineCharacter;
+ var indentLength = info.service.getIndentationAtPosition(classTarget.getSourceFile().fileName, firstBrace.end, formatCodeOptions);
+
+ var indent = formatting.getIndentationString(indentLength + formatCodeOptions.IndentSize, formatCodeOptions);
+ var indentForContent = formatting.getIndentationString(indentLength + 2 * formatCodeOptions.IndentSize, formatCodeOptions);
+
+ let refactorings: ts.server.protocol.Refactoring[] = [];
+ interfaceTarget.members.forEach(function (member) {
+ let name = member.name && (member.name).text;
+ if (name && identifierName === name) {
+ let content = "";
+ if (member.kind === ts.SyntaxKind.MethodSignature) {
+ var memberAsText = member.getFullText();
+ content = (newLine + indent) + memberAsText.replace('abstract', '');
+ if (content.lastIndexOf(';') === content.length - 1) {
+ content = content.substring(0, content.length - 1);
+ }
+ content += ' {' + newLine + indentForContent + 'return null;' + newLine + indent + '}';
+ } else {
+ content = (newLine + indent) + member.getFullText();
+ }
+ var refactoring = {
+ span: {
+ start: firstBrace.end,
+ length: 0
+ },
+ newText: content,
+ filePath: classTarget.getSourceFile().fileName
+ };
+ refactorings.push(refactoring);
+ }
+ });
+
+ return refactorings;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/services/quickfixes/semantic/messageAndCodeBasedQuickFixBase.ts b/src/services/quickfixes/semantic/messageAndCodeBasedQuickFixBase.ts
new file mode 100644
index 0000000000000..2f75d25ba896c
--- /dev/null
+++ b/src/services/quickfixes/semantic/messageAndCodeBasedQuickFixBase.ts
@@ -0,0 +1,99 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0.
+// See LICENSE.txt in the project root for complete license information.
+
+///
+///
+///
+///
+
+namespace ts.quickFix.semantic {
+ export interface QuickFixContext {
+ identifierAndClassName: IdentifierAndClassName;
+ error: Diagnostic;
+ positionNode: Node;
+ identifier: Identifier;
+ info: QuickFixQueryInformation;
+ }
+
+ export interface IdentifierAndClassName {
+ identifierName?: string,
+ className?: string,
+ file?: string,
+ abstractClassName?: string,
+ interfaceName?: string
+ }
+
+ export abstract class MessageAndCodeBasedQuickFixBase implements QuickFix {
+ getIdentifierAndClassNames(info: QuickFixQueryInformation, errorText: string, error: Diagnostic): IdentifierAndClassName[] {
+
+ var match = errorText.match(this.getPattern());
+ if (!match) {
+ return undefined;
+ }
+ return this.getIdentifierAndClassNameFromMatch(info, error, match);
+ }
+
+ private getRelevantIdentifierAndClassName(info: QuickFixQueryInformation): QuickFixContext[] {
+ var errors = info.positionErrors.filter(x => x.code === this.getSupportedErrorCode());
+ let quickFixContents: QuickFixContext[] = [];
+ let errorToErrorText = errors.forEach(error => {
+ if (error) {
+ let positionNode = getTokenAtPosition(info.sourceFile, error.start);
+ if (positionNode.kind === SyntaxKind.Identifier) {
+ const flattenedErrors = this.flatten(error.messageText);
+ flattenedErrors.map(errorText => {
+ let mainError = flattenedErrors[0];
+
+ errorText = mainError + errorText;
+
+ var identifierAndClassNames = this.getIdentifierAndClassNames(info, errorText, error);
+
+ if (identifierAndClassNames && identifierAndClassNames.length) {
+ let identifier: Identifier = positionNode;
+ identifierAndClassNames.forEach(identifierAndClassName => {
+ quickFixContents.push({ identifierAndClassName, error, positionNode, identifier, info });
+ });
+ }
+ });
+ }
+ }
+ })
+
+ return quickFixContents;
+ }
+
+ public provideFix(info: QuickFixQueryInformation): ts.server.protocol.QuickFix[] {
+ let quickFixContexts = this.getRelevantIdentifierAndClassName(info)
+ if (quickFixContexts.length === 0) return [];
+ return quickFixContexts.map(context => {
+ return { display: this.getDisplay(context), refactorings: this.getRefactorings(context) }
+ });
+ }
+
+ private flatten(messageText: string | DiagnosticMessageChain): string[] {
+ if (typeof messageText === "string") {
+ return [messageText];
+ }
+ else {
+ let diagnosticChain = messageText;
+ let result: string[] = [];
+
+ let indent = 0;
+ while (diagnosticChain) {
+ result.push(diagnosticChain.messageText);
+ diagnosticChain = diagnosticChain.next;
+ }
+
+ return result;
+ }
+ }
+
+ protected abstract getPattern(): RegExp;
+ protected abstract getSupportedErrorCode(): number;
+ protected abstract getDisplay(context: QuickFixContext): string;
+ protected abstract getRefactorings(context: QuickFixContext): server.protocol.Refactoring[];
+ protected abstract getIdentifierAndClassNameFromMatch(info: QuickFixQueryInformation, error: Diagnostic, match: RegExpMatchArray): IdentifierAndClassName[];
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/services/quickfixes/semantic/typeAssertPropertyAccessToAny.ts b/src/services/quickfixes/semantic/typeAssertPropertyAccessToAny.ts
new file mode 100644
index 0000000000000..5284b02bce5db
--- /dev/null
+++ b/src/services/quickfixes/semantic/typeAssertPropertyAccessToAny.ts
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0.
+// See LICENSE.txt in the project root for complete license information.
+
+///
+namespace ts.quickFix.semantic {
+ export class TypeAssertPropertyAccessToAny extends MessageAndCodeBasedQuickFixBase {
+ protected getPattern(): RegExp {
+ return /Property \'(\w+)\' does not exist on type \.*/;
+ }
+ protected getSupportedErrorCode(): number {
+ return Diagnostics.Property_0_does_not_exist_on_type_1.code;
+ }
+ protected getIdentifierAndClassNameFromMatch(info: QuickFixQueryInformation, error: Diagnostic, match: RegExpMatchArray): IdentifierAndClassName[] {
+ var [, identifierName] = match;
+ return [{ identifierName }];
+ }
+ protected getDisplay(context: QuickFixContext): string {
+ var {identifierName} = context.identifierAndClassName;
+ return `Assert "any" for property access "${identifierName}"`;
+ }
+
+ protected getRefactorings(context: QuickFixContext) {
+ var {identifierName} = context.identifierAndClassName;
+ let {info, error} = context;
+ let positionNode = getTokenAtPosition(error.file, error.start);
+ let parent = positionNode.parent;
+ if (parent.kind === ts.SyntaxKind.PropertyAccessExpression) {
+ let propertyAccess = parent;
+ let start = propertyAccess.getStart();
+ let end = propertyAccess.dotToken.getStart();
+
+ let oldText = propertyAccess.getText().substr(0, end - start);
+
+ let FileSpan: ts.server.protocol.Refactoring = {
+ filePath: parent.getSourceFile().fileName,
+ span: {
+ start: start,
+ length: end - start,
+ },
+ newText: `(${oldText} as any)`
+ };
+
+ return [FileSpan];
+ }
+ return [];
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/services/quickfixes/utils.ts b/src/services/quickfixes/utils.ts
new file mode 100644
index 0000000000000..48c1437e5dece
--- /dev/null
+++ b/src/services/quickfixes/utils.ts
@@ -0,0 +1,84 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the Apache License, Version 2.0.
+// See LICENSE.txt in the project root for complete license information.
+
+///
+///
+
+namespace ts.quickFix {
+ let path = require('path');
+
+ export interface GetPathCompletions {
+ prefix: string;
+ allFiles: string[];
+ program: ts.Program;
+ filePath: string;
+ sourceFile: ts.SourceFile;
+ }
+ export interface PathCompletion {
+ fileName: string;
+ relativePath: string;
+ fullPath: string;
+ }
+ function getFileName(fullFilePath: string) {
+ let parts = fullFilePath.split('/');
+ return parts[parts.length - 1];
+ }
+
+ export function removeExt(filePath: string) {
+ return filePath.substr(0, filePath.lastIndexOf('.'));
+ }
+
+ export function makeRelativePath(relativeFolder: string, filePath: string) {
+ var relativePath = path.relative(relativeFolder, filePath).split('\\').join('/');
+ if (relativePath[0] !== '.') {
+ relativePath = './' + relativePath;
+ }
+ return relativePath;
+ }
+
+ var forEachChild = ts.forEachChild;
+
+ export function getClassesOrInterfacesBasedOnName(sourcefiles: SourceFile[], name: string): Array {
+ let results: Array = [];
+ function findNode(node: Node) {
+ if (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.InterfaceDeclaration) {
+ let typedNode = node;
+ let nodeName = typedNode.name && typedNode.name.text;
+ if (nodeName === name) {
+ results.push(typedNode);
+ }
+ }
+ forEachChild(node, findNode);
+ }
+
+ for (let file of sourcefiles) {
+ forEachChild(file, findNode);
+ }
+
+ return results;
+ }
+
+ export function getNodeByKindAndName(sourcefiles: SourceFile[], kind: SyntaxKind, name: string): Node {
+ let found: Node = undefined;
+
+ function findNode(node: Node) {
+ if (node.kind === kind) {
+ // Now lookup name:
+ if (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.InterfaceDeclaration) {
+ let nodeName = (node).name && ((node).name).text;
+ if (nodeName === name) {
+ found = node;
+ }
+ }
+ }
+
+ if (!found) { forEachChild(node, findNode); }
+ }
+
+ for (let file of sourcefiles) {
+ forEachChild(file, findNode);
+ }
+
+ return found;
+ }
+}
\ No newline at end of file
diff --git a/src/services/services.ts b/src/services/services.ts
index 13df3a54a30be..f4c42eaca06c0 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -10,6 +10,7 @@
///
///
///
+///
namespace ts {
/** The version of the language service API */