Skip to content

Commit

Permalink
feat: #488 - LanguageService - getSuggestionDiagnostics, getEditsForR…
Browse files Browse the repository at this point in the history
…efactor, getCodeFixesAtPosition
  • Loading branch information
cancerberoSgx authored and dsherret committed Nov 8, 2018
1 parent c026a16 commit 9e42b10
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 16 deletions.
7 changes: 3 additions & 4 deletions src/compiler/ast/function/ArrowFunction.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { ts, SyntaxKind } from "../../../typescript";
import { AsyncableNode, BodiedNode, JSDocableNode, ModifierableNode, SignaturedDeclaration, TextInsertableNode, TypeParameteredNode } from "../base";
import { FunctionLikeDeclaration } from "./FunctionLikeDeclaration";
import { SyntaxKind, ts } from "../../../typescript";
import { AsyncableNode, BodiedNode, TextInsertableNode } from "../base";
import { Node } from "../common";
import { Expression } from "../expression";
import { StatementedNode } from "../statement";
import { FunctionLikeDeclaration } from "./FunctionLikeDeclaration";

export const ArrowFunctionBase = TextInsertableNode(BodiedNode(AsyncableNode(FunctionLikeDeclaration(Expression))));
export class ArrowFunction extends ArrowFunctionBase<ts.ArrowFunction> {
Expand Down
56 changes: 55 additions & 1 deletion src/compiler/tools/LanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import { Node } from "../ast/common";
import { SourceFile } from "../ast/module";
import { FormatCodeSettings, UserPreferences, RenameOptions } from "./inputs";
import { Program } from "./Program";
import { DefinitionInfo, EmitOutput, FileTextChanges, ImplementationLocation, RenameLocation, TextChange } from "./results";
import { DefinitionInfo, EmitOutput, FileTextChanges, ImplementationLocation, RenameLocation, TextChange, DiagnosticWithLocation, RefactorEditInfo, CodeFixAction } from "./results";

export interface TextRange {
getPos(): number;
getEnd(): number;
}

export class LanguageService {
private readonly _compilerObject: ts.LanguageService;
Expand Down Expand Up @@ -230,6 +235,16 @@ export class LanguageService {
return renameLocations.map(l => new RenameLocation(this.context, l));
}

/**
* Gets the suggestion diagnostics.
* @param filePathOrSourceFile - The source file or file path to get suggestions for.
*/
getSuggestionDiagnostics(filePathOrSourceFile: SourceFile | string): DiagnosticWithLocation[] {
const filePath = this._getFilePathFromFilePathOrSourceFile(filePathOrSourceFile);
const suggestionDiagnostics = this.compilerObject.getSuggestionDiagnostics(filePath);
return suggestionDiagnostics.map(d => this.context.compilerFactory.getDiagnosticWithLocation(d));
}

/**
* Gets the formatting edits for a range.
* @param filePath - File path.
Expand Down Expand Up @@ -337,6 +352,45 @@ export class LanguageService {
.map(fileTextChanges => new FileTextChanges(fileTextChanges));
}

/**
* Gets the edit information for applying a refactor at a the provided position in a source file.
* @param filePathOrSourceFile - File path or source file to get the edits for.
* @param formatOptions - Fomat code settings.
* @param positionOrRange - Position in the source file where to apply given refactor.
* @param refactorName - Refactor name.
* @param actionName - Refactor action name.
* @param preferences - User preferences for refactoring.
*/
getEditsForRefactor(filePathOrSourceFile: string | SourceFile, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange,
refactorName: string, actionName: string, preferences: UserPreferences = {}): RefactorEditInfo | undefined
{
const filePath = this._getFilePathFromFilePathOrSourceFile(filePathOrSourceFile);
const position = typeof positionOrRange === "number" ? positionOrRange : { pos: positionOrRange.getPos(), end: positionOrRange.getEnd() };
const compilerObject = this.compilerObject.getEditsForRefactor(filePath, this._getFilledSettings(formatOptions),
position, refactorName, actionName, this._getFilledUserPreferences(preferences));

return compilerObject != null ? new RefactorEditInfo(compilerObject) : undefined;
}

/**
* Gets the edit information for applying a code fix at the provided text range in a source file.
* @param filePathOrSourceFile - File path or source file to get the code fixes for.
* @param start - Start position of the text range to be fixed.
* @param end - End position of the text range to be fixed.
* @param errorCodes - One or more error codes associated with the code fixes to apply.
* @param formatOptions - Format code settings.
* @param preferences - User preferences for refactoring.
*/
getCodeFixesAtPosition(filePathOrSourceFile: string | SourceFile, start: number, end: number, errorCodes: ReadonlyArray<number>,
formatOptions: FormatCodeSettings = {}, preferences: UserPreferences = {}): CodeFixAction[]
{
const filePath = this._getFilePathFromFilePathOrSourceFile(filePathOrSourceFile);
const compilerResult = this.compilerObject.getCodeFixesAtPosition(filePath, start, end, errorCodes,
this._getFilledSettings(formatOptions), this._getFilledUserPreferences(preferences || {}));

return compilerResult.map(compilerObject => new CodeFixAction(compilerObject));
}

private _getFilePathFromFilePathOrSourceFile(filePathOrSourceFile: SourceFile | string) {
const filePath = typeof filePathOrSourceFile === "string"
? this.context.fileSystemWrapper.getStandardizedAbsolutePath(filePathOrSourceFile)
Expand Down
32 changes: 32 additions & 0 deletions src/compiler/tools/results/CodeAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ts } from "../../../typescript";
import {FileTextChanges} from "./FileTextChanges";

/**
* Represents a code action.
*/
export class CodeAction<TCompilerObject extends ts.CodeAction = ts.CodeAction> {
/** @internal */
private readonly _compilerObject: TCompilerObject;

/** @private */
constructor(compilerObject: TCompilerObject) {
this._compilerObject = compilerObject;
}

/** Gets the compiler text change. */
get compilerObject() {
return this._compilerObject;
}

/** Description of the code action. */
getDescription() {
return this.compilerObject.description;
}

/** Text changes to apply to each file as part of the code action. */
getChanges() {
return this.compilerObject.changes.map(change => new FileTextChanges(change));
}

// TODO: commands property
}
29 changes: 29 additions & 0 deletions src/compiler/tools/results/CodeFixAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ts } from "../../../typescript";
import { CodeAction } from "./CodeAction";

/**
* Represents a code fix action.
*/
export class CodeFixAction extends CodeAction<ts.CodeFixAction> {
/**
* Short name to identify the fix, for use by telemetry.
*/
getFixName() {
return this.compilerObject.fixName;
}

/**
* If present, one may call 'getCombinedCodeFix' with this fixId.
* This may be omitted to indicate that the code fix can't be applied in a group.
*/
getFixId() {
return this.compilerObject.fixId;
}

/**
* Gets the description of the code fix when fixing everything.
*/
getFixAllDescription() {
return this.compilerObject.fixAllDescription;
}
}
11 changes: 8 additions & 3 deletions src/compiler/tools/results/FileTextChanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ export class FileTextChanges {
/** @internal */
private readonly _compilerObject: ts.FileTextChanges;

/**
* @private
*/
/** @private */
constructor(compilerObject: ts.FileTextChanges) {
this._compilerObject = compilerObject;
}
Expand All @@ -27,4 +25,11 @@ export class FileTextChanges {
getTextChanges() {
return this._compilerObject.textChanges.map(c => new TextChange(c));
}

/**
* Gets if this change is for creating a new file.
*/
isNewFile() {
return !!this._compilerObject.isNewFile;
}
}
45 changes: 45 additions & 0 deletions src/compiler/tools/results/RefactorEditInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ts } from "../../../typescript";
import { Memoize } from "../../../utils";
import { FileTextChanges } from "./FileTextChanges";

/**
* Set of edits to make in response to a refactor action, plus an optional location where renaming should be invoked from.
*/
export class RefactorEditInfo {
/** @internal */
private readonly _compilerObject: ts.RefactorEditInfo;

/** @private */
constructor(compilerObject: ts.RefactorEditInfo) {
this._compilerObject = compilerObject;
}

/** Gets the compiler refactor edit info. */
get compilerObject() {
return this._compilerObject;
}

/**
* Gets refactor file text changes
*/
@Memoize
getEdits() {
return this.compilerObject.edits.map(edit => new FileTextChanges(edit));
}

/**
* Gets the file path for a rename refactor.
*/
getRenameFilePath() {
return this.compilerObject.renameFilename;
}

/**
* Location where renaming should be invoked from.
*/
getRenameLocation() {
return this.compilerObject.renameLocation;
}

// TODO: getCommands
}
3 changes: 3 additions & 0 deletions src/compiler/tools/results/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from "./CodeAction";
export * from "./CodeFixAction";
export * from "./DefinitionInfo";
export * from "./Diagnostic";
export * from "./DiagnosticMessageChain";
Expand All @@ -8,6 +10,7 @@ export * from "./EmitResult";
export * from "./FileTextChanges";
export * from "./ImplementationLocation";
export * from "./OutputFile";
export * from "./RefactorEditInfo";
export * from "./ReferencedSymbol";
export * from "./ReferencedSymbolDefinitionInfo";
export * from "./ReferenceEntry";
Expand Down
11 changes: 10 additions & 1 deletion src/tests/compiler/statement/statementTests.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { expect } from "chai";
import { Statement } from "../../../compiler";
import { getInfoFromText } from "../testHelpers";

describe(nameof(Statement), () => {
// todo: tests
describe(nameof<Statement>(n => n.remove), () => {
it("should remove the statement from its sourcefile", () => {
const { sourceFile } = getInfoFromText(`const foo = 1; const bar = 2;`);
const statement = sourceFile.getVariableDeclarationOrThrow("foo");
statement.remove();
expect(sourceFile.getText()).to.equals(`const bar = 2;`);
});
});
});

0 comments on commit 9e42b10

Please sign in to comment.