mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Support a "getCombinedCodeFix" service (#20338)
* Support a "getCombinedCodeFix" service * Rename things * Code review * Rename things * Update API baselines * CodeActionAll -> CombinedCodeActions * Take a `scope` parameter instead of `fileName` for flexibility * Renames and bugfixes * Make API changes internal * Code review * Update comment
This commit is contained in:
119 files changed
+2942
-1789
No files matched your search
+15
-2
@@ -228,13 +228,27 @@ namespace ts {
|
||||
|
||||
export function zipWith<T, U, V>(arrayA: ReadonlyArray<T>, arrayB: ReadonlyArray<U>, callback: (a: T, b: U, index: number) => V): V[] {
|
||||
const result: V[] = [];
|
||||
Debug.assert(arrayA.length === arrayB.length);
|
||||
Debug.assertEqual(arrayA.length, arrayB.length);
|
||||
for (let i = 0; i < arrayA.length; i++) {
|
||||
result.push(callback(arrayA[i], arrayB[i], i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function zipToIterator<T, U>(arrayA: ReadonlyArray<T>, arrayB: ReadonlyArray<U>): Iterator<[T, U]> {
|
||||
Debug.assertEqual(arrayA.length, arrayB.length);
|
||||
let i = 0;
|
||||
return {
|
||||
next() {
|
||||
if (i === arrayA.length) {
|
||||
return { value: undefined as never, done: true };
|
||||
}
|
||||
i++;
|
||||
return { value: [arrayA[i - 1], arrayB[i - 1]], done: false };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function zipToMap<T>(keys: ReadonlyArray<string>, values: ReadonlyArray<T>): Map<T> {
|
||||
Debug.assert(keys.length === values.length);
|
||||
const map = createMap<T>();
|
||||
@@ -1385,7 +1399,6 @@ namespace ts {
|
||||
this.set(key, values = [value]);
|
||||
}
|
||||
return values;
|
||||
|
||||
}
|
||||
function multiMapRemove<T>(this: MultiMap<T>, key: string, value: T) {
|
||||
const values = this.get(key);
|
||||
|
||||
@@ -949,7 +949,7 @@ namespace ts {
|
||||
|
||||
export interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement, JSDocContainer {
|
||||
kind: SyntaxKind.Constructor;
|
||||
parent?: ClassDeclaration | ClassExpression;
|
||||
parent?: ClassLikeDeclaration;
|
||||
body?: FunctionBody;
|
||||
/* @internal */ returnFlowNode?: FlowNode;
|
||||
}
|
||||
@@ -957,14 +957,14 @@ namespace ts {
|
||||
/** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */
|
||||
export interface SemicolonClassElement extends ClassElement {
|
||||
kind: SyntaxKind.SemicolonClassElement;
|
||||
parent?: ClassDeclaration | ClassExpression;
|
||||
parent?: ClassLikeDeclaration;
|
||||
}
|
||||
|
||||
// See the comment on MethodDeclaration for the intuition behind GetAccessorDeclaration being a
|
||||
// ClassElement and an ObjectLiteralElement.
|
||||
export interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer {
|
||||
kind: SyntaxKind.GetAccessor;
|
||||
parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression;
|
||||
parent?: ClassLikeDeclaration | ObjectLiteralExpression;
|
||||
name: PropertyName;
|
||||
body?: FunctionBody;
|
||||
}
|
||||
@@ -973,7 +973,7 @@ namespace ts {
|
||||
// ClassElement and an ObjectLiteralElement.
|
||||
export interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer {
|
||||
kind: SyntaxKind.SetAccessor;
|
||||
parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression;
|
||||
parent?: ClassLikeDeclaration | ObjectLiteralExpression;
|
||||
name: PropertyName;
|
||||
body?: FunctionBody;
|
||||
}
|
||||
@@ -982,7 +982,7 @@ namespace ts {
|
||||
|
||||
export interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement {
|
||||
kind: SyntaxKind.IndexSignature;
|
||||
parent?: ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeLiteralNode;
|
||||
parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
|
||||
}
|
||||
|
||||
export interface TypeNode extends Node {
|
||||
@@ -1986,7 +1986,7 @@ namespace ts {
|
||||
|
||||
export interface HeritageClause extends Node {
|
||||
kind: SyntaxKind.HeritageClause;
|
||||
parent?: InterfaceDeclaration | ClassDeclaration | ClassExpression;
|
||||
parent?: InterfaceDeclaration | ClassLikeDeclaration;
|
||||
token: SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword;
|
||||
types: NodeArray<ExpressionWithTypeArguments>;
|
||||
}
|
||||
|
||||
@@ -2376,7 +2376,7 @@ Actual: ${stringify(fullActual)}`);
|
||||
*/
|
||||
public getAndApplyCodeActions(errorCode?: number, index?: number) {
|
||||
const fileName = this.activeFile.fileName;
|
||||
this.applyCodeActions(this.getCodeFixActions(fileName, errorCode), index);
|
||||
this.applyCodeActions(this.getCodeFixes(fileName, errorCode), index);
|
||||
}
|
||||
|
||||
public applyCodeActionFromCompletion(markerName: string, options: FourSlashInterface.VerifyCompletionActionOptions) {
|
||||
@@ -2429,6 +2429,17 @@ Actual: ${stringify(fullActual)}`);
|
||||
this.verifyRangeIs(expectedText, includeWhiteSpace);
|
||||
}
|
||||
|
||||
public verifyCodeFixAll(options: FourSlashInterface.VerifyCodeFixAllOptions): void {
|
||||
const { fixId, newFileContent } = options;
|
||||
const fixIds = ts.mapDefined(this.getCodeFixes(this.activeFile.fileName), a => a.fixId);
|
||||
ts.Debug.assert(ts.contains(fixIds, fixId), "No available code fix has that group id.", () => `Expected '${fixId}'. Available action ids: ${fixIds}`);
|
||||
const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings);
|
||||
assert.deepEqual(commands, options.commands);
|
||||
assert(changes.every(c => c.fileName === this.activeFile.fileName), "TODO: support testing codefixes that touch multiple files");
|
||||
this.applyChanges(changes);
|
||||
this.verifyCurrentFileContent(newFileContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies fixes for the errors in fileName and compares the results to
|
||||
* expectedContents after all fixes have been applied.
|
||||
@@ -2441,7 +2452,7 @@ Actual: ${stringify(fullActual)}`);
|
||||
public verifyFileAfterCodeFix(expectedContents: string, fileName?: string) {
|
||||
fileName = fileName ? fileName : this.activeFile.fileName;
|
||||
|
||||
this.applyCodeActions(this.getCodeFixActions(fileName));
|
||||
this.applyCodeActions(this.getCodeFixes(fileName));
|
||||
|
||||
const actualContents: string = this.getFileContent(fileName);
|
||||
if (this.removeWhitespace(actualContents) !== this.removeWhitespace(expectedContents)) {
|
||||
@@ -2451,7 +2462,7 @@ Actual: ${stringify(fullActual)}`);
|
||||
|
||||
public verifyCodeFix(options: FourSlashInterface.VerifyCodeFixOptions) {
|
||||
const fileName = this.activeFile.fileName;
|
||||
const actions = this.getCodeFixActions(fileName, options.errorCode);
|
||||
const actions = this.getCodeFixes(fileName, options.errorCode);
|
||||
let index = options.index;
|
||||
if (index === undefined) {
|
||||
if (!(actions && actions.length === 1)) {
|
||||
@@ -2490,7 +2501,7 @@ Actual: ${stringify(fullActual)}`);
|
||||
* Rerieves a codefix satisfying the parameters, or undefined if no such codefix is found.
|
||||
* @param fileName Path to file where error should be retrieved from.
|
||||
*/
|
||||
private getCodeFixActions(fileName: string, errorCode?: number): ts.CodeAction[] {
|
||||
private getCodeFixes(fileName: string, errorCode?: number): ts.CodeFixAction[] {
|
||||
const diagnosticsForCodeFix = this.getDiagnostics(fileName).map(diagnostic => ({
|
||||
start: diagnostic.start,
|
||||
length: diagnostic.length,
|
||||
@@ -2506,7 +2517,7 @@ Actual: ${stringify(fullActual)}`);
|
||||
});
|
||||
}
|
||||
|
||||
private applyCodeActions(actions: ts.CodeAction[], index?: number): void {
|
||||
private applyCodeActions(actions: ReadonlyArray<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. ${actions ? actions.map(a => `${Harness.IO.newLine()} "${a.description}"`) : ""}`);
|
||||
@@ -2519,8 +2530,10 @@ Actual: ${stringify(fullActual)}`);
|
||||
}
|
||||
}
|
||||
|
||||
const changes = actions[index].changes;
|
||||
this.applyChanges(actions[index].changes);
|
||||
}
|
||||
|
||||
private applyChanges(changes: ReadonlyArray<ts.FileTextChanges>): void {
|
||||
for (const change of changes) {
|
||||
this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false);
|
||||
}
|
||||
@@ -2532,7 +2545,7 @@ Actual: ${stringify(fullActual)}`);
|
||||
this.raiseError("Exactly one range should be specified in the testfile.");
|
||||
}
|
||||
|
||||
const codeFixes = this.getCodeFixActions(this.activeFile.fileName, errorCode);
|
||||
const codeFixes = this.getCodeFixes(this.activeFile.fileName, errorCode);
|
||||
|
||||
if (codeFixes.length === 0) {
|
||||
if (expectedTextArray.length !== 0) {
|
||||
@@ -2871,7 +2884,7 @@ Actual: ${stringify(fullActual)}`);
|
||||
}
|
||||
|
||||
public verifyCodeFixAvailable(negative: boolean, info: FourSlashInterface.VerifyCodeFixAvailableOptions[] | undefined) {
|
||||
const codeFixes = this.getCodeFixActions(this.activeFile.fileName);
|
||||
const codeFixes = this.getCodeFixes(this.activeFile.fileName);
|
||||
|
||||
if (negative) {
|
||||
if (codeFixes.length) {
|
||||
@@ -3038,7 +3051,7 @@ Actual: ${stringify(fullActual)}`);
|
||||
}
|
||||
|
||||
public printAvailableCodeFixes() {
|
||||
const codeFixes = this.getCodeFixActions(this.activeFile.fileName);
|
||||
const codeFixes = this.getCodeFixes(this.activeFile.fileName);
|
||||
Harness.IO.log(stringify(codeFixes));
|
||||
}
|
||||
|
||||
@@ -4149,6 +4162,10 @@ namespace FourSlashInterface {
|
||||
this.state.verifyRangeAfterCodeFix(expectedText, includeWhiteSpace, errorCode, index);
|
||||
}
|
||||
|
||||
public codeFixAll(options: VerifyCodeFixAllOptions): void {
|
||||
this.state.verifyCodeFixAll(options);
|
||||
}
|
||||
|
||||
public fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, actionName: string, formattingOptions?: ts.FormatCodeSettings): void {
|
||||
this.state.verifyFileAfterApplyingRefactorAtMarker(markerName, expectedContent, refactorNameToApply, actionName, formattingOptions);
|
||||
}
|
||||
@@ -4584,6 +4601,12 @@ namespace FourSlashInterface {
|
||||
commands?: ts.CodeActionCommand[];
|
||||
}
|
||||
|
||||
export interface VerifyCodeFixAllOptions {
|
||||
fixId: string;
|
||||
newFileContent: string;
|
||||
commands: ReadonlyArray<{}>;
|
||||
}
|
||||
|
||||
export interface VerifyRefactorOptions {
|
||||
name: string;
|
||||
actionName: string;
|
||||
|
||||
@@ -36,8 +36,8 @@ namespace assert {
|
||||
export function isFalse(expr: boolean, msg = "Expected value to be false."): void {
|
||||
assert(!expr, msg);
|
||||
}
|
||||
export function equal<T>(a: T, b: T, msg = "Expected values to be equal."): void {
|
||||
assert(a === b, msg);
|
||||
export function equal<T>(a: T, b: T, msg?: string): void {
|
||||
assert(a === b, msg || (() => `Expected to be equal:\nExpected:\n${JSON.stringify(a)}\nActual:\n${JSON.stringify(b)}`));
|
||||
}
|
||||
export function notEqual<T>(a: T, b: T, msg = "Expected values to not be equal."): void {
|
||||
assert(a !== b, msg);
|
||||
|
||||
@@ -521,9 +521,10 @@ namespace Harness.LanguageService {
|
||||
getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): ts.TextSpan {
|
||||
return unwrapJSONCallResult(this.shim.getSpanOfEnclosingComment(fileName, position, onlyMultiLine));
|
||||
}
|
||||
getCodeFixesAtPosition(): ts.CodeAction[] {
|
||||
getCodeFixesAtPosition(): never {
|
||||
throw new Error("Not supported on the shim.");
|
||||
}
|
||||
getCombinedCodeFix = ts.notImplemented;
|
||||
applyCodeActionCommand = ts.notImplemented;
|
||||
getCodeFixDiagnostics(): ts.Diagnostic[] {
|
||||
throw new Error("Not supported on the shim.");
|
||||
|
||||
+11
-11
@@ -200,7 +200,7 @@ namespace ts.server {
|
||||
const response = this.processResponse<protocol.CompletionDetailsResponse>(request);
|
||||
Debug.assert(response.body.length === 1, "Unexpected length of completion details response body.");
|
||||
|
||||
const convertedCodeActions = map(response.body[0].codeActions, codeAction => this.convertCodeActions(codeAction, fileName));
|
||||
const convertedCodeActions = map(response.body[0].codeActions, ({ description, changes }) => ({ description, changes: this.convertChanges(changes, fileName) }));
|
||||
return { ...response.body[0], codeActions: convertedCodeActions };
|
||||
}
|
||||
|
||||
@@ -553,15 +553,18 @@ namespace ts.server {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: number[]): CodeAction[] {
|
||||
getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: ReadonlyArray<number>): ReadonlyArray<CodeFixAction> {
|
||||
const args: protocol.CodeFixRequestArgs = { ...this.createFileRangeRequestArgs(file, start, end), errorCodes };
|
||||
|
||||
const request = this.processRequest<protocol.CodeFixRequest>(CommandNames.GetCodeFixes, args);
|
||||
const response = this.processResponse<protocol.CodeFixResponse>(request);
|
||||
|
||||
return response.body.map(entry => this.convertCodeActions(entry, file));
|
||||
// TODO: GH#20538 shouldn't need cast
|
||||
return (response.body as ReadonlyArray<protocol.CodeFixAction>).map(({ description, changes, fixId }) => ({ description, changes: this.convertChanges(changes, file), fixId }));
|
||||
}
|
||||
|
||||
getCombinedCodeFix = notImplemented;
|
||||
|
||||
applyCodeActionCommand = notImplemented;
|
||||
|
||||
private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs {
|
||||
@@ -638,14 +641,11 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
convertCodeActions(entry: protocol.CodeAction, fileName: string): CodeAction {
|
||||
return {
|
||||
description: entry.description,
|
||||
changes: entry.changes.map(change => ({
|
||||
fileName: change.fileName,
|
||||
textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, fileName))
|
||||
}))
|
||||
};
|
||||
private convertChanges(changes: protocol.FileCodeEdits[], fileName: string): FileTextChanges[] {
|
||||
return changes.map(change => ({
|
||||
fileName: change.fileName,
|
||||
textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, fileName))
|
||||
}));
|
||||
}
|
||||
|
||||
convertTextChangeToCodeEdit(change: protocol.CodeEdit, fileName: string): ts.TextChange {
|
||||
|
||||
+52
-3
@@ -100,9 +100,14 @@ namespace ts.server.protocol {
|
||||
BreakpointStatement = "breakpointStatement",
|
||||
CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects",
|
||||
GetCodeFixes = "getCodeFixes",
|
||||
ApplyCodeActionCommand = "applyCodeActionCommand",
|
||||
/* @internal */
|
||||
GetCodeFixesFull = "getCodeFixes-full",
|
||||
// TODO: GH#20538
|
||||
/* @internal */
|
||||
GetCombinedCodeFix = "getCombinedCodeFix",
|
||||
/* @internal */
|
||||
GetCombinedCodeFixFull = "getCombinedCodeFix-full",
|
||||
ApplyCodeActionCommand = "applyCodeActionCommand",
|
||||
GetSupportedCodeFixes = "getSupportedCodeFixes",
|
||||
|
||||
GetApplicableRefactors = "getApplicableRefactors",
|
||||
@@ -552,6 +557,19 @@ namespace ts.server.protocol {
|
||||
arguments: CodeFixRequestArgs;
|
||||
}
|
||||
|
||||
// TODO: GH#20538
|
||||
/* @internal */
|
||||
export interface GetCombinedCodeFixRequest extends Request {
|
||||
command: CommandTypes.GetCombinedCodeFix;
|
||||
arguments: GetCombinedCodeFixRequestArgs;
|
||||
}
|
||||
|
||||
// TODO: GH#20538
|
||||
/* @internal */
|
||||
export interface GetCombinedCodeFixResponse extends Response {
|
||||
body: CombinedCodeActions;
|
||||
}
|
||||
|
||||
export interface ApplyCodeActionCommandRequest extends Request {
|
||||
command: CommandTypes.ApplyCodeActionCommand;
|
||||
arguments: ApplyCodeActionCommandRequestArgs;
|
||||
@@ -601,7 +619,21 @@ namespace ts.server.protocol {
|
||||
/**
|
||||
* Errorcodes we want to get the fixes for.
|
||||
*/
|
||||
errorCodes?: number[];
|
||||
errorCodes?: ReadonlyArray<number>;
|
||||
}
|
||||
|
||||
// TODO: GH#20538
|
||||
/* @internal */
|
||||
export interface GetCombinedCodeFixRequestArgs {
|
||||
scope: GetCombinedCodeFixScope;
|
||||
fixId: {};
|
||||
}
|
||||
|
||||
// TODO: GH#20538
|
||||
/* @internal */
|
||||
export interface GetCombinedCodeFixScope {
|
||||
type: "file";
|
||||
args: FileRequestArgs;
|
||||
}
|
||||
|
||||
export interface ApplyCodeActionCommandRequestArgs {
|
||||
@@ -1587,7 +1619,7 @@ namespace ts.server.protocol {
|
||||
|
||||
export interface CodeFixResponse extends Response {
|
||||
/** The code actions that are available */
|
||||
body?: CodeAction[];
|
||||
body?: CodeAction[]; // TODO: GH#20538 CodeFixAction[]
|
||||
}
|
||||
|
||||
export interface CodeAction {
|
||||
@@ -1599,6 +1631,23 @@ namespace ts.server.protocol {
|
||||
commands?: {}[];
|
||||
}
|
||||
|
||||
// TODO: GH#20538
|
||||
/* @internal */
|
||||
export interface CombinedCodeActions {
|
||||
changes: ReadonlyArray<FileCodeEdits>;
|
||||
commands?: ReadonlyArray<{}>;
|
||||
}
|
||||
|
||||
// TODO: GH#20538
|
||||
/* @internal */
|
||||
export interface CodeFixAction extends CodeAction {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
fixId?: {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format and format on key response message.
|
||||
*/
|
||||
|
||||
+27
-12
@@ -1538,18 +1538,14 @@ namespace ts.server {
|
||||
const oldText = snapshot.getText(0, snapshot.getLength());
|
||||
mappedRenameLocation = getLocationInNewDocument(oldText, renameFilename, renameLocation, edits);
|
||||
}
|
||||
return {
|
||||
renameLocation: mappedRenameLocation,
|
||||
renameFilename,
|
||||
edits: edits.map(change => this.mapTextChangesToCodeEdits(project, change))
|
||||
};
|
||||
return { renameLocation: mappedRenameLocation, renameFilename, edits: this.mapTextChangesToCodeEdits(project, edits) };
|
||||
}
|
||||
else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): protocol.CodeAction[] | CodeAction[] {
|
||||
private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.CodeAction> | ReadonlyArray<CodeAction> {
|
||||
if (args.errorCodes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -1571,6 +1567,19 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
private getCombinedCodeFix({ scope, fixId }: protocol.GetCombinedCodeFixRequestArgs, simplifiedResult: boolean): protocol.CombinedCodeActions | CombinedCodeActions {
|
||||
Debug.assert(scope.type === "file");
|
||||
const { file, project } = this.getFileAndProject(scope.args);
|
||||
const formatOptions = this.projectService.getFormatCodeOptions(file);
|
||||
const res = project.getLanguageService().getCombinedCodeFix({ type: "file", fileName: file }, fixId, formatOptions);
|
||||
if (simplifiedResult) {
|
||||
return { changes: this.mapTextChangesToCodeEdits(project, res.changes), commands: res.commands };
|
||||
}
|
||||
else {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
private applyCodeActionCommand(args: protocol.ApplyCodeActionCommandRequestArgs): {} {
|
||||
const commands = args.command as CodeActionCommand | CodeActionCommand[]; // They should be sending back the command we sent them.
|
||||
for (const command of toArray(commands)) {
|
||||
@@ -1605,15 +1614,15 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private mapCodeAction({ description, changes: unmappedChanges, commands }: CodeAction, scriptInfo: ScriptInfo): protocol.CodeAction {
|
||||
const changes = unmappedChanges.map(change => ({
|
||||
fileName: change.fileName,
|
||||
textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo))
|
||||
}));
|
||||
const changes = unmappedChanges.map(change => this.mapTextChangesToCodeEditsUsingScriptinfo(change, scriptInfo));
|
||||
return { description, changes, commands };
|
||||
}
|
||||
|
||||
private mapTextChangesToCodeEdits(project: Project, textChanges: FileTextChanges): protocol.FileCodeEdits {
|
||||
const scriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(textChanges.fileName));
|
||||
private mapTextChangesToCodeEdits(project: Project, textChanges: ReadonlyArray<FileTextChanges>): protocol.FileCodeEdits[] {
|
||||
return textChanges.map(change => this.mapTextChangesToCodeEditsUsingScriptinfo(change, project.getScriptInfoForNormalizedPath(toNormalizedPath(change.fileName))));
|
||||
}
|
||||
|
||||
private mapTextChangesToCodeEditsUsingScriptinfo(textChanges: FileTextChanges, scriptInfo: ScriptInfo): protocol.FileCodeEdits {
|
||||
return {
|
||||
fileName: textChanges.fileName,
|
||||
textChanges: textChanges.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo))
|
||||
@@ -1960,6 +1969,12 @@ namespace ts.server {
|
||||
[CommandNames.GetCodeFixesFull]: (request: protocol.CodeFixRequest) => {
|
||||
return this.requiredResponse(this.getCodeFixes(request.arguments, /*simplifiedResult*/ false));
|
||||
},
|
||||
[CommandNames.GetCombinedCodeFix]: (request: protocol.GetCombinedCodeFixRequest) => {
|
||||
return this.requiredResponse(this.getCombinedCodeFix(request.arguments, /*simplifiedResult*/ true));
|
||||
},
|
||||
[CommandNames.GetCombinedCodeFixFull]: (request: protocol.GetCombinedCodeFixRequest) => {
|
||||
return this.requiredResponse(this.getCombinedCodeFix(request.arguments, /*simplifiedResult*/ false));
|
||||
},
|
||||
[CommandNames.ApplyCodeActionCommand]: (request: protocol.ApplyCodeActionCommandRequest) => {
|
||||
return this.requiredResponse(this.applyCodeActionCommand(request.arguments));
|
||||
},
|
||||
|
||||
@@ -1,40 +1,56 @@
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
export interface CodeFix {
|
||||
export interface CodeFixRegistration {
|
||||
errorCodes: number[];
|
||||
getCodeActions(context: CodeFixContext): CodeAction[] | undefined;
|
||||
getCodeActions(context: CodeFixContext): CodeFixAction[] | undefined;
|
||||
fixIds?: string[];
|
||||
getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions;
|
||||
}
|
||||
|
||||
export interface CodeFixContext extends textChanges.TextChangesContext {
|
||||
errorCode: number;
|
||||
export interface CodeFixContextBase extends textChanges.TextChangesContext {
|
||||
sourceFile: SourceFile;
|
||||
span: TextSpan;
|
||||
program: Program;
|
||||
host: LanguageServiceHost;
|
||||
cancellationToken: CancellationToken;
|
||||
}
|
||||
|
||||
export namespace codefix {
|
||||
const codeFixes: CodeFix[][] = [];
|
||||
export interface CodeFixAllContext extends CodeFixContextBase {
|
||||
fixId: {};
|
||||
}
|
||||
|
||||
export function registerCodeFix(codeFix: CodeFix) {
|
||||
forEach(codeFix.errorCodes, error => {
|
||||
let fixes = codeFixes[error];
|
||||
if (!fixes) {
|
||||
fixes = [];
|
||||
codeFixes[error] = fixes;
|
||||
export interface CodeFixContext extends CodeFixContextBase {
|
||||
errorCode: number;
|
||||
span: TextSpan;
|
||||
}
|
||||
|
||||
export namespace codefix {
|
||||
const codeFixRegistrations: CodeFixRegistration[][] = [];
|
||||
const fixIdToRegistration = createMap<CodeFixRegistration>();
|
||||
|
||||
export function registerCodeFix(reg: CodeFixRegistration) {
|
||||
for (const error of reg.errorCodes) {
|
||||
let registrations = codeFixRegistrations[error];
|
||||
if (!registrations) {
|
||||
registrations = [];
|
||||
codeFixRegistrations[error] = registrations;
|
||||
}
|
||||
fixes.push(codeFix);
|
||||
});
|
||||
registrations.push(reg);
|
||||
}
|
||||
if (reg.fixIds) {
|
||||
for (const fixId of reg.fixIds) {
|
||||
Debug.assert(!fixIdToRegistration.has(fixId));
|
||||
fixIdToRegistration.set(fixId, reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getSupportedErrorCodes() {
|
||||
return Object.keys(codeFixes);
|
||||
return Object.keys(codeFixRegistrations);
|
||||
}
|
||||
|
||||
export function getFixes(context: CodeFixContext): CodeAction[] {
|
||||
const fixes = codeFixes[context.errorCode];
|
||||
const allActions: CodeAction[] = [];
|
||||
export function getFixes(context: CodeFixContext): CodeFixAction[] {
|
||||
const fixes = codeFixRegistrations[context.errorCode];
|
||||
const allActions: CodeFixAction[] = [];
|
||||
|
||||
forEach(fixes, f => {
|
||||
const actions = f.getCodeActions(context);
|
||||
@@ -52,5 +68,40 @@ namespace ts {
|
||||
|
||||
return allActions;
|
||||
}
|
||||
|
||||
export function getAllFixes(context: CodeFixAllContext): CombinedCodeActions {
|
||||
// Currently fixId is always a string.
|
||||
return fixIdToRegistration.get(cast(context.fixId, isString))!.getAllCodeActions!(context);
|
||||
}
|
||||
|
||||
function createCombinedCodeActions(changes: FileTextChanges[], commands?: CodeActionCommand[]): CombinedCodeActions {
|
||||
return { changes, commands };
|
||||
}
|
||||
|
||||
export function createFileTextChanges(fileName: string, textChanges: TextChange[]): FileTextChanges {
|
||||
return { fileName, textChanges };
|
||||
}
|
||||
|
||||
export function codeFixAll(context: CodeFixAllContext, errorCodes: number[], use: (changes: textChanges.ChangeTracker, error: Diagnostic, commands: Push<CodeActionCommand>) => void): CombinedCodeActions {
|
||||
const commands: CodeActionCommand[] = [];
|
||||
const changes = textChanges.ChangeTracker.with(context, t =>
|
||||
eachDiagnostic(context, errorCodes, diag => use(t, diag, commands)));
|
||||
return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands);
|
||||
}
|
||||
|
||||
export function codeFixAllWithTextChanges(context: CodeFixAllContext, errorCodes: number[], use: (changes: Push<TextChange>, error: Diagnostic) => void): CombinedCodeActions {
|
||||
const changes: TextChange[] = [];
|
||||
eachDiagnostic(context, errorCodes, diag => use(changes, diag));
|
||||
changes.sort((a, b) => b.span.start - a.span.start);
|
||||
return createCombinedCodeActions([createFileTextChanges(context.sourceFile.fileName, changes)]);
|
||||
}
|
||||
|
||||
function eachDiagnostic({ program, sourceFile }: CodeFixAllContext, errorCodes: number[], cb: (diag: Diagnostic) => void): void {
|
||||
for (const diag of program.getSemanticDiagnostics(sourceFile)) {
|
||||
if (contains(errorCodes, diag.code)) {
|
||||
cb(diag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,22 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const fixId = "addMissingInvocationForDecorator";
|
||||
const errorCodes = [Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0.code];
|
||||
registerCodeFix({
|
||||
errorCodes: [Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0.code],
|
||||
getCodeActions: (context: CodeFixContext) => {
|
||||
const sourceFile = context.sourceFile;
|
||||
const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false);
|
||||
const decorator = getAncestor(token, SyntaxKind.Decorator) as Decorator;
|
||||
Debug.assert(!!decorator, "Expected position to be owned by a decorator.");
|
||||
const replacement = createCall(decorator.expression, /*typeArguments*/ undefined, /*argumentsArray*/ undefined);
|
||||
const changeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
changeTracker.replaceNode(sourceFile, decorator.expression, replacement);
|
||||
|
||||
return [{
|
||||
description: getLocaleSpecificMessage(Diagnostics.Call_decorator_expression),
|
||||
changes: changeTracker.getChanges()
|
||||
}];
|
||||
}
|
||||
errorCodes,
|
||||
getCodeActions: (context) => {
|
||||
const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start));
|
||||
return [{ description: getLocaleSpecificMessage(Diagnostics.Call_decorator_expression), changes, fixId }];
|
||||
},
|
||||
fixIds: [fixId],
|
||||
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file!, diag.start!)),
|
||||
});
|
||||
|
||||
function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) {
|
||||
const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false);
|
||||
const decorator = findAncestor(token, isDecorator)!;
|
||||
Debug.assert(!!decorator, "Expected position to be owned by a decorator.");
|
||||
const replacement = createCall(decorator.expression, /*typeArguments*/ undefined, /*argumentsArray*/ undefined);
|
||||
changeTracker.replaceNode(sourceFile, decorator.expression, replacement);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,36 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const fixId = "correctQualifiedNameToIndexedAccessType";
|
||||
const errorCodes = [Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1.code];
|
||||
registerCodeFix({
|
||||
errorCodes: [Diagnostics.Cannot_access_0_1_because_0_is_a_type_but_not_a_namespace_Did_you_mean_to_retrieve_the_type_of_the_property_1_in_0_with_0_1.code],
|
||||
getCodeActions: (context: CodeFixContext) => {
|
||||
const sourceFile = context.sourceFile;
|
||||
const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false);
|
||||
const qualifiedName = getAncestor(token, SyntaxKind.QualifiedName) as QualifiedName;
|
||||
Debug.assert(!!qualifiedName, "Expected position to be owned by a qualified name.");
|
||||
if (!isIdentifier(qualifiedName.left)) {
|
||||
return undefined;
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const qualifiedName = getQualifiedName(context.sourceFile, context.span.start);
|
||||
if (!qualifiedName) return undefined;
|
||||
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, qualifiedName));
|
||||
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Rewrite_as_the_indexed_access_type_0), [`${qualifiedName.left.text}["${qualifiedName.right.text}"]`]);
|
||||
return [{ description, changes, fixId }];
|
||||
},
|
||||
fixIds: [fixId],
|
||||
getAllCodeActions: (context) => codeFixAll(context, errorCodes, (changes, diag) => {
|
||||
const q = getQualifiedName(diag.file, diag.start);
|
||||
if (q) {
|
||||
doChange(changes, diag.file, q);
|
||||
}
|
||||
const leftText = qualifiedName.left.getText(sourceFile);
|
||||
const rightText = qualifiedName.right.getText(sourceFile);
|
||||
const replacement = createIndexedAccessTypeNode(
|
||||
createTypeReferenceNode(qualifiedName.left, /*typeArguments*/ undefined),
|
||||
createLiteralTypeNode(createLiteral(rightText)));
|
||||
const changeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
changeTracker.replaceNode(sourceFile, qualifiedName, replacement);
|
||||
|
||||
return [{
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Rewrite_as_the_indexed_access_type_0), [`${leftText}["${rightText}"]`]),
|
||||
changes: changeTracker.getChanges()
|
||||
}];
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
function getQualifiedName(sourceFile: SourceFile, pos: number): QualifiedName & { left: Identifier } | undefined {
|
||||
const qualifiedName = findAncestor(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false), isQualifiedName)!;
|
||||
Debug.assert(!!qualifiedName, "Expected position to be owned by a qualified name.");
|
||||
return isIdentifier(qualifiedName.left) ? qualifiedName as QualifiedName & { left: Identifier } : undefined;
|
||||
}
|
||||
|
||||
function doChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, qualifiedName: QualifiedName): void {
|
||||
const rightText = qualifiedName.right.text;
|
||||
const replacement = createIndexedAccessTypeNode(
|
||||
createTypeReferenceNode(qualifiedName.left, /*typeArguments*/ undefined),
|
||||
createLiteralTypeNode(createLiteral(rightText)));
|
||||
changeTracker.replaceNode(sourceFile, qualifiedName, replacement);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,47 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
registerCodeFix({
|
||||
errorCodes: getApplicableDiagnosticCodes(),
|
||||
getCodeActions: getDisableJsDiagnosticsCodeActions
|
||||
const fixId = "disableJsDiagnostics";
|
||||
const errorCodes = mapDefined(Object.keys(Diagnostics), key => {
|
||||
const diag = (Diagnostics as MapLike<DiagnosticMessage>)[key];
|
||||
return diag.category === DiagnosticCategory.Error ? diag.code : undefined;
|
||||
});
|
||||
|
||||
function getApplicableDiagnosticCodes(): number[] {
|
||||
const allDiagnostcs = <MapLike<DiagnosticMessage>>Diagnostics;
|
||||
return Object.keys(allDiagnostcs)
|
||||
.filter(d => allDiagnostcs[d] && allDiagnostcs[d].category === DiagnosticCategory.Error)
|
||||
.map(d => allDiagnostcs[d].code);
|
||||
}
|
||||
registerCodeFix({
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const { sourceFile, program, newLineCharacter, span } = context;
|
||||
|
||||
function getIgnoreCommentLocationForLocation(sourceFile: SourceFile, position: number, newLineCharacter: string) {
|
||||
if (!isInJavaScriptFile(sourceFile) || !isCheckJsEnabledForFile(sourceFile, program.getCompilerOptions())) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [{
|
||||
description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message),
|
||||
changes: [createFileTextChanges(sourceFile.fileName, [getIgnoreCommentLocationForLocation(sourceFile, span.start, newLineCharacter)])],
|
||||
fixId,
|
||||
},
|
||||
{
|
||||
description: getLocaleSpecificMessage(Diagnostics.Disable_checking_for_this_file),
|
||||
changes: [createFileTextChanges(sourceFile.fileName, [{
|
||||
span: {
|
||||
start: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.pos : 0,
|
||||
length: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.end - sourceFile.checkJsDirective.pos : 0
|
||||
},
|
||||
newText: `// @ts-nocheck${newLineCharacter}`
|
||||
}])],
|
||||
// fixId unnecessary because adding `// @ts-nocheck` even once will ignore every error in the file.
|
||||
fixId: undefined,
|
||||
}];
|
||||
},
|
||||
fixIds: [fixId], // No point applying as a group, doing it once will fix all errors
|
||||
getAllCodeActions: context => codeFixAllWithTextChanges(context, errorCodes, (changes, err) => {
|
||||
if (err.start !== undefined) {
|
||||
changes.push(getIgnoreCommentLocationForLocation(err.file!, err.start, context.newLineCharacter));
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
function getIgnoreCommentLocationForLocation(sourceFile: SourceFile, position: number, newLineCharacter: string): TextChange {
|
||||
const { line } = getLineAndCharacterOfPosition(sourceFile, position);
|
||||
const lineStartPosition = getStartPositionOfLine(line, sourceFile);
|
||||
const startPosition = getFirstNonSpaceCharacterPosition(sourceFile.text, lineStartPosition);
|
||||
@@ -38,33 +67,4 @@ namespace ts.codefix {
|
||||
newText: `${position === startPosition ? "" : newLineCharacter}// @ts-ignore${newLineCharacter}`
|
||||
};
|
||||
}
|
||||
|
||||
function getDisableJsDiagnosticsCodeActions(context: CodeFixContext): CodeAction[] | undefined {
|
||||
const { sourceFile, program, newLineCharacter, span } = context;
|
||||
|
||||
if (!isInJavaScriptFile(sourceFile) || !isCheckJsEnabledForFile(sourceFile, program.getCompilerOptions())) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [{
|
||||
description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message),
|
||||
changes: [{
|
||||
fileName: sourceFile.fileName,
|
||||
textChanges: [getIgnoreCommentLocationForLocation(sourceFile, span.start, newLineCharacter)]
|
||||
}]
|
||||
},
|
||||
{
|
||||
description: getLocaleSpecificMessage(Diagnostics.Disable_checking_for_this_file),
|
||||
changes: [{
|
||||
fileName: sourceFile.fileName,
|
||||
textChanges: [{
|
||||
span: {
|
||||
start: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.pos : 0,
|
||||
length: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.end - sourceFile.checkJsDirective.pos : 0
|
||||
},
|
||||
newText: `// @ts-nocheck${newLineCharacter}`
|
||||
}]
|
||||
}]
|
||||
}];
|
||||
}
|
||||
}
|
||||
@@ -1,208 +1,194 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const errorCodes = [
|
||||
Diagnostics.Property_0_does_not_exist_on_type_1.code,
|
||||
Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code,
|
||||
];
|
||||
const fixId = "addMissingMember";
|
||||
registerCodeFix({
|
||||
errorCodes: [Diagnostics.Property_0_does_not_exist_on_type_1.code,
|
||||
Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code],
|
||||
getCodeActions: getActionsForAddMissingMember
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const info = getInfo(context.sourceFile, context.span.start, context.program.getTypeChecker());
|
||||
if (!info) return undefined;
|
||||
const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
|
||||
const methodCodeAction = call && getActionForMethodDeclaration(context, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs);
|
||||
const addMember = inJs ?
|
||||
singleElementArray(getActionsForAddMissingMemberInJavaScriptFile(context, classDeclarationSourceFile, classDeclaration, token.text, makeStatic)) :
|
||||
getActionsForAddMissingMemberInTypeScriptFile(context, classDeclarationSourceFile, classDeclaration, token, makeStatic);
|
||||
return concatenate(singleElementArray(methodCodeAction), addMember);
|
||||
},
|
||||
fixIds: [fixId],
|
||||
getAllCodeActions: context => {
|
||||
const seenNames = createMap<true>();
|
||||
return codeFixAll(context, errorCodes, (changes, diag) => {
|
||||
const { newLineCharacter, program } = context;
|
||||
const info = getInfo(diag.file!, diag.start!, context.program.getTypeChecker());
|
||||
if (!info) return;
|
||||
const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
|
||||
if (!addToSeen(seenNames, token.text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always prefer to add a method declaration if possible.
|
||||
if (call) {
|
||||
addMethodDeclaration(changes, classDeclarationSourceFile, classDeclaration, token, call, newLineCharacter, makeStatic, inJs);
|
||||
}
|
||||
else {
|
||||
if (inJs) {
|
||||
addMissingMemberInJs(changes, classDeclarationSourceFile, classDeclaration, token.text, makeStatic, newLineCharacter);
|
||||
}
|
||||
else {
|
||||
const typeNode = getTypeNode(program.getTypeChecker(), classDeclaration, token);
|
||||
addPropertyDeclaration(changes, classDeclarationSourceFile, classDeclaration, token.text, typeNode, makeStatic, newLineCharacter);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function getActionsForAddMissingMember(context: CodeFixContext): CodeAction[] | undefined {
|
||||
|
||||
const tokenSourceFile = context.sourceFile;
|
||||
const start = context.span.start;
|
||||
interface Info { token: Identifier; classDeclaration: ClassLikeDeclaration; makeStatic: boolean; classDeclarationSourceFile: SourceFile; inJs: boolean; call: CallExpression; }
|
||||
function getInfo(tokenSourceFile: SourceFile, tokenPos: number, checker: TypeChecker): Info | undefined {
|
||||
// The identifier of the missing property. eg:
|
||||
// this.missing = 1;
|
||||
// ^^^^^^^
|
||||
const token = getTokenAtPosition(tokenSourceFile, start, /*includeJsDocComment*/ false);
|
||||
|
||||
if (token.kind !== SyntaxKind.Identifier) {
|
||||
const token = getTokenAtPosition(tokenSourceFile, tokenPos, /*includeJsDocComment*/ false);
|
||||
if (!isIdentifier(token)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!isPropertyAccessExpression(token.parent)) {
|
||||
const classAndMakeStatic = getClassAndMakeStatic(token, checker);
|
||||
if (!classAndMakeStatic) {
|
||||
return undefined;
|
||||
}
|
||||
const { classDeclaration, makeStatic } = classAndMakeStatic;
|
||||
const classDeclarationSourceFile = classDeclaration.getSourceFile();
|
||||
const inJs = isInJavaScriptFile(classDeclarationSourceFile);
|
||||
const call = tryCast(token.parent.parent, isCallExpression);
|
||||
|
||||
return { token, classDeclaration, makeStatic, classDeclarationSourceFile, inJs, call };
|
||||
}
|
||||
|
||||
function getClassAndMakeStatic(token: Node, checker: TypeChecker): { readonly classDeclaration: ClassLikeDeclaration, readonly makeStatic: boolean } | undefined {
|
||||
const { parent } = token;
|
||||
if (!isPropertyAccessExpression(parent)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const tokenName = token.getText(tokenSourceFile);
|
||||
|
||||
let makeStatic = false;
|
||||
let classDeclaration: ClassLikeDeclaration;
|
||||
|
||||
if (token.parent.expression.kind === SyntaxKind.ThisKeyword) {
|
||||
if (parent.expression.kind === SyntaxKind.ThisKeyword) {
|
||||
const containingClassMemberDeclaration = getThisContainer(token, /*includeArrowFunctions*/ false);
|
||||
if (!isClassElement(containingClassMemberDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
classDeclaration = <ClassLikeDeclaration>containingClassMemberDeclaration.parent;
|
||||
|
||||
const classDeclaration = containingClassMemberDeclaration.parent;
|
||||
// Property accesses on `this` in a static method are accesses of a static member.
|
||||
makeStatic = classDeclaration && hasModifier(containingClassMemberDeclaration, ModifierFlags.Static);
|
||||
return isClassLike(classDeclaration) ? { classDeclaration, makeStatic: hasModifier(containingClassMemberDeclaration, ModifierFlags.Static) } : undefined;
|
||||
}
|
||||
else {
|
||||
|
||||
const checker = context.program.getTypeChecker();
|
||||
const leftExpression = token.parent.expression;
|
||||
const leftExpressionType = checker.getTypeAtLocation(leftExpression);
|
||||
|
||||
if (leftExpressionType.flags & TypeFlags.Object) {
|
||||
const symbol = leftExpressionType.symbol;
|
||||
if (symbol.flags & SymbolFlags.Class) {
|
||||
classDeclaration = symbol.declarations && <ClassLikeDeclaration>symbol.declarations[0];
|
||||
if (leftExpressionType !== checker.getDeclaredTypeOfSymbol(symbol)) {
|
||||
// The expression is a class symbol but the type is not the instance-side.
|
||||
makeStatic = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!classDeclaration || !isClassLike(classDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const classDeclarationSourceFile = getSourceFileOfNode(classDeclaration);
|
||||
|
||||
return isInJavaScriptFile(classDeclarationSourceFile) ?
|
||||
getActionsForAddMissingMemberInJavaScriptFile(classDeclaration, makeStatic) :
|
||||
getActionsForAddMissingMemberInTypeScriptFile(classDeclaration, makeStatic);
|
||||
|
||||
function getActionsForAddMissingMemberInJavaScriptFile(classDeclaration: ClassLikeDeclaration, makeStatic: boolean): CodeAction[] | undefined {
|
||||
let actions: CodeAction[];
|
||||
|
||||
const methodCodeAction = getActionForMethodDeclaration(/*includeTypeScriptSyntax*/ false);
|
||||
if (methodCodeAction) {
|
||||
actions = [methodCodeAction];
|
||||
}
|
||||
|
||||
if (makeStatic) {
|
||||
if (classDeclaration.kind === SyntaxKind.ClassExpression) {
|
||||
return actions;
|
||||
}
|
||||
|
||||
const className = classDeclaration.name.getText();
|
||||
|
||||
const staticInitialization = createStatement(createAssignment(
|
||||
createPropertyAccess(createIdentifier(className), tokenName),
|
||||
createIdentifier("undefined")));
|
||||
|
||||
const staticInitializationChangeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
staticInitializationChangeTracker.insertNodeAfter(
|
||||
classDeclarationSourceFile,
|
||||
classDeclaration,
|
||||
staticInitialization,
|
||||
{ prefix: context.newLineCharacter, suffix: context.newLineCharacter });
|
||||
const initializeStaticAction = {
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_static_property_0), [tokenName]),
|
||||
changes: staticInitializationChangeTracker.getChanges()
|
||||
};
|
||||
|
||||
(actions || (actions = [])).push(initializeStaticAction);
|
||||
return actions;
|
||||
}
|
||||
else {
|
||||
const classConstructor = getFirstConstructorWithBody(classDeclaration);
|
||||
if (!classConstructor) {
|
||||
return actions;
|
||||
}
|
||||
|
||||
const propertyInitialization = createStatement(createAssignment(
|
||||
createPropertyAccess(createThis(), tokenName),
|
||||
createIdentifier("undefined")));
|
||||
|
||||
const propertyInitializationChangeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
propertyInitializationChangeTracker.insertNodeBefore(
|
||||
classDeclarationSourceFile,
|
||||
classConstructor.body.getLastToken(),
|
||||
propertyInitialization,
|
||||
{ suffix: context.newLineCharacter });
|
||||
|
||||
const initializeAction = {
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_property_0_in_the_constructor), [tokenName]),
|
||||
changes: propertyInitializationChangeTracker.getChanges()
|
||||
};
|
||||
|
||||
(actions || (actions = [])).push(initializeAction);
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
function getActionsForAddMissingMemberInTypeScriptFile(classDeclaration: ClassLikeDeclaration, makeStatic: boolean): CodeAction[] | undefined {
|
||||
let actions: CodeAction[];
|
||||
|
||||
const methodCodeAction = getActionForMethodDeclaration(/*includeTypeScriptSyntax*/ true);
|
||||
if (methodCodeAction) {
|
||||
actions = [methodCodeAction];
|
||||
}
|
||||
|
||||
let typeNode: TypeNode;
|
||||
if (token.parent.parent.kind === SyntaxKind.BinaryExpression) {
|
||||
const binaryExpression = token.parent.parent as BinaryExpression;
|
||||
const otherExpression = token.parent === binaryExpression.left ? binaryExpression.right : binaryExpression.left;
|
||||
const checker = context.program.getTypeChecker();
|
||||
const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(otherExpression)));
|
||||
typeNode = checker.typeToTypeNode(widenedType, classDeclaration);
|
||||
}
|
||||
typeNode = typeNode || createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
||||
|
||||
const property = createProperty(
|
||||
/*decorators*/undefined,
|
||||
/*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined,
|
||||
tokenName,
|
||||
/*questionToken*/ undefined,
|
||||
typeNode,
|
||||
/*initializer*/ undefined);
|
||||
const propertyChangeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
propertyChangeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, property, context.newLineCharacter);
|
||||
|
||||
const diag = makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0;
|
||||
actions = append(actions, {
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(diag), [tokenName]),
|
||||
changes: propertyChangeTracker.getChanges()
|
||||
});
|
||||
|
||||
if (!makeStatic) {
|
||||
// Index signatures cannot have the static modifier.
|
||||
const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword);
|
||||
const indexingParameter = createParameter(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
/*dotDotDotToken*/ undefined,
|
||||
"x",
|
||||
/*questionToken*/ undefined,
|
||||
stringTypeNode,
|
||||
/*initializer*/ undefined);
|
||||
const indexSignature = createIndexSignature(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
[indexingParameter],
|
||||
typeNode);
|
||||
|
||||
const indexSignatureChangeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
indexSignatureChangeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature, context.newLineCharacter);
|
||||
|
||||
actions.push({
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]),
|
||||
changes: indexSignatureChangeTracker.getChanges()
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
function getActionForMethodDeclaration(includeTypeScriptSyntax: boolean): CodeAction | undefined {
|
||||
if (token.parent.parent.kind === SyntaxKind.CallExpression) {
|
||||
const callExpression = <CallExpression>token.parent.parent;
|
||||
const methodDeclaration = createMethodFromCallExpression(callExpression, tokenName, includeTypeScriptSyntax, makeStatic);
|
||||
|
||||
const methodDeclarationChangeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
methodDeclarationChangeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, methodDeclaration, context.newLineCharacter);
|
||||
const diag = makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0;
|
||||
return {
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(diag), [tokenName]),
|
||||
changes: methodDeclarationChangeTracker.getChanges()
|
||||
};
|
||||
const leftExpressionType = checker.getTypeAtLocation(parent.expression);
|
||||
const { symbol } = leftExpressionType;
|
||||
if (!(leftExpressionType.flags & TypeFlags.Object && symbol.flags & SymbolFlags.Class)) {
|
||||
return undefined;
|
||||
}
|
||||
const classDeclaration = cast(first(symbol.declarations), isClassLike);
|
||||
// The expression is a class symbol but the type is not the instance-side.
|
||||
return { classDeclaration, makeStatic: leftExpressionType !== checker.getDeclaredTypeOfSymbol(symbol) };
|
||||
}
|
||||
}
|
||||
|
||||
function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined {
|
||||
const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, classDeclarationSourceFile, classDeclaration, tokenName, makeStatic, context.newLineCharacter));
|
||||
if (changes.length === 0) return undefined;
|
||||
const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Initialize_static_property_0 : Diagnostics.Initialize_property_0_in_the_constructor), [tokenName]);
|
||||
return { description, changes, fixId };
|
||||
}
|
||||
|
||||
function addMissingMemberInJs(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean, newLineCharacter: string): void {
|
||||
if (makeStatic) {
|
||||
if (classDeclaration.kind === SyntaxKind.ClassExpression) {
|
||||
return;
|
||||
}
|
||||
const className = classDeclaration.name.getText();
|
||||
const staticInitialization = initializePropertyToUndefined(createIdentifier(className), tokenName);
|
||||
changeTracker.insertNodeAfter(classDeclarationSourceFile, classDeclaration, staticInitialization, { prefix: newLineCharacter, suffix: newLineCharacter });
|
||||
}
|
||||
else {
|
||||
const classConstructor = getFirstConstructorWithBody(classDeclaration);
|
||||
if (!classConstructor) {
|
||||
return;
|
||||
}
|
||||
const propertyInitialization = initializePropertyToUndefined(createThis(), tokenName);
|
||||
changeTracker.insertNodeBefore(classDeclarationSourceFile, classConstructor.body.getLastToken(), propertyInitialization, { suffix: newLineCharacter });
|
||||
}
|
||||
}
|
||||
|
||||
function initializePropertyToUndefined(obj: Expression, propertyName: string) {
|
||||
return createStatement(createAssignment(createPropertyAccess(obj, propertyName), createIdentifier("undefined")));
|
||||
}
|
||||
|
||||
function getActionsForAddMissingMemberInTypeScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, makeStatic: boolean): CodeFixAction[] | undefined {
|
||||
const typeNode = getTypeNode(context.program.getTypeChecker(), classDeclaration, token);
|
||||
const addProp = createAddPropertyDeclarationAction(context, classDeclarationSourceFile, classDeclaration, makeStatic, token.text, typeNode);
|
||||
return makeStatic ? [addProp] : [addProp, createAddIndexSignatureAction(context, classDeclarationSourceFile, classDeclaration, token.text, typeNode)];
|
||||
}
|
||||
|
||||
function getTypeNode(checker: TypeChecker, classDeclaration: ClassLikeDeclaration, token: Node) {
|
||||
let typeNode: TypeNode;
|
||||
if (token.parent.parent.kind === SyntaxKind.BinaryExpression) {
|
||||
const binaryExpression = token.parent.parent as BinaryExpression;
|
||||
const otherExpression = token.parent === binaryExpression.left ? binaryExpression.right : binaryExpression.left;
|
||||
const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(otherExpression)));
|
||||
typeNode = checker.typeToTypeNode(widenedType, classDeclaration);
|
||||
}
|
||||
return typeNode || createKeywordTypeNode(SyntaxKind.AnyKeyword);
|
||||
}
|
||||
|
||||
function createAddPropertyDeclarationAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, makeStatic: boolean, tokenName: string, typeNode: TypeNode): CodeFixAction {
|
||||
const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0), [tokenName]);
|
||||
const changes = textChanges.ChangeTracker.with(context, t => addPropertyDeclaration(t, classDeclarationSourceFile, classDeclaration, tokenName, typeNode, makeStatic, context.newLineCharacter));
|
||||
return { description, changes, fixId };
|
||||
}
|
||||
|
||||
function addPropertyDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, typeNode: TypeNode, makeStatic: boolean, newLineCharacter: string): void {
|
||||
const property = createProperty(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined,
|
||||
tokenName,
|
||||
/*questionToken*/ undefined,
|
||||
typeNode,
|
||||
/*initializer*/ undefined);
|
||||
changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, property, newLineCharacter);
|
||||
}
|
||||
|
||||
function createAddIndexSignatureAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, typeNode: TypeNode): CodeFixAction {
|
||||
// Index signatures cannot have the static modifier.
|
||||
const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword);
|
||||
const indexingParameter = createParameter(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
/*dotDotDotToken*/ undefined,
|
||||
"x",
|
||||
/*questionToken*/ undefined,
|
||||
stringTypeNode,
|
||||
/*initializer*/ undefined);
|
||||
const indexSignature = createIndexSignature(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
[indexingParameter],
|
||||
typeNode);
|
||||
|
||||
const changes = textChanges.ChangeTracker.with(context, t => t.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature, context.newLineCharacter));
|
||||
// No fixId here because code-fix-all currently only works on adding individual named properties.
|
||||
return { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), changes, fixId: undefined };
|
||||
}
|
||||
|
||||
function getActionForMethodDeclaration(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean): CodeFixAction | undefined {
|
||||
const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0), [token.text]);
|
||||
const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(t, classDeclarationSourceFile, classDeclaration, token, callExpression, context.newLineCharacter, makeStatic, inJs));
|
||||
return { description, changes, fixId };
|
||||
}
|
||||
|
||||
function addMethodDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, newLineCharacter: string, makeStatic: boolean, inJs: boolean) {
|
||||
const methodDeclaration = createMethodFromCallExpression(callExpression, token.text, inJs, makeStatic);
|
||||
changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, methodDeclaration, newLineCharacter);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,41 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const fixId = "fixCannotFindModule";
|
||||
const errorCodes = [Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type.code];
|
||||
registerCodeFix({
|
||||
errorCodes: [
|
||||
Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type.code,
|
||||
errorCodes,
|
||||
getCodeActions: context => [
|
||||
{ fixId, ...tryGetCodeActionForInstallPackageTypes(context.host, context.sourceFile.fileName, getModuleName(context.sourceFile, context.span.start)) }
|
||||
],
|
||||
getCodeActions: context => {
|
||||
const { sourceFile, span: { start } } = context;
|
||||
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
|
||||
if (!isStringLiteral(token)) {
|
||||
throw Debug.fail(); // These errors should only happen on the module name.
|
||||
fixIds: [fixId],
|
||||
getAllCodeActions: context => codeFixAll(context, errorCodes, (_, diag, commands) => {
|
||||
const pkg = getTypesPackageNameToInstall(context.host, getModuleName(diag.file, diag.start));
|
||||
if (pkg) {
|
||||
commands.push(getCommand(diag.file.fileName, pkg));
|
||||
}
|
||||
|
||||
const action = tryGetCodeActionForInstallPackageTypes(context.host, sourceFile.fileName, token.text);
|
||||
return action && [action];
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
export function tryGetCodeActionForInstallPackageTypes(host: LanguageServiceHost, fileName: string, moduleName: string): CodeAction | undefined {
|
||||
function getModuleName(sourceFile: SourceFile, pos: number): string {
|
||||
return cast(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false), isStringLiteral).text;
|
||||
}
|
||||
|
||||
function getCommand(fileName: string, packageName: string): InstallPackageAction {
|
||||
return { type: "install package", file: fileName, packageName };
|
||||
}
|
||||
|
||||
function getTypesPackageNameToInstall(host: LanguageServiceHost, moduleName: string): string | undefined {
|
||||
const { packageName } = getPackageName(moduleName);
|
||||
// If !registry, registry not available yet, can't do anything.
|
||||
return host.isKnownTypesPackageName(packageName) ? getTypesPackageName(packageName) : undefined;
|
||||
}
|
||||
|
||||
if (!host.isKnownTypesPackageName(packageName)) {
|
||||
// If !registry, registry not available yet, can't do anything.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const typesPackageName = getTypesPackageName(packageName);
|
||||
return {
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Install_0), [typesPackageName]),
|
||||
export function tryGetCodeActionForInstallPackageTypes(host: LanguageServiceHost, fileName: string, moduleName: string): CodeAction | undefined {
|
||||
const packageName = getTypesPackageNameToInstall(host, moduleName);
|
||||
return packageName === undefined ? undefined : {
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Install_0), [packageName]),
|
||||
changes: [],
|
||||
commands: [{ type: "install package", file: fileName, packageName: typesPackageName }],
|
||||
commands: [getCommand(fileName, packageName)],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,48 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const errorCodes = [
|
||||
Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2.code,
|
||||
Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1.code,
|
||||
];
|
||||
const fixId = "fixClassDoesntImplementInheritedAbstractMember";
|
||||
registerCodeFix({
|
||||
errorCodes: [Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2.code],
|
||||
getCodeActions: getActionForClassLikeMissingAbstractMember
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const { program, sourceFile, span } = context;
|
||||
const changes = textChanges.ChangeTracker.with(context, t =>
|
||||
addMissingMembers(getClass(sourceFile, span.start), sourceFile, program.getTypeChecker(), context.newLineCharacter, t));
|
||||
return changes.length === 0 ? undefined : [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes, fixId }];
|
||||
},
|
||||
fixIds: [fixId],
|
||||
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
|
||||
addMissingMembers(getClass(diag.file!, diag.start!), context.sourceFile, context.program.getTypeChecker(), context.newLineCharacter, changes);
|
||||
}),
|
||||
});
|
||||
|
||||
registerCodeFix({
|
||||
errorCodes: [Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1.code],
|
||||
getCodeActions: getActionForClassLikeMissingAbstractMember
|
||||
});
|
||||
|
||||
function getActionForClassLikeMissingAbstractMember(context: CodeFixContext): CodeAction[] | undefined {
|
||||
const sourceFile = context.sourceFile;
|
||||
const start = context.span.start;
|
||||
function getClass(sourceFile: SourceFile, pos: number): ClassLikeDeclaration {
|
||||
// This is the identifier in the case of a class declaration
|
||||
// or the class keyword token in the case of a class expression.
|
||||
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
|
||||
const checker = context.program.getTypeChecker();
|
||||
const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false);
|
||||
const classDeclaration = token.parent;
|
||||
Debug.assert(isClassLike(classDeclaration));
|
||||
return classDeclaration as ClassLikeDeclaration;
|
||||
}
|
||||
|
||||
if (isClassLike(token.parent)) {
|
||||
const classDeclaration = token.parent as ClassLikeDeclaration;
|
||||
function addMissingMembers(classDeclaration: ClassLikeDeclaration, sourceFile: SourceFile, checker: TypeChecker, newLineCharacter: string, changeTracker: textChanges.ChangeTracker): void {
|
||||
const extendsNode = getClassExtendsHeritageClauseElement(classDeclaration);
|
||||
const instantiatedExtendsType = checker.getTypeAtLocation(extendsNode);
|
||||
|
||||
const extendsNode = getClassExtendsHeritageClauseElement(classDeclaration);
|
||||
const instantiatedExtendsType = checker.getTypeAtLocation(extendsNode);
|
||||
|
||||
// Note that this is ultimately derived from a map indexed by symbol names,
|
||||
// so duplicates cannot occur.
|
||||
const extendsSymbols = checker.getPropertiesOfType(instantiatedExtendsType);
|
||||
const abstractAndNonPrivateExtendsSymbols = extendsSymbols.filter(symbolPointsToNonPrivateAndAbstractMember);
|
||||
|
||||
const newNodes = createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, checker);
|
||||
const changes = newNodesToChanges(newNodes, getOpenBraceOfClassLike(classDeclaration, sourceFile), context);
|
||||
if (changes && changes.length > 0) {
|
||||
return [{
|
||||
description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class),
|
||||
changes
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
// Note that this is ultimately derived from a map indexed by symbol names,
|
||||
// so duplicates cannot occur.
|
||||
const abstractAndNonPrivateExtendsSymbols = checker.getPropertiesOfType(instantiatedExtendsType).filter(symbolPointsToNonPrivateAndAbstractMember);
|
||||
|
||||
createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, checker, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member, newLineCharacter));
|
||||
}
|
||||
|
||||
function symbolPointsToNonPrivateAndAbstractMember(symbol: Symbol): boolean {
|
||||
const decls = symbol.getDeclarations();
|
||||
Debug.assert(!!(decls && decls.length > 0));
|
||||
const flags = getModifierFlags(decls[0]);
|
||||
// See `codeFixClassExtendAbstractProtectedProperty.ts` in https://github.com/Microsoft/TypeScript/pull/11547/files
|
||||
// (now named `codeFixClassExtendAbstractPrivateProperty.ts`)
|
||||
const flags = getModifierFlags(first(symbol.getDeclarations()));
|
||||
return !(flags & ModifierFlags.Private) && !!(flags & ModifierFlags.Abstract);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +1,70 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const errorCodes = [Diagnostics.Class_0_incorrectly_implements_interface_1.code];
|
||||
const fixId = "fixClassIncorrectlyImplementsInterface"; // TODO: share a group with fixClassDoesntImplementInheritedAbstractMember?
|
||||
registerCodeFix({
|
||||
errorCodes: [Diagnostics.Class_0_incorrectly_implements_interface_1.code],
|
||||
getCodeActions: getActionForClassLikeIncorrectImplementsInterface
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const { newLineCharacter, program, sourceFile, span } = context;
|
||||
const classDeclaration = getClass(sourceFile, span.start);
|
||||
const checker = program.getTypeChecker();
|
||||
return mapDefined<ExpressionWithTypeArguments, CodeFixAction>(getClassImplementsHeritageClauseElements(classDeclaration), implementedTypeNode => {
|
||||
const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, newLineCharacter, t));
|
||||
if (changes.length === 0) return undefined;
|
||||
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]);
|
||||
return { description, changes, fixId };
|
||||
});
|
||||
},
|
||||
fixIds: [fixId],
|
||||
getAllCodeActions(context) {
|
||||
const seenClassDeclarations = createMap<true>();
|
||||
return codeFixAll(context, errorCodes, (changes, diag) => {
|
||||
const classDeclaration = getClass(diag.file!, diag.start!);
|
||||
if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) {
|
||||
for (const implementedTypeNode of getClassImplementsHeritageClauseElements(classDeclaration)) {
|
||||
addMissingDeclarations(context.program.getTypeChecker(), implementedTypeNode, diag.file!, classDeclaration, context.newLineCharacter, changes);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function getActionForClassLikeIncorrectImplementsInterface(context: CodeFixContext): CodeAction[] | undefined {
|
||||
const sourceFile = context.sourceFile;
|
||||
const start = context.span.start;
|
||||
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
|
||||
const checker = context.program.getTypeChecker();
|
||||
function getClass(sourceFile: SourceFile, pos: number): ClassLikeDeclaration {
|
||||
const classDeclaration = getContainingClass(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false));
|
||||
Debug.assert(!!classDeclaration);
|
||||
return classDeclaration!;
|
||||
}
|
||||
|
||||
const classDeclaration = getContainingClass(token);
|
||||
if (!classDeclaration) {
|
||||
return undefined;
|
||||
function addMissingDeclarations(
|
||||
checker: TypeChecker,
|
||||
implementedTypeNode: ExpressionWithTypeArguments,
|
||||
sourceFile: SourceFile,
|
||||
classDeclaration: ClassLikeDeclaration,
|
||||
newLineCharacter: string,
|
||||
changeTracker: textChanges.ChangeTracker
|
||||
): void {
|
||||
// Note that this is ultimately derived from a map indexed by symbol names,
|
||||
// so duplicates cannot occur.
|
||||
const implementedType = checker.getTypeAtLocation(implementedTypeNode) as InterfaceType;
|
||||
const implementedTypeSymbols = checker.getPropertiesOfType(implementedType);
|
||||
const nonPrivateMembers = implementedTypeSymbols.filter(symbol => !(getModifierFlags(symbol.valueDeclaration) & ModifierFlags.Private));
|
||||
|
||||
const classType = checker.getTypeAtLocation(classDeclaration);
|
||||
|
||||
if (!checker.getIndexTypeOfType(classType, IndexKind.Number)) {
|
||||
createMissingIndexSignatureDeclaration(implementedType, IndexKind.Number);
|
||||
}
|
||||
if (!checker.getIndexTypeOfType(classType, IndexKind.String)) {
|
||||
createMissingIndexSignatureDeclaration(implementedType, IndexKind.String);
|
||||
}
|
||||
|
||||
const openBrace = getOpenBraceOfClassLike(classDeclaration, sourceFile);
|
||||
const classType = checker.getTypeAtLocation(classDeclaration) as InterfaceType;
|
||||
const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDeclaration);
|
||||
|
||||
const hasNumericIndexSignature = !!checker.getIndexTypeOfType(classType, IndexKind.Number);
|
||||
const hasStringIndexSignature = !!checker.getIndexTypeOfType(classType, IndexKind.String);
|
||||
|
||||
const result: CodeAction[] = [];
|
||||
for (const implementedTypeNode of implementedTypeNodes) {
|
||||
// Note that this is ultimately derived from a map indexed by symbol names,
|
||||
// so duplicates cannot occur.
|
||||
const implementedType = checker.getTypeAtLocation(implementedTypeNode) as InterfaceType;
|
||||
const implementedTypeSymbols = checker.getPropertiesOfType(implementedType);
|
||||
const nonPrivateMembers = implementedTypeSymbols.filter(symbol => !(getModifierFlags(symbol.valueDeclaration) & ModifierFlags.Private));
|
||||
|
||||
let newNodes: Node[] = [];
|
||||
createAndAddMissingIndexSignatureDeclaration(implementedType, IndexKind.Number, hasNumericIndexSignature, newNodes);
|
||||
createAndAddMissingIndexSignatureDeclaration(implementedType, IndexKind.String, hasStringIndexSignature, newNodes);
|
||||
newNodes = newNodes.concat(createMissingMemberNodes(classDeclaration, nonPrivateMembers, checker));
|
||||
const message = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]);
|
||||
if (newNodes.length > 0) {
|
||||
pushAction(result, newNodes, message);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
function createAndAddMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind, hasIndexSigOfKind: boolean, newNodes: Node[]): void {
|
||||
if (hasIndexSigOfKind) {
|
||||
return;
|
||||
}
|
||||
createMissingMemberNodes(classDeclaration, nonPrivateMembers, checker, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member, newLineCharacter));
|
||||
|
||||
function createMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind): void {
|
||||
const indexInfoOfKind = checker.getIndexInfoOfType(type, kind);
|
||||
|
||||
if (!indexInfoOfKind) {
|
||||
return;
|
||||
if (indexInfoOfKind) {
|
||||
changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, checker.indexInfoToIndexSignatureDeclaration(indexInfoOfKind, kind, classDeclaration), newLineCharacter);
|
||||
}
|
||||
const newIndexSignatureDeclaration = checker.indexInfoToIndexSignatureDeclaration(indexInfoOfKind, kind, classDeclaration);
|
||||
newNodes.push(newIndexSignatureDeclaration);
|
||||
}
|
||||
|
||||
function pushAction(result: CodeAction[], newNodes: Node[], description: string): void {
|
||||
result.push({ description, changes: newNodesToChanges(newNodes, openBrace, context) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,52 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const fixId = "classSuperMustPrecedeThisAccess";
|
||||
const errorCodes = [Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code];
|
||||
registerCodeFix({
|
||||
errorCodes: [Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code],
|
||||
getCodeActions: (context: CodeFixContext) => {
|
||||
const sourceFile = context.sourceFile;
|
||||
|
||||
const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false);
|
||||
if (token.kind !== SyntaxKind.ThisKeyword) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const constructor = getContainingFunction(token);
|
||||
const superCall = findSuperCall((<ConstructorDeclaration>constructor).body);
|
||||
if (!superCall) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// figure out if the `this` access is actually inside the supercall
|
||||
// i.e. super(this.a), since in that case we won't suggest a fix
|
||||
if (superCall.expression && superCall.expression.kind === SyntaxKind.CallExpression) {
|
||||
const expressionArguments = (<CallExpression>superCall.expression).arguments;
|
||||
for (const arg of expressionArguments) {
|
||||
if ((<PropertyAccessExpression>arg).expression === token) {
|
||||
return undefined;
|
||||
}
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const { sourceFile } = context;
|
||||
const nodes = getNodes(sourceFile, context.span.start);
|
||||
if (!nodes) return undefined;
|
||||
const { constructor, superCall } = nodes;
|
||||
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, constructor, superCall, context.newLineCharacter));
|
||||
return [{ description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), changes, fixId }];
|
||||
},
|
||||
fixIds: [fixId],
|
||||
getAllCodeActions(context) {
|
||||
const { newLineCharacter, sourceFile } = context;
|
||||
const seenClasses = createMap<true>(); // Ensure we only do this once per class.
|
||||
return codeFixAll(context, errorCodes, (changes, diag) => {
|
||||
const nodes = getNodes(diag.file!, diag.start!);
|
||||
if (!nodes) return;
|
||||
const { constructor, superCall } = nodes;
|
||||
if (addToSeen(seenClasses, getNodeId(constructor.parent))) {
|
||||
doChange(changes, sourceFile, constructor, superCall, newLineCharacter);
|
||||
}
|
||||
}
|
||||
const changeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
changeTracker.insertNodeAfter(sourceFile, getOpenBrace(<ConstructorDeclaration>constructor, sourceFile), superCall, { suffix: context.newLineCharacter });
|
||||
changeTracker.deleteNode(sourceFile, superCall);
|
||||
|
||||
return [{
|
||||
description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor),
|
||||
changes: changeTracker.getChanges()
|
||||
}];
|
||||
|
||||
function findSuperCall(n: Node): ExpressionStatement {
|
||||
if (n.kind === SyntaxKind.ExpressionStatement && isSuperCall((<ExpressionStatement>n).expression)) {
|
||||
return <ExpressionStatement>n;
|
||||
}
|
||||
if (isFunctionLike(n)) {
|
||||
return undefined;
|
||||
}
|
||||
return forEachChild(n, findSuperCall);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, constructor: ConstructorDeclaration, superCall: ExpressionStatement, newLineCharacter: string): void {
|
||||
changes.insertNodeAtConstructorStart(sourceFile, constructor, superCall, newLineCharacter);
|
||||
changes.deleteNode(sourceFile, superCall);
|
||||
}
|
||||
|
||||
function getNodes(sourceFile: SourceFile, pos: number): { readonly constructor: ConstructorDeclaration, readonly superCall: ExpressionStatement } {
|
||||
const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false);
|
||||
Debug.assert(token.kind === SyntaxKind.ThisKeyword);
|
||||
const constructor = getContainingFunction(token) as ConstructorDeclaration;
|
||||
const superCall = findSuperCall(constructor.body);
|
||||
// figure out if the `this` access is actually inside the supercall
|
||||
// i.e. super(this.a), since in that case we won't suggest a fix
|
||||
return superCall && !superCall.expression.arguments.some(arg => isPropertyAccessExpression(arg) && arg.expression === token) ? { constructor, superCall } : undefined;
|
||||
}
|
||||
|
||||
function findSuperCall(n: Node): ExpressionStatement & { expression: CallExpression } | undefined {
|
||||
return isExpressionStatement(n) && isSuperCall(n.expression)
|
||||
? n as ExpressionStatement & { expression: CallExpression }
|
||||
: isFunctionLike(n)
|
||||
? undefined
|
||||
: forEachChild(n, findSuperCall);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,28 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const fixId = "constructorForDerivedNeedSuperCall";
|
||||
const errorCodes = [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code];
|
||||
registerCodeFix({
|
||||
errorCodes: [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code],
|
||||
getCodeActions: (context: CodeFixContext) => {
|
||||
const sourceFile = context.sourceFile;
|
||||
const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false);
|
||||
|
||||
if (token.kind !== SyntaxKind.ConstructorKeyword) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const changeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
const superCall = createStatement(createCall(createSuper(), /*typeArguments*/ undefined, /*argumentsArray*/ emptyArray));
|
||||
changeTracker.insertNodeAtConstructorStart(sourceFile, <ConstructorDeclaration>token.parent, superCall, context.newLineCharacter);
|
||||
|
||||
return [{
|
||||
description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call),
|
||||
changes: changeTracker.getChanges()
|
||||
}];
|
||||
}
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const { sourceFile } = context;
|
||||
const ctr = getNode(sourceFile, context.span.start);
|
||||
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, ctr, context.newLineCharacter));
|
||||
return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), changes, fixId }];
|
||||
},
|
||||
fixIds: [fixId],
|
||||
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) =>
|
||||
doChange(changes, context.sourceFile, getNode(diag.file, diag.start!), context.newLineCharacter)),
|
||||
});
|
||||
}
|
||||
|
||||
function getNode(sourceFile: SourceFile, pos: number): ConstructorDeclaration {
|
||||
const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false);
|
||||
Debug.assert(token.kind === SyntaxKind.ConstructorKeyword);
|
||||
return token.parent as ConstructorDeclaration;
|
||||
}
|
||||
|
||||
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, ctr: ConstructorDeclaration, newLineCharacter: string) {
|
||||
const superCall = createStatement(createCall(createSuper(), /*typeArguments*/ undefined, /*argumentsArray*/ emptyArray));
|
||||
changes.insertNodeAtConstructorStart(sourceFile, ctr, superCall, newLineCharacter);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,37 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const fixId = "extendsInterfaceBecomesImplements";
|
||||
const errorCodes = [Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements.code];
|
||||
registerCodeFix({
|
||||
errorCodes: [Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements.code],
|
||||
getCodeActions: (context: CodeFixContext) => {
|
||||
const sourceFile = context.sourceFile;
|
||||
const start = context.span.start;
|
||||
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
|
||||
const classDeclNode = getContainingClass(token);
|
||||
if (!(token.kind === SyntaxKind.Identifier && isClassLike(classDeclNode))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const heritageClauses = classDeclNode.heritageClauses;
|
||||
if (!(heritageClauses && heritageClauses.length > 0)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const extendsToken = heritageClauses[0].getFirstToken();
|
||||
if (!(extendsToken && extendsToken.kind === SyntaxKind.ExtendsKeyword)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const changeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
changeTracker.replaceNode(sourceFile, extendsToken, createToken(SyntaxKind.ImplementsKeyword));
|
||||
|
||||
// We replace existing keywords with commas.
|
||||
for (let i = 1; i < heritageClauses.length; i++) {
|
||||
const keywordToken = heritageClauses[i].getFirstToken();
|
||||
if (keywordToken) {
|
||||
changeTracker.replaceNode(sourceFile, keywordToken, createToken(SyntaxKind.CommaToken));
|
||||
}
|
||||
}
|
||||
|
||||
const result = [{
|
||||
description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements),
|
||||
changes: changeTracker.getChanges()
|
||||
}];
|
||||
|
||||
return result;
|
||||
}
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const { sourceFile } = context;
|
||||
const nodes = getNodes(sourceFile, context.span.start);
|
||||
if (!nodes) return undefined;
|
||||
const { extendsToken, heritageClauses } = nodes;
|
||||
const changes = textChanges.ChangeTracker.with(context, t => doChanges(t, sourceFile, extendsToken, heritageClauses));
|
||||
return [{ description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements), changes, fixId }];
|
||||
},
|
||||
fixIds: [fixId],
|
||||
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
|
||||
const nodes = getNodes(diag.file, diag.start!);
|
||||
if (nodes) doChanges(changes, diag.file, nodes.extendsToken, nodes.heritageClauses);
|
||||
}),
|
||||
});
|
||||
|
||||
function getNodes(sourceFile: SourceFile, pos: number) {
|
||||
const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false);
|
||||
const heritageClauses = getContainingClass(token)!.heritageClauses;
|
||||
const extendsToken = heritageClauses[0].getFirstToken();
|
||||
return extendsToken.kind === SyntaxKind.ExtendsKeyword ? { extendsToken, heritageClauses } : undefined;
|
||||
}
|
||||
|
||||
function doChanges(changes: textChanges.ChangeTracker, sourceFile: SourceFile, extendsToken: Node, heritageClauses: ReadonlyArray<HeritageClause>): void {
|
||||
changes.replaceNode(sourceFile, extendsToken, createToken(SyntaxKind.ImplementsKeyword));
|
||||
// We replace existing keywords with commas.
|
||||
for (let i = 1; i < heritageClauses.length; i++) {
|
||||
const keywordToken = heritageClauses[i].getFirstToken()!;
|
||||
changes.replaceNode(sourceFile, keywordToken, createToken(SyntaxKind.CommaToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,26 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const fixId = "forgottenThisPropertyAccess";
|
||||
const errorCodes = [Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code];
|
||||
registerCodeFix({
|
||||
errorCodes: [Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code],
|
||||
getCodeActions: (context: CodeFixContext) => {
|
||||
const sourceFile = context.sourceFile;
|
||||
const token = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false);
|
||||
if (token.kind !== SyntaxKind.Identifier) {
|
||||
return undefined;
|
||||
}
|
||||
const changeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
changeTracker.replaceNode(sourceFile, token, createPropertyAccess(createThis(), <Identifier>token));
|
||||
|
||||
return [{
|
||||
description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable),
|
||||
changes: changeTracker.getChanges()
|
||||
}];
|
||||
}
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const { sourceFile } = context;
|
||||
const token = getNode(sourceFile, context.span.start);
|
||||
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, token));
|
||||
return [{ description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable), changes, fixId }];
|
||||
},
|
||||
fixIds: [fixId],
|
||||
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
|
||||
doChange(changes, context.sourceFile, getNode(diag.file, diag.start!));
|
||||
}),
|
||||
});
|
||||
|
||||
function getNode(sourceFile: SourceFile, pos: number): Identifier {
|
||||
return cast(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false), isIdentifier);
|
||||
}
|
||||
|
||||
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Identifier): void {
|
||||
changes.replaceNode(sourceFile, token, createPropertyAccess(createThis(), token));
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,91 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const fixIdPlain = "fixJSDocTypes_plain";
|
||||
const fixIdNullable = "fixJSDocTypes_nullable";
|
||||
const errorCodes = [Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments.code];
|
||||
registerCodeFix({
|
||||
errorCodes: [Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments.code],
|
||||
getCodeActions: getActionsForJSDocTypes
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const { sourceFile } = context;
|
||||
const checker = context.program.getTypeChecker();
|
||||
const info = getInfo(sourceFile, context.span.start, checker);
|
||||
if (!info) return undefined;
|
||||
const { typeNode, type } = info;
|
||||
const original = typeNode.getText(sourceFile);
|
||||
const actions = [fix(type, fixIdPlain)];
|
||||
if (typeNode.kind === SyntaxKind.JSDocNullableType) {
|
||||
// for nullable types, suggest the flow-compatible `T | null | undefined`
|
||||
// in addition to the jsdoc/closure-compatible `T | null`
|
||||
actions.push(fix(checker.getNullableType(type, TypeFlags.Undefined), fixIdNullable));
|
||||
}
|
||||
return actions;
|
||||
|
||||
function fix(type: Type, fixId: string): CodeFixAction {
|
||||
const newText = typeString(type, checker);
|
||||
return {
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_0_to_1), [original, newText]),
|
||||
changes: [createFileTextChanges(sourceFile.fileName, [createChange(typeNode, sourceFile, newText)])],
|
||||
fixId,
|
||||
};
|
||||
}
|
||||
},
|
||||
fixIds: [fixIdPlain, fixIdNullable],
|
||||
getAllCodeActions(context) {
|
||||
const { fixId, program, sourceFile } = context;
|
||||
const checker = program.getTypeChecker();
|
||||
return codeFixAllWithTextChanges(context, errorCodes, (changes, err) => {
|
||||
const info = getInfo(err.file, err.start!, checker);
|
||||
if (!info) return;
|
||||
const { typeNode, type } = info;
|
||||
const fixedType = typeNode.kind === SyntaxKind.JSDocNullableType && fixId === fixIdNullable ? checker.getNullableType(type, TypeFlags.Undefined) : type;
|
||||
changes.push(createChange(typeNode, sourceFile, typeString(fixedType, checker)));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function getActionsForJSDocTypes(context: CodeFixContext): CodeAction[] | undefined {
|
||||
const sourceFile = context.sourceFile;
|
||||
const node = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false);
|
||||
function getInfo(sourceFile: SourceFile, pos: number, checker: TypeChecker): { readonly typeNode: TypeNode, type: Type } {
|
||||
const decl = findAncestor(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false), isTypeContainer);
|
||||
const typeNode = decl && decl.type;
|
||||
return typeNode && { typeNode, type: checker.getTypeFromTypeNode(typeNode) };
|
||||
}
|
||||
|
||||
function createChange(declaration: TypeNode, sourceFile: SourceFile, newText: string): TextChange {
|
||||
return { span: createTextSpanFromBounds(declaration.getStart(sourceFile), declaration.getEnd()), newText };
|
||||
}
|
||||
|
||||
function typeString(type: Type, checker: TypeChecker): string {
|
||||
return checker.typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTruncation);
|
||||
}
|
||||
|
||||
// TODO: GH#19856 Node & { type: TypeNode }
|
||||
type TypeContainer =
|
||||
| AsExpression | CallSignatureDeclaration | ConstructSignatureDeclaration | FunctionDeclaration
|
||||
| GetAccessorDeclaration | IndexSignatureDeclaration | MappedTypeNode | MethodDeclaration
|
||||
| MethodSignature | ParameterDeclaration | PropertyDeclaration | PropertySignature | SetAccessorDeclaration
|
||||
| TypeAliasDeclaration | TypeAssertion | VariableDeclaration;
|
||||
function isTypeContainer(node: Node): node is TypeContainer {
|
||||
// NOTE: Some locations are not handled yet:
|
||||
// MappedTypeNode.typeParameters and SignatureDeclaration.typeParameters, as well as CallExpression.typeArguments
|
||||
const decl = ts.findAncestor(node,
|
||||
n =>
|
||||
n.kind === SyntaxKind.AsExpression ||
|
||||
n.kind === SyntaxKind.CallSignature ||
|
||||
n.kind === SyntaxKind.ConstructSignature ||
|
||||
n.kind === SyntaxKind.FunctionDeclaration ||
|
||||
n.kind === SyntaxKind.GetAccessor ||
|
||||
n.kind === SyntaxKind.IndexSignature ||
|
||||
n.kind === SyntaxKind.MappedType ||
|
||||
n.kind === SyntaxKind.MethodDeclaration ||
|
||||
n.kind === SyntaxKind.MethodSignature ||
|
||||
n.kind === SyntaxKind.Parameter ||
|
||||
n.kind === SyntaxKind.PropertyDeclaration ||
|
||||
n.kind === SyntaxKind.PropertySignature ||
|
||||
n.kind === SyntaxKind.SetAccessor ||
|
||||
n.kind === SyntaxKind.TypeAliasDeclaration ||
|
||||
n.kind === SyntaxKind.TypeAssertionExpression ||
|
||||
n.kind === SyntaxKind.VariableDeclaration);
|
||||
if (!decl) return;
|
||||
const checker = context.program.getTypeChecker();
|
||||
|
||||
const jsdocType = (decl as VariableDeclaration).type;
|
||||
if (!jsdocType) return;
|
||||
const original = getTextOfNode(jsdocType);
|
||||
const type = checker.getTypeFromTypeNode(jsdocType);
|
||||
const actions = [createAction(jsdocType, sourceFile.fileName, original, checker.typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTruncation))];
|
||||
if (jsdocType.kind === SyntaxKind.JSDocNullableType) {
|
||||
// for nullable types, suggest the flow-compatible `T | null | undefined`
|
||||
// in addition to the jsdoc/closure-compatible `T | null`
|
||||
const replacementWithUndefined = checker.typeToString(checker.getNullableType(type, TypeFlags.Undefined), /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTruncation);
|
||||
actions.push(createAction(jsdocType, sourceFile.fileName, original, replacementWithUndefined));
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.AsExpression:
|
||||
case SyntaxKind.CallSignature:
|
||||
case SyntaxKind.ConstructSignature:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.IndexSignature:
|
||||
case SyntaxKind.MappedType:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.MethodSignature:
|
||||
case SyntaxKind.Parameter:
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
case SyntaxKind.PropertySignature:
|
||||
case SyntaxKind.SetAccessor:
|
||||
case SyntaxKind.TypeAliasDeclaration:
|
||||
case SyntaxKind.TypeAssertionExpression:
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
function createAction(declaration: TypeNode, fileName: string, original: string, replacement: string): CodeAction {
|
||||
return {
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_0_to_1), [original, replacement]),
|
||||
changes: [{
|
||||
fileName,
|
||||
textChanges: [{
|
||||
span: { start: declaration.getStart(), length: declaration.getWidth() },
|
||||
newText: replacement
|
||||
}]
|
||||
}],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,34 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const fixId = "fixSpelling";
|
||||
const errorCodes = [
|
||||
Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code,
|
||||
Diagnostics.Cannot_find_name_0_Did_you_mean_1.code,
|
||||
];
|
||||
registerCodeFix({
|
||||
errorCodes: [Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code,
|
||||
Diagnostics.Cannot_find_name_0_Did_you_mean_1.code],
|
||||
getCodeActions: getActionsForCorrectSpelling
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const { sourceFile } = context;
|
||||
const info = getInfo(sourceFile, context.span.start, context.program.getTypeChecker());
|
||||
if (!info) return undefined;
|
||||
const { node, suggestion } = info;
|
||||
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node, suggestion));
|
||||
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_spelling_to_0), [suggestion]);
|
||||
return [{ description, changes, fixId }];
|
||||
},
|
||||
fixIds: [fixId],
|
||||
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
|
||||
const info = getInfo(diag.file!, diag.start!, context.program.getTypeChecker());
|
||||
if (info) doChange(changes, context.sourceFile, info.node, info.suggestion);
|
||||
}),
|
||||
});
|
||||
|
||||
function getActionsForCorrectSpelling(context: CodeFixContext): CodeAction[] | undefined {
|
||||
const sourceFile = context.sourceFile;
|
||||
|
||||
function getInfo(sourceFile: SourceFile, pos: number, checker: TypeChecker): { node: Node, suggestion: string } | undefined {
|
||||
// This is the identifier of the misspelled word. eg:
|
||||
// this.speling = 1;
|
||||
// ^^^^^^^
|
||||
const node = getTokenAtPosition(sourceFile, context.span.start, /*includeJsDocComment*/ false); // TODO: GH#15852
|
||||
const checker = context.program.getTypeChecker();
|
||||
const node = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); // TODO: GH#15852
|
||||
|
||||
let suggestion: string;
|
||||
if (isPropertyAccessExpression(node.parent) && node.parent.name === node) {
|
||||
Debug.assert(node.kind === SyntaxKind.Identifier);
|
||||
@@ -26,18 +41,12 @@ namespace ts.codefix {
|
||||
Debug.assert(name !== undefined, "name should be defined");
|
||||
suggestion = checker.getSuggestionForNonexistentSymbol(node, name, convertSemanticMeaningToSymbolFlags(meaning));
|
||||
}
|
||||
if (suggestion) {
|
||||
return [{
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_spelling_to_0), [suggestion]),
|
||||
changes: [{
|
||||
fileName: sourceFile.fileName,
|
||||
textChanges: [{
|
||||
span: { start: node.getStart(), length: node.getWidth() },
|
||||
newText: suggestion
|
||||
}],
|
||||
}],
|
||||
}];
|
||||
}
|
||||
|
||||
return suggestion === undefined ? undefined : { node, suggestion };
|
||||
}
|
||||
|
||||
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, node: Node, suggestion: string) {
|
||||
changes.replaceNode(sourceFile, node, createIdentifier(suggestion));
|
||||
}
|
||||
|
||||
function convertSemanticMeaningToSymbolFlags(meaning: SemanticMeaning): SymbolFlags {
|
||||
|
||||
@@ -1,207 +1,235 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const fixIdPrefix = "unusedIdentifier_prefix";
|
||||
const fixIdDelete = "unusedIdentifier_delete";
|
||||
const errorCodes = [
|
||||
Diagnostics._0_is_declared_but_its_value_is_never_read.code,
|
||||
Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code,
|
||||
];
|
||||
registerCodeFix({
|
||||
errorCodes: [
|
||||
Diagnostics._0_is_declared_but_its_value_is_never_read.code,
|
||||
Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code
|
||||
],
|
||||
getCodeActions: (context: CodeFixContext) => {
|
||||
const sourceFile = context.sourceFile;
|
||||
const start = context.span.start;
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const { sourceFile } = context;
|
||||
const token = getToken(sourceFile, context.span.start);
|
||||
const result: CodeFixAction[] = [];
|
||||
|
||||
let token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
|
||||
|
||||
// this handles var ["computed"] = 12;
|
||||
if (token.kind === SyntaxKind.OpenBracketToken) {
|
||||
token = getTokenAtPosition(sourceFile, start + 1, /*includeJsDocComment*/ false);
|
||||
const deletion = textChanges.ChangeTracker.with(context, t => tryDeleteDeclaration(t, sourceFile, token));
|
||||
if (deletion.length) {
|
||||
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), [token.getText()]);
|
||||
result.push({ description, changes: deletion, fixId: fixIdDelete });
|
||||
}
|
||||
|
||||
switch (token.kind) {
|
||||
case ts.SyntaxKind.Identifier:
|
||||
return deleteIdentifierOrPrefixWithUnderscore(<Identifier>token, context.errorCode);
|
||||
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
case SyntaxKind.NamespaceImport:
|
||||
return [deleteNode(token.parent)];
|
||||
|
||||
default:
|
||||
return deleteDefault();
|
||||
const prefix = textChanges.ChangeTracker.with(context, t => tryPrefixDeclaration(t, context.errorCode, sourceFile, token));
|
||||
if (prefix.length) {
|
||||
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Prefix_0_with_an_underscore), [token.getText()]);
|
||||
result.push({ description, changes: prefix, fixId: fixIdPrefix });
|
||||
}
|
||||
|
||||
function deleteDefault(): CodeAction[] | undefined {
|
||||
if (isDeclarationName(token)) {
|
||||
return [deleteNode(token.parent)];
|
||||
}
|
||||
else if (isLiteralComputedPropertyDeclarationName(token)) {
|
||||
return [deleteNode(token.parent.parent)];
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function prefixIdentifierWithUnderscore(identifier: Identifier): CodeAction {
|
||||
const startPosition = identifier.getStart(sourceFile, /*includeJsDocComment*/ false);
|
||||
return {
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Prefix_0_with_an_underscore), { 0: token.getText() }),
|
||||
changes: [{
|
||||
fileName: sourceFile.path,
|
||||
textChanges: [{
|
||||
span: { start: startPosition, length: 0 },
|
||||
newText: "_"
|
||||
}]
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
function deleteIdentifierOrPrefixWithUnderscore(identifier: Identifier, errorCode: number): CodeAction[] | undefined {
|
||||
const parent = identifier.parent;
|
||||
switch (parent.kind) {
|
||||
case ts.SyntaxKind.VariableDeclaration:
|
||||
return deleteVariableDeclarationOrPrefixWithUnderscore(identifier, <ts.VariableDeclaration>parent);
|
||||
|
||||
case SyntaxKind.TypeParameter:
|
||||
const typeParameters = (<DeclarationWithTypeParameters>parent.parent).typeParameters;
|
||||
if (typeParameters.length === 1) {
|
||||
const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1, /*includeJsDocComment*/ false);
|
||||
const nextToken = getTokenAtPosition(sourceFile, typeParameters.end, /*includeJsDocComment*/ false);
|
||||
Debug.assert(previousToken.kind === SyntaxKind.LessThanToken);
|
||||
Debug.assert(nextToken.kind === SyntaxKind.GreaterThanToken);
|
||||
|
||||
return [deleteNodeRange(previousToken, nextToken)];
|
||||
}
|
||||
else {
|
||||
return [deleteNodeInList(parent)];
|
||||
}
|
||||
|
||||
case ts.SyntaxKind.Parameter:
|
||||
const functionDeclaration = <FunctionDeclaration>parent.parent;
|
||||
const deleteAction = functionDeclaration.parameters.length === 1 ? deleteNode(parent) : deleteNodeInList(parent);
|
||||
return errorCode === Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code
|
||||
? [deleteAction]
|
||||
: [deleteAction, prefixIdentifierWithUnderscore(identifier)];
|
||||
|
||||
// handle case where 'import a = A;'
|
||||
case SyntaxKind.ImportEqualsDeclaration:
|
||||
const importEquals = getAncestor(identifier, SyntaxKind.ImportEqualsDeclaration);
|
||||
return [deleteNode(importEquals)];
|
||||
|
||||
case SyntaxKind.ImportSpecifier:
|
||||
const namedImports = <NamedImports>parent.parent;
|
||||
if (namedImports.elements.length === 1) {
|
||||
return deleteNamedImportBinding(namedImports);
|
||||
}
|
||||
else {
|
||||
// delete import specifier
|
||||
return [deleteNodeInList(parent)];
|
||||
}
|
||||
|
||||
case SyntaxKind.ImportClause: // this covers both 'import |d|' and 'import |d,| *'
|
||||
const importClause = <ImportClause>parent;
|
||||
if (!importClause.namedBindings) { // |import d from './file'|
|
||||
const importDecl = getAncestor(importClause, SyntaxKind.ImportDeclaration);
|
||||
return [deleteNode(importDecl)];
|
||||
}
|
||||
else {
|
||||
// import |d,| * as ns from './file'
|
||||
const start = importClause.name.getStart(sourceFile);
|
||||
const nextToken = getTokenAtPosition(sourceFile, importClause.name.end, /*includeJsDocComment*/ false);
|
||||
if (nextToken && nextToken.kind === SyntaxKind.CommaToken) {
|
||||
// shift first non-whitespace position after comma to the start position of the node
|
||||
return [deleteRange({ pos: start, end: skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true) })];
|
||||
}
|
||||
else {
|
||||
return [deleteNode(importClause.name)];
|
||||
}
|
||||
}
|
||||
|
||||
case SyntaxKind.NamespaceImport:
|
||||
return deleteNamedImportBinding(<NamespaceImport>parent);
|
||||
|
||||
default:
|
||||
return deleteDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function deleteNamedImportBinding(namedBindings: NamedImportBindings): CodeAction[] | undefined {
|
||||
if ((<ImportClause>namedBindings.parent).name) {
|
||||
// Delete named imports while preserving the default import
|
||||
// import d|, * as ns| from './file'
|
||||
// import d|, { a }| from './file'
|
||||
const previousToken = getTokenAtPosition(sourceFile, namedBindings.pos - 1, /*includeJsDocComment*/ false);
|
||||
if (previousToken && previousToken.kind === SyntaxKind.CommaToken) {
|
||||
return [deleteRange({ pos: previousToken.getStart(), end: namedBindings.end })];
|
||||
return result;
|
||||
},
|
||||
fixIds: [fixIdPrefix, fixIdDelete],
|
||||
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
|
||||
const { sourceFile } = context;
|
||||
const token = getToken(diag.file!, diag.start!);
|
||||
switch (context.fixId) {
|
||||
case fixIdPrefix:
|
||||
if (isIdentifier(token) && canPrefix(token)) {
|
||||
tryPrefixDeclaration(changes, diag.code, sourceFile, token);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
else {
|
||||
// Delete the entire import declaration
|
||||
// |import * as ns from './file'|
|
||||
// |import { a } from './file'|
|
||||
const importDecl = getAncestor(namedBindings, SyntaxKind.ImportDeclaration);
|
||||
return [deleteNode(importDecl)];
|
||||
}
|
||||
break;
|
||||
case fixIdDelete:
|
||||
tryDeleteDeclaration(changes, sourceFile, token);
|
||||
break;
|
||||
default:
|
||||
Debug.fail(JSON.stringify(context.fixId));
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
// token.parent is a variableDeclaration
|
||||
function deleteVariableDeclarationOrPrefixWithUnderscore(identifier: Identifier, varDecl: ts.VariableDeclaration): CodeAction[] | undefined {
|
||||
function getToken(sourceFile: SourceFile, pos: number): Node {
|
||||
const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false);
|
||||
// this handles var ["computed"] = 12;
|
||||
return token.kind === SyntaxKind.OpenBracketToken ? getTokenAtPosition(sourceFile, pos + 1, /*includeJsDocComment*/ false) : token;
|
||||
}
|
||||
|
||||
function tryPrefixDeclaration(changes: textChanges.ChangeTracker, errorCode: number, sourceFile: SourceFile, token: Node): void {
|
||||
// Don't offer to prefix a property.
|
||||
if (errorCode !== Diagnostics.Property_0_is_declared_but_its_value_is_never_read.code && isIdentifier(token) && canPrefix(token)) {
|
||||
changes.replaceNode(sourceFile, token, createIdentifier(`_${token.text}`));
|
||||
}
|
||||
}
|
||||
|
||||
function canPrefix(token: Identifier): boolean {
|
||||
switch (token.parent.kind) {
|
||||
case SyntaxKind.Parameter:
|
||||
return true;
|
||||
case SyntaxKind.VariableDeclaration: {
|
||||
const varDecl = token.parent as VariableDeclaration;
|
||||
switch (varDecl.parent.parent.kind) {
|
||||
case SyntaxKind.ForStatement:
|
||||
const forStatement = <ForStatement>varDecl.parent.parent;
|
||||
const forInitializer = <VariableDeclarationList>forStatement.initializer;
|
||||
return [forInitializer.declarations.length === 1 ? deleteNode(forInitializer) : deleteNodeInList(varDecl)];
|
||||
|
||||
case SyntaxKind.ForOfStatement:
|
||||
const forOfStatement = <ForOfStatement>varDecl.parent.parent;
|
||||
Debug.assert(forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList);
|
||||
const forOfInitializer = <VariableDeclarationList>forOfStatement.initializer;
|
||||
return [
|
||||
replaceNode(forOfInitializer.declarations[0], createObjectLiteral()),
|
||||
prefixIdentifierWithUnderscore(identifier)
|
||||
];
|
||||
|
||||
case SyntaxKind.ForInStatement:
|
||||
// There is no valid fix in the case of:
|
||||
// for .. in
|
||||
return [prefixIdentifierWithUnderscore(identifier)];
|
||||
|
||||
default:
|
||||
const variableStatement = <VariableStatement>varDecl.parent.parent;
|
||||
if (variableStatement.declarationList.declarations.length === 1) {
|
||||
return [deleteNode(variableStatement)];
|
||||
}
|
||||
else {
|
||||
return [deleteNodeInList(varDecl)];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function deleteNode(n: Node) {
|
||||
return makeChange(textChanges.ChangeTracker.fromContext(context).deleteNode(sourceFile, n));
|
||||
}
|
||||
|
||||
function deleteRange(range: TextRange) {
|
||||
return makeChange(textChanges.ChangeTracker.fromContext(context).deleteRange(sourceFile, range));
|
||||
}
|
||||
|
||||
function deleteNodeInList(n: Node) {
|
||||
return makeChange(textChanges.ChangeTracker.fromContext(context).deleteNodeInList(sourceFile, n));
|
||||
}
|
||||
|
||||
function deleteNodeRange(start: Node, end: Node) {
|
||||
return makeChange(textChanges.ChangeTracker.fromContext(context).deleteNodeRange(sourceFile, start, end));
|
||||
}
|
||||
|
||||
function replaceNode(n: Node, newNode: Node) {
|
||||
return makeChange(textChanges.ChangeTracker.fromContext(context).replaceNode(sourceFile, n, newNode));
|
||||
}
|
||||
|
||||
function makeChange(changeTracker: textChanges.ChangeTracker): CodeAction {
|
||||
return {
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), { 0: token.getText() }),
|
||||
changes: changeTracker.getChanges()
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryDeleteDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node): void {
|
||||
switch (token.kind) {
|
||||
case SyntaxKind.Identifier:
|
||||
tryDeleteIdentifier(changes, sourceFile, <Identifier>token);
|
||||
break;
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
case SyntaxKind.NamespaceImport:
|
||||
changes.deleteNode(sourceFile, token.parent);
|
||||
break;
|
||||
default:
|
||||
tryDeleteDefault(changes, sourceFile, token);
|
||||
}
|
||||
}
|
||||
|
||||
function tryDeleteDefault(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node): void {
|
||||
if (isDeclarationName(token)) {
|
||||
changes.deleteNode(sourceFile, token.parent);
|
||||
}
|
||||
else if (isLiteralComputedPropertyDeclarationName(token)) {
|
||||
changes.deleteNode(sourceFile, token.parent.parent);
|
||||
}
|
||||
}
|
||||
|
||||
function tryDeleteIdentifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, identifier: Identifier): void {
|
||||
const parent = identifier.parent;
|
||||
switch (parent.kind) {
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
tryDeleteVariableDeclaration(changes, sourceFile, <VariableDeclaration>parent);
|
||||
break;
|
||||
|
||||
case SyntaxKind.TypeParameter:
|
||||
const typeParameters = (<DeclarationWithTypeParameters>parent.parent).typeParameters;
|
||||
if (typeParameters.length === 1) {
|
||||
const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1, /*includeJsDocComment*/ false);
|
||||
const nextToken = getTokenAtPosition(sourceFile, typeParameters.end, /*includeJsDocComment*/ false);
|
||||
Debug.assert(previousToken.kind === SyntaxKind.LessThanToken);
|
||||
Debug.assert(nextToken.kind === SyntaxKind.GreaterThanToken);
|
||||
|
||||
changes.deleteNodeRange(sourceFile, previousToken, nextToken);
|
||||
}
|
||||
else {
|
||||
changes.deleteNodeInList(sourceFile, parent);
|
||||
}
|
||||
break;
|
||||
|
||||
case SyntaxKind.Parameter:
|
||||
const functionDeclaration = <FunctionDeclaration>parent.parent;
|
||||
if (functionDeclaration.parameters.length === 1) {
|
||||
changes.deleteNode(sourceFile, parent);
|
||||
}
|
||||
else {
|
||||
changes.deleteNodeInList(sourceFile, parent);
|
||||
}
|
||||
break;
|
||||
|
||||
// handle case where 'import a = A;'
|
||||
case SyntaxKind.ImportEqualsDeclaration:
|
||||
const importEquals = getAncestor(identifier, SyntaxKind.ImportEqualsDeclaration);
|
||||
changes.deleteNode(sourceFile, importEquals);
|
||||
break;
|
||||
|
||||
case SyntaxKind.ImportSpecifier:
|
||||
const namedImports = <NamedImports>parent.parent;
|
||||
if (namedImports.elements.length === 1) {
|
||||
tryDeleteNamedImportBinding(changes, sourceFile, namedImports);
|
||||
}
|
||||
else {
|
||||
// delete import specifier
|
||||
changes.deleteNodeInList(sourceFile, parent);
|
||||
}
|
||||
break;
|
||||
|
||||
case SyntaxKind.ImportClause: // this covers both 'import |d|' and 'import |d,| *'
|
||||
const importClause = <ImportClause>parent;
|
||||
if (!importClause.namedBindings) { // |import d from './file'|
|
||||
changes.deleteNode(sourceFile, getAncestor(importClause, SyntaxKind.ImportDeclaration)!);
|
||||
}
|
||||
else {
|
||||
// import |d,| * as ns from './file'
|
||||
const start = importClause.name.getStart(sourceFile);
|
||||
const nextToken = getTokenAtPosition(sourceFile, importClause.name.end, /*includeJsDocComment*/ false);
|
||||
if (nextToken && nextToken.kind === SyntaxKind.CommaToken) {
|
||||
// shift first non-whitespace position after comma to the start position of the node
|
||||
const end = skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true);
|
||||
changes.deleteRange(sourceFile, { pos: start, end });
|
||||
}
|
||||
else {
|
||||
changes.deleteNode(sourceFile, importClause.name);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SyntaxKind.NamespaceImport:
|
||||
tryDeleteNamedImportBinding(changes, sourceFile, <NamespaceImport>parent);
|
||||
break;
|
||||
|
||||
default:
|
||||
tryDeleteDefault(changes, sourceFile, identifier);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function tryDeleteNamedImportBinding(changes: textChanges.ChangeTracker, sourceFile: SourceFile, namedBindings: NamedImportBindings): void {
|
||||
if ((<ImportClause>namedBindings.parent).name) {
|
||||
// Delete named imports while preserving the default import
|
||||
// import d|, * as ns| from './file'
|
||||
// import d|, { a }| from './file'
|
||||
const previousToken = getTokenAtPosition(sourceFile, namedBindings.pos - 1, /*includeJsDocComment*/ false);
|
||||
if (previousToken && previousToken.kind === SyntaxKind.CommaToken) {
|
||||
changes.deleteRange(sourceFile, { pos: previousToken.getStart(), end: namedBindings.end });
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Delete the entire import declaration
|
||||
// |import * as ns from './file'|
|
||||
// |import { a } from './file'|
|
||||
const importDecl = getAncestor(namedBindings, SyntaxKind.ImportDeclaration);
|
||||
changes.deleteNode(sourceFile, importDecl);
|
||||
}
|
||||
}
|
||||
|
||||
// token.parent is a variableDeclaration
|
||||
function tryDeleteVariableDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, varDecl: VariableDeclaration): void {
|
||||
switch (varDecl.parent.parent.kind) {
|
||||
case SyntaxKind.ForStatement: {
|
||||
const forStatement = varDecl.parent.parent;
|
||||
const forInitializer = <VariableDeclarationList>forStatement.initializer;
|
||||
if (forInitializer.declarations.length === 1) {
|
||||
changes.deleteNode(sourceFile, forInitializer);
|
||||
}
|
||||
else {
|
||||
changes.deleteNodeInList(sourceFile, varDecl);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SyntaxKind.ForOfStatement:
|
||||
const forOfStatement = <ForOfStatement>varDecl.parent.parent;
|
||||
Debug.assert(forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList);
|
||||
const forOfInitializer = <VariableDeclarationList>forOfStatement.initializer;
|
||||
changes.replaceNode(sourceFile, forOfInitializer.declarations[0], createObjectLiteral());
|
||||
break;
|
||||
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.TryStatement:
|
||||
break;
|
||||
|
||||
default:
|
||||
const variableStatement = <VariableStatement>varDecl.parent.parent;
|
||||
if (variableStatement.declarationList.declarations.length === 1) {
|
||||
changes.deleteNode(sourceFile, variableStatement);
|
||||
}
|
||||
else {
|
||||
changes.deleteNodeInList(sourceFile, varDecl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,24 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
|
||||
export function newNodesToChanges(newNodes: Node[], insertAfter: Node, context: CodeFixContext) {
|
||||
const sourceFile = context.sourceFile;
|
||||
|
||||
const changeTracker = textChanges.ChangeTracker.fromContext(context);
|
||||
|
||||
for (const newNode of newNodes) {
|
||||
changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode, { suffix: context.newLineCharacter });
|
||||
}
|
||||
|
||||
const changes = changeTracker.getChanges();
|
||||
if (!some(changes)) {
|
||||
return changes;
|
||||
}
|
||||
|
||||
Debug.assert(changes.length === 1);
|
||||
const consolidatedChanges: FileTextChanges[] = [{
|
||||
fileName: changes[0].fileName,
|
||||
textChanges: [{
|
||||
span: changes[0].textChanges[0].span,
|
||||
newText: changes[0].textChanges.reduce((prev, cur) => prev + cur.newText, "")
|
||||
}]
|
||||
|
||||
}];
|
||||
return consolidatedChanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds members of the resolved type that are missing in the class pointed to by class decl
|
||||
* and generates source code for the missing members.
|
||||
* @param possiblyMissingSymbols The collection of symbols to filter and then get insertions for.
|
||||
* @returns Empty string iff there are no member insertions.
|
||||
*/
|
||||
export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: Symbol[], checker: TypeChecker): Node[] {
|
||||
export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: ReadonlyArray<Symbol>, checker: TypeChecker, out: (node: ClassElement) => void): void {
|
||||
const classMembers = classDeclaration.symbol.members;
|
||||
const missingMembers = possiblyMissingSymbols.filter(symbol => !classMembers.has(symbol.escapedName));
|
||||
|
||||
let newNodes: Node[] = [];
|
||||
for (const symbol of missingMembers) {
|
||||
const newNode = createNewNodeForMemberSymbol(symbol, classDeclaration, checker);
|
||||
if (newNode) {
|
||||
if (Array.isArray(newNode)) {
|
||||
newNodes = newNodes.concat(newNode);
|
||||
}
|
||||
else {
|
||||
newNodes.push(newNode);
|
||||
}
|
||||
for (const symbol of possiblyMissingSymbols) {
|
||||
if (!classMembers.has(symbol.escapedName)) {
|
||||
addNewNodeForMemberSymbol(symbol, classDeclaration, checker, out);
|
||||
}
|
||||
}
|
||||
return newNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns Empty string iff there we can't figure out a representation for `symbol` in `enclosingDeclaration`.
|
||||
*/
|
||||
function createNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker): Node[] | Node | undefined {
|
||||
function addNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker, out: (node: Node) => void): void {
|
||||
const declarations = symbol.getDeclarations();
|
||||
if (!(declarations && declarations.length)) {
|
||||
return undefined;
|
||||
@@ -63,7 +26,7 @@ namespace ts.codefix {
|
||||
|
||||
const declaration = declarations[0] as Declaration;
|
||||
// Clone name to remove leading trivia.
|
||||
const name = getSynthesizedClone(getNameOfDeclaration(declaration)) as PropertyName;
|
||||
const name = getSynthesizedDeepClone(getNameOfDeclaration(declaration)) as PropertyName;
|
||||
const visibilityModifier = createVisibilityModifier(getModifierFlags(declaration));
|
||||
const modifiers = visibilityModifier ? createNodeArray([visibilityModifier]) : undefined;
|
||||
const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration));
|
||||
@@ -75,14 +38,14 @@ namespace ts.codefix {
|
||||
case SyntaxKind.PropertySignature:
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
const typeNode = checker.typeToTypeNode(type, enclosingDeclaration);
|
||||
const property = createProperty(
|
||||
out(createProperty(
|
||||
/*decorators*/undefined,
|
||||
modifiers,
|
||||
name,
|
||||
optional ? createToken(SyntaxKind.QuestionToken) : undefined,
|
||||
typeNode,
|
||||
/*initializer*/ undefined);
|
||||
return property;
|
||||
/*initializer*/ undefined));
|
||||
break;
|
||||
case SyntaxKind.MethodSignature:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
// The signature for the implementation appears as an entry in `signatures` iff
|
||||
@@ -94,81 +57,71 @@ namespace ts.codefix {
|
||||
// correspondence of declarations and signatures.
|
||||
const signatures = checker.getSignaturesOfType(type, SignatureKind.Call);
|
||||
if (!some(signatures)) {
|
||||
return undefined;
|
||||
break;
|
||||
}
|
||||
|
||||
if (declarations.length === 1) {
|
||||
Debug.assert(signatures.length === 1);
|
||||
const signature = signatures[0];
|
||||
return signatureToMethodDeclaration(signature, enclosingDeclaration, createStubbedMethodBody());
|
||||
outputMethod(signature, modifiers, name, createStubbedMethodBody());
|
||||
break;
|
||||
}
|
||||
|
||||
const signatureDeclarations: MethodDeclaration[] = [];
|
||||
for (const signature of signatures) {
|
||||
const methodDeclaration = signatureToMethodDeclaration(signature, enclosingDeclaration);
|
||||
if (methodDeclaration) {
|
||||
signatureDeclarations.push(methodDeclaration);
|
||||
}
|
||||
// Need to ensure nodes are fresh each time so they can have different positions.
|
||||
outputMethod(signature, getSynthesizedDeepClones(modifiers), getSynthesizedDeepClone(name));
|
||||
}
|
||||
|
||||
if (declarations.length > signatures.length) {
|
||||
const signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration);
|
||||
const methodDeclaration = signatureToMethodDeclaration(signature, enclosingDeclaration, createStubbedMethodBody());
|
||||
if (methodDeclaration) {
|
||||
signatureDeclarations.push(methodDeclaration);
|
||||
}
|
||||
outputMethod(signature, modifiers, name, createStubbedMethodBody());
|
||||
}
|
||||
else {
|
||||
Debug.assert(declarations.length === signatures.length);
|
||||
const methodImplementingSignatures = createMethodImplementingSignatures(signatures, name, optional, modifiers);
|
||||
signatureDeclarations.push(methodImplementingSignatures);
|
||||
out(createMethodImplementingSignatures(signatures, name, optional, modifiers));
|
||||
}
|
||||
return signatureDeclarations;
|
||||
default:
|
||||
return undefined;
|
||||
break;
|
||||
}
|
||||
|
||||
function signatureToMethodDeclaration(signature: Signature, enclosingDeclaration: Node, body?: Block) {
|
||||
const signatureDeclaration = <MethodDeclaration>checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.SuppressAnyReturnType);
|
||||
if (signatureDeclaration) {
|
||||
signatureDeclaration.decorators = undefined;
|
||||
signatureDeclaration.modifiers = modifiers;
|
||||
signatureDeclaration.name = name;
|
||||
signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined;
|
||||
signatureDeclaration.body = body;
|
||||
}
|
||||
return signatureDeclaration;
|
||||
function outputMethod(signature: Signature, modifiers: NodeArray<Modifier>, name: PropertyName, body?: Block): void {
|
||||
const method = signatureToMethodDeclaration(checker, signature, enclosingDeclaration, modifiers, name, optional, body);
|
||||
if (method) out(method);
|
||||
}
|
||||
}
|
||||
|
||||
export function createMethodFromCallExpression(callExpression: CallExpression, methodName: string, includeTypeScriptSyntax: boolean, makeStatic: boolean): MethodDeclaration {
|
||||
const parameters = createDummyParameters(callExpression.arguments.length, /*names*/ undefined, /*minArgumentCount*/ undefined, includeTypeScriptSyntax);
|
||||
|
||||
let typeParameters: TypeParameterDeclaration[];
|
||||
if (includeTypeScriptSyntax) {
|
||||
const typeArgCount = length(callExpression.typeArguments);
|
||||
for (let i = 0; i < typeArgCount; i++) {
|
||||
const name = typeArgCount < 8 ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`;
|
||||
const typeParameter = createTypeParameterDeclaration(name, /*constraint*/ undefined, /*defaultType*/ undefined);
|
||||
(typeParameters ? typeParameters : typeParameters = []).push(typeParameter);
|
||||
}
|
||||
function signatureToMethodDeclaration(checker: TypeChecker, signature: Signature, enclosingDeclaration: ClassLikeDeclaration, modifiers: NodeArray<Modifier>, name: PropertyName, optional: boolean, body: Block | undefined) {
|
||||
const signatureDeclaration = <MethodDeclaration>checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.SuppressAnyReturnType);
|
||||
if (!signatureDeclaration) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const newMethod = createMethod(
|
||||
signatureDeclaration.decorators = undefined;
|
||||
signatureDeclaration.modifiers = modifiers;
|
||||
signatureDeclaration.name = name;
|
||||
signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined;
|
||||
signatureDeclaration.body = body;
|
||||
return signatureDeclaration;
|
||||
}
|
||||
|
||||
function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T> | undefined): NodeArray<T> | undefined {
|
||||
return nodes && createNodeArray(nodes.map(getSynthesizedDeepClone));
|
||||
}
|
||||
|
||||
export function createMethodFromCallExpression({ typeArguments, arguments: args }: CallExpression, methodName: string, inJs: boolean, makeStatic: boolean): MethodDeclaration {
|
||||
return createMethod(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined,
|
||||
/*asteriskToken*/ undefined,
|
||||
methodName,
|
||||
/*questionToken*/ undefined,
|
||||
typeParameters,
|
||||
parameters,
|
||||
/*type*/ includeTypeScriptSyntax ? createKeywordTypeNode(SyntaxKind.AnyKeyword) : undefined,
|
||||
createStubbedMethodBody()
|
||||
);
|
||||
return newMethod;
|
||||
/*typeParameters*/ inJs ? undefined : map(typeArguments, (_, i) =>
|
||||
createTypeParameterDeclaration(CharacterCodes.T + typeArguments.length - 1 <= CharacterCodes.Z ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`)),
|
||||
/*parameters*/ createDummyParameters(args.length, /*names*/ undefined, /*minArgumentCount*/ undefined, inJs),
|
||||
/*type*/ inJs ? undefined : createKeywordTypeNode(SyntaxKind.AnyKeyword),
|
||||
createStubbedMethodBody());
|
||||
}
|
||||
|
||||
function createDummyParameters(argCount: number, names: string[] | undefined, minArgumentCount: number | undefined, addAnyType: boolean) {
|
||||
function createDummyParameters(argCount: number, names: string[] | undefined, minArgumentCount: number | undefined, inJs: boolean): ParameterDeclaration[] {
|
||||
const parameters: ParameterDeclaration[] = [];
|
||||
for (let i = 0; i < argCount; i++) {
|
||||
const newParameter = createParameter(
|
||||
@@ -177,11 +130,10 @@ namespace ts.codefix {
|
||||
/*dotDotDotToken*/ undefined,
|
||||
/*name*/ names && names[i] || `arg${i}`,
|
||||
/*questionToken*/ minArgumentCount !== undefined && i >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined,
|
||||
/*type*/ addAnyType ? createKeywordTypeNode(SyntaxKind.AnyKeyword) : undefined,
|
||||
/*type*/ inJs ? undefined : createKeywordTypeNode(SyntaxKind.AnyKeyword),
|
||||
/*initializer*/ undefined);
|
||||
parameters.push(newParameter);
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@@ -205,7 +157,7 @@ namespace ts.codefix {
|
||||
const maxNonRestArgs = maxArgsSignature.parameters.length - (maxArgsSignature.hasRestParameter ? 1 : 0);
|
||||
const maxArgsParameterSymbolNames = maxArgsSignature.parameters.map(symbol => symbol.name);
|
||||
|
||||
const parameters = createDummyParameters(maxNonRestArgs, maxArgsParameterSymbolNames, minArgumentCount, /*addAnyType*/ true);
|
||||
const parameters = createDummyParameters(maxNonRestArgs, maxArgsParameterSymbolNames, minArgumentCount, /*inJs*/ false);
|
||||
|
||||
if (someSigHasRestParameter) {
|
||||
const anyArrayType = createArrayTypeNode(createKeywordTypeNode(SyntaxKind.AnyKeyword));
|
||||
@@ -229,7 +181,7 @@ namespace ts.codefix {
|
||||
/*returnType*/ undefined);
|
||||
}
|
||||
|
||||
export function createStubbedMethod(
|
||||
function createStubbedMethod(
|
||||
modifiers: ReadonlyArray<Modifier>,
|
||||
name: PropertyName,
|
||||
optional: boolean,
|
||||
@@ -258,7 +210,7 @@ namespace ts.codefix {
|
||||
/*multiline*/ true);
|
||||
}
|
||||
|
||||
function createVisibilityModifier(flags: ModifierFlags) {
|
||||
function createVisibilityModifier(flags: ModifierFlags): Modifier | undefined {
|
||||
if (flags & ModifierFlags.Public) {
|
||||
return createToken(SyntaxKind.PublicKeyword);
|
||||
}
|
||||
|
||||
@@ -9,14 +9,17 @@ namespace ts.codefix {
|
||||
Diagnostics.Cannot_find_namespace_0.code,
|
||||
Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code
|
||||
],
|
||||
getCodeActions: getImportCodeActions
|
||||
getCodeActions: getImportCodeActions,
|
||||
// TODO: GH#20315
|
||||
fixIds: [],
|
||||
getAllCodeActions: notImplemented,
|
||||
});
|
||||
|
||||
type ImportCodeActionKind = "CodeChange" | "InsertingIntoExistingImport" | "NewImport";
|
||||
// Map from module Id to an array of import declarations in that module.
|
||||
type ImportDeclarationMap = AnyImportSyntax[][];
|
||||
|
||||
interface ImportCodeAction extends CodeAction {
|
||||
interface ImportCodeAction extends CodeFixAction {
|
||||
kind: ImportCodeActionKind;
|
||||
moduleSpecifier?: string;
|
||||
}
|
||||
@@ -155,6 +158,8 @@ namespace ts.codefix {
|
||||
return {
|
||||
description: formatMessage.apply(undefined, [undefined, description].concat(<any[]>diagnosticArgs)),
|
||||
changes,
|
||||
// TODO: GH#20315
|
||||
fixId: undefined,
|
||||
kind,
|
||||
moduleSpecifier
|
||||
};
|
||||
|
||||
@@ -1,270 +1,256 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const fixId = "inferFromUsage";
|
||||
const errorCodes = [
|
||||
// Variable declarations
|
||||
Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code,
|
||||
|
||||
// Variable uses
|
||||
Diagnostics.Variable_0_implicitly_has_an_1_type.code,
|
||||
|
||||
// Parameter declarations
|
||||
Diagnostics.Parameter_0_implicitly_has_an_1_type.code,
|
||||
Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code,
|
||||
|
||||
// Get Accessor declarations
|
||||
Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code,
|
||||
Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code,
|
||||
|
||||
// Set Accessor declarations
|
||||
Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code,
|
||||
|
||||
// Property declarations
|
||||
Diagnostics.Member_0_implicitly_has_an_1_type.code,
|
||||
];
|
||||
registerCodeFix({
|
||||
errorCodes: [
|
||||
// Variable declarations
|
||||
Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code,
|
||||
errorCodes,
|
||||
getCodeActions({ sourceFile, program, span: { start }, errorCode, cancellationToken }) {
|
||||
if (isSourceFileJavaScript(sourceFile)) {
|
||||
return undefined; // TODO: GH#20113
|
||||
}
|
||||
|
||||
// Variable uses
|
||||
Diagnostics.Variable_0_implicitly_has_an_1_type.code,
|
||||
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
|
||||
const fix = getFix(sourceFile, token, errorCode, program, cancellationToken);
|
||||
if (!fix) return undefined;
|
||||
|
||||
// Parameter declarations
|
||||
Diagnostics.Parameter_0_implicitly_has_an_1_type.code,
|
||||
Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code,
|
||||
|
||||
// Get Accessor declarations
|
||||
Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code,
|
||||
Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code,
|
||||
|
||||
// Set Accessor declarations
|
||||
Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code,
|
||||
|
||||
// Property declarations
|
||||
Diagnostics.Member_0_implicitly_has_an_1_type.code,
|
||||
],
|
||||
getCodeActions: getActionsForAddExplicitTypeAnnotation
|
||||
const { declaration, textChanges } = fix;
|
||||
const name = getNameOfDeclaration(declaration);
|
||||
const description = formatStringFromArgs(getLocaleSpecificMessage(getDiagnostic(errorCode, token)), [name.getText()]);
|
||||
return [{ description, changes: [{ fileName: sourceFile.fileName, textChanges }], fixId }];
|
||||
},
|
||||
fixIds: [fixId],
|
||||
getAllCodeActions(context) {
|
||||
const { sourceFile, program, cancellationToken } = context;
|
||||
const seenFunctions = createMap<true>();
|
||||
return codeFixAllWithTextChanges(context, errorCodes, (changes, err) => {
|
||||
const fix = getFix(sourceFile, getTokenAtPosition(err.file!, err.start!, /*includeJsDocComment*/ false), err.code, program, cancellationToken, seenFunctions);
|
||||
if (fix) changes.push(...fix.textChanges);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function getActionsForAddExplicitTypeAnnotation({ sourceFile, program, span: { start }, errorCode, cancellationToken }: CodeFixContext): CodeAction[] | undefined {
|
||||
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
|
||||
let writer: StringSymbolWriter;
|
||||
function getDiagnostic(errorCode: number, token: Node): DiagnosticMessage {
|
||||
switch (errorCode) {
|
||||
case Diagnostics.Parameter_0_implicitly_has_an_1_type.code:
|
||||
return isSetAccessor(getContainingFunction(token)) ? Diagnostics.Infer_type_of_0_from_usage : Diagnostics.Infer_parameter_types_from_usage;
|
||||
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
|
||||
return Diagnostics.Infer_parameter_types_from_usage;
|
||||
default:
|
||||
return Diagnostics.Infer_type_of_0_from_usage;
|
||||
}
|
||||
}
|
||||
|
||||
if (isInJavaScriptFile(token)) {
|
||||
interface Fix {
|
||||
readonly declaration: Declaration;
|
||||
readonly textChanges: TextChange[];
|
||||
}
|
||||
|
||||
function getFix(sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, seenFunctions?: Map<true>): Fix | undefined {
|
||||
if (!isAllowedTokenKind(token.kind)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
switch (token.kind) {
|
||||
const containingFunction = getContainingFunction(token);
|
||||
switch (errorCode) {
|
||||
// Variable and Property declarations
|
||||
case Diagnostics.Member_0_implicitly_has_an_1_type.code:
|
||||
case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code:
|
||||
return getCodeActionForVariableDeclaration(<PropertyDeclaration | PropertySignature | VariableDeclaration>token.parent, sourceFile, program, cancellationToken);
|
||||
|
||||
case Diagnostics.Variable_0_implicitly_has_an_1_type.code: {
|
||||
const symbol = program.getTypeChecker().getSymbolAtLocation(token);
|
||||
return symbol && symbol.valueDeclaration && getCodeActionForVariableDeclaration(<VariableDeclaration>symbol.valueDeclaration, sourceFile, program, cancellationToken);
|
||||
}
|
||||
|
||||
// Parameter declarations
|
||||
case Diagnostics.Parameter_0_implicitly_has_an_1_type.code:
|
||||
if (isSetAccessor(containingFunction)) {
|
||||
return getCodeActionForSetAccessor(containingFunction, sourceFile, program, cancellationToken);
|
||||
}
|
||||
// falls through
|
||||
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
|
||||
return !seenFunctions || addToSeen(seenFunctions, getNodeId(containingFunction))
|
||||
? getCodeActionForParameters(<ParameterDeclaration>token.parent, containingFunction, sourceFile, program, cancellationToken)
|
||||
: undefined;
|
||||
|
||||
// Get Accessor declarations
|
||||
case Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code:
|
||||
case Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code:
|
||||
return isGetAccessor(containingFunction) ? getCodeActionForGetAccessor(containingFunction, sourceFile, program, cancellationToken) : undefined;
|
||||
|
||||
// Set Accessor declarations
|
||||
case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code:
|
||||
return isSetAccessor(containingFunction) ? getCodeActionForSetAccessor(containingFunction, sourceFile, program, cancellationToken) : undefined;
|
||||
|
||||
default:
|
||||
throw Debug.fail(String(errorCode));
|
||||
}
|
||||
}
|
||||
|
||||
function isAllowedTokenKind(kind: SyntaxKind): boolean {
|
||||
switch (kind) {
|
||||
case SyntaxKind.Identifier:
|
||||
case SyntaxKind.DotDotDotToken:
|
||||
case SyntaxKind.PublicKeyword:
|
||||
case SyntaxKind.PrivateKeyword:
|
||||
case SyntaxKind.ProtectedKeyword:
|
||||
case SyntaxKind.ReadonlyKeyword:
|
||||
// Allowed
|
||||
break;
|
||||
return true;
|
||||
default:
|
||||
return undefined;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const containingFunction = getContainingFunction(token);
|
||||
const checker = program.getTypeChecker();
|
||||
function getCodeActionForVariableDeclaration(declaration: VariableDeclaration | PropertyDeclaration | PropertySignature, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Fix | undefined {
|
||||
if (!isIdentifier(declaration.name)) return undefined;
|
||||
const type = inferTypeForVariableFromUsage(declaration.name, sourceFile, program, cancellationToken);
|
||||
return makeFix(declaration, declaration.name.getEnd(), type, program);
|
||||
}
|
||||
|
||||
switch (errorCode) {
|
||||
// Variable and Property declarations
|
||||
case Diagnostics.Member_0_implicitly_has_an_1_type.code:
|
||||
case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code:
|
||||
return getCodeActionForVariableDeclaration(<PropertyDeclaration | PropertySignature | VariableDeclaration>token.parent);
|
||||
case Diagnostics.Variable_0_implicitly_has_an_1_type.code:
|
||||
return getCodeActionForVariableUsage(<Identifier>token);
|
||||
|
||||
// Parameter declarations
|
||||
case Diagnostics.Parameter_0_implicitly_has_an_1_type.code:
|
||||
if (isSetAccessor(containingFunction)) {
|
||||
return getCodeActionForSetAccessor(containingFunction);
|
||||
}
|
||||
// falls through
|
||||
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
|
||||
return getCodeActionForParameters(<ParameterDeclaration>token.parent);
|
||||
|
||||
// Get Accessor declarations
|
||||
case Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code:
|
||||
case Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code:
|
||||
return isGetAccessor(containingFunction) ? getCodeActionForGetAccessor(containingFunction) : undefined;
|
||||
|
||||
// Set Accessor declarations
|
||||
case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code:
|
||||
return isSetAccessor(containingFunction) ? getCodeActionForSetAccessor(containingFunction) : undefined;
|
||||
function isApplicableFunctionForInference(declaration: FunctionLike): declaration is MethodDeclaration | FunctionDeclaration | ConstructorDeclaration {
|
||||
switch (declaration.kind) {
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.Constructor:
|
||||
return true;
|
||||
case SyntaxKind.FunctionExpression:
|
||||
return !!(declaration as FunctionExpression).name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
function getCodeActionForVariableDeclaration(declaration: VariableDeclaration | PropertyDeclaration | PropertySignature) {
|
||||
if (!isIdentifier(declaration.name)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const type = inferTypeForVariableFromUsage(declaration.name);
|
||||
const typeString = type && typeToString(type, declaration);
|
||||
|
||||
if (!typeString) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return createCodeActions(declaration.name.getText(), declaration.name.getEnd(), `: ${typeString}`);
|
||||
}
|
||||
|
||||
function getCodeActionForVariableUsage(token: Identifier) {
|
||||
const symbol = checker.getSymbolAtLocation(token);
|
||||
return symbol && symbol.valueDeclaration && getCodeActionForVariableDeclaration(<VariableDeclaration>symbol.valueDeclaration);
|
||||
}
|
||||
|
||||
function isApplicableFunctionForInference(declaration: FunctionLike): declaration is MethodDeclaration | FunctionDeclaration | ConstructorDeclaration {
|
||||
switch (declaration.kind) {
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.Constructor:
|
||||
return true;
|
||||
case SyntaxKind.FunctionExpression:
|
||||
return !!(declaration as FunctionExpression).name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getCodeActionForParameters(parameterDeclaration: ParameterDeclaration): CodeAction[] {
|
||||
if (!isIdentifier(parameterDeclaration.name) || !isApplicableFunctionForInference(containingFunction)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const types = inferTypeForParametersFromUsage(containingFunction) ||
|
||||
map(containingFunction.parameters, p => isIdentifier(p.name) && inferTypeForVariableFromUsage(p.name));
|
||||
|
||||
if (!types) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const textChanges: TextChange[] = zipWith(containingFunction.parameters, types, (parameter, type) => {
|
||||
if (type && !parameter.type && !parameter.initializer) {
|
||||
const typeString = typeToString(type, containingFunction);
|
||||
return typeString ? {
|
||||
span: { start: parameter.end, length: 0 },
|
||||
newText: `: ${typeString}`
|
||||
} : undefined;
|
||||
}
|
||||
}).filter(c => !!c);
|
||||
|
||||
return textChanges.length ? [{
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Infer_parameter_types_from_usage), [parameterDeclaration.name.getText()]),
|
||||
changes: [{
|
||||
fileName: sourceFile.fileName,
|
||||
textChanges
|
||||
}]
|
||||
}] : undefined;
|
||||
}
|
||||
|
||||
function getCodeActionForSetAccessor(setAccessorDeclaration: SetAccessorDeclaration) {
|
||||
const setAccessorParameter = setAccessorDeclaration.parameters[0];
|
||||
if (!setAccessorParameter || !isIdentifier(setAccessorDeclaration.name) || !isIdentifier(setAccessorParameter.name)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const type = inferTypeForVariableFromUsage(setAccessorDeclaration.name) ||
|
||||
inferTypeForVariableFromUsage(setAccessorParameter.name);
|
||||
const typeString = type && typeToString(type, containingFunction);
|
||||
if (!typeString) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return createCodeActions(setAccessorDeclaration.name.getText(), setAccessorParameter.name.getEnd(), `: ${typeString}`);
|
||||
}
|
||||
|
||||
function getCodeActionForGetAccessor(getAccessorDeclaration: GetAccessorDeclaration) {
|
||||
if (!isIdentifier(getAccessorDeclaration.name)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const type = inferTypeForVariableFromUsage(getAccessorDeclaration.name);
|
||||
const typeString = type && typeToString(type, containingFunction);
|
||||
if (!typeString) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const closeParenToken = getFirstChildOfKind(getAccessorDeclaration, sourceFile, SyntaxKind.CloseParenToken);
|
||||
return createCodeActions(getAccessorDeclaration.name.getText(), closeParenToken.getEnd(), `: ${typeString}`);
|
||||
}
|
||||
|
||||
function createCodeActions(name: string, start: number, typeString: string) {
|
||||
return [{
|
||||
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Infer_type_of_0_from_usage), [name]),
|
||||
changes: [{
|
||||
fileName: sourceFile.fileName,
|
||||
textChanges: [{
|
||||
span: { start, length: 0 },
|
||||
newText: typeString
|
||||
}]
|
||||
}]
|
||||
}];
|
||||
}
|
||||
|
||||
function getReferences(token: PropertyName | Token<SyntaxKind.ConstructorKeyword>) {
|
||||
const references = FindAllReferences.findReferencedSymbols(
|
||||
program,
|
||||
cancellationToken,
|
||||
program.getSourceFiles(),
|
||||
token.getSourceFile(),
|
||||
token.getStart());
|
||||
|
||||
Debug.assert(!!references, "Found no references!");
|
||||
Debug.assert(references.length === 1, "Found more references than expected");
|
||||
|
||||
return map(references[0].references, r => <Identifier>getTokenAtPosition(program.getSourceFile(r.fileName), r.textSpan.start, /*includeJsDocComment*/ false));
|
||||
}
|
||||
|
||||
function inferTypeForVariableFromUsage(token: Identifier) {
|
||||
return InferFromReference.inferTypeFromReferences(getReferences(token), checker, cancellationToken);
|
||||
}
|
||||
|
||||
function inferTypeForParametersFromUsage(containingFunction: FunctionLikeDeclaration) {
|
||||
switch (containingFunction.kind) {
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
const isConstructor = containingFunction.kind === SyntaxKind.Constructor;
|
||||
const searchToken = isConstructor ?
|
||||
<Token<SyntaxKind.ConstructorKeyword>>getFirstChildOfKind(containingFunction, sourceFile, SyntaxKind.ConstructorKeyword) :
|
||||
containingFunction.name;
|
||||
if (searchToken) {
|
||||
return InferFromReference.inferTypeForParametersFromReferences(getReferences(searchToken), containingFunction, checker, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeAccessiblityWriter() {
|
||||
if (!writer) {
|
||||
let str = "";
|
||||
let typeIsAccessible = true;
|
||||
|
||||
const writeText: (text: string) => void = text => str += text;
|
||||
writer = {
|
||||
string: () => typeIsAccessible ? str : undefined,
|
||||
writeKeyword: writeText,
|
||||
writeOperator: writeText,
|
||||
writePunctuation: writeText,
|
||||
writeSpace: writeText,
|
||||
writeStringLiteral: writeText,
|
||||
writeParameter: writeText,
|
||||
writeProperty: writeText,
|
||||
writeSymbol: writeText,
|
||||
writeLine: () => str += " ",
|
||||
increaseIndent: noop,
|
||||
decreaseIndent: noop,
|
||||
clear: () => { str = ""; typeIsAccessible = true; },
|
||||
trackSymbol: (symbol, declaration, meaning) => {
|
||||
if (checker.isSymbolAccessible(symbol, declaration, meaning, /*shouldComputeAliasToMarkVisible*/ false).accessibility !== SymbolAccessibility.Accessible) {
|
||||
typeIsAccessible = false;
|
||||
}
|
||||
},
|
||||
reportInaccessibleThisError: () => { typeIsAccessible = false; },
|
||||
reportPrivateInBaseOfClassExpression: () => { typeIsAccessible = false; },
|
||||
reportInaccessibleUniqueSymbolError: () => { typeIsAccessible = false; }
|
||||
};
|
||||
}
|
||||
writer.clear();
|
||||
return writer;
|
||||
}
|
||||
|
||||
function typeToString(type: Type, enclosingDeclaration: Declaration) {
|
||||
const writer = getTypeAccessiblityWriter();
|
||||
checker.getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration);
|
||||
return writer.string();
|
||||
}
|
||||
|
||||
function getFirstChildOfKind(node: Node, sourcefile: SourceFile, kind: SyntaxKind) {
|
||||
for (const child of node.getChildren(sourcefile)) {
|
||||
if (child.kind === kind) return child;
|
||||
}
|
||||
function getCodeActionForParameters(parameterDeclaration: ParameterDeclaration, containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Fix | undefined {
|
||||
if (!isIdentifier(parameterDeclaration.name) || !isApplicableFunctionForInference(containingFunction)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const types = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken) ||
|
||||
containingFunction.parameters.map(p => isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, sourceFile, program, cancellationToken) : undefined);
|
||||
if (!types) return undefined;
|
||||
|
||||
const textChanges = arrayFrom(mapDefinedIterator(zipToIterator(containingFunction.parameters, types), ([parameter, type]) =>
|
||||
type && !parameter.type && !parameter.initializer ? makeChange(containingFunction, parameter.end, type, program) : undefined));
|
||||
return textChanges.length ? { declaration: parameterDeclaration, textChanges } : undefined;
|
||||
}
|
||||
|
||||
function getCodeActionForSetAccessor(setAccessorDeclaration: SetAccessorDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Fix | undefined {
|
||||
const setAccessorParameter = setAccessorDeclaration.parameters[0];
|
||||
if (!setAccessorParameter || !isIdentifier(setAccessorDeclaration.name) || !isIdentifier(setAccessorParameter.name)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, sourceFile, program, cancellationToken) ||
|
||||
inferTypeForVariableFromUsage(setAccessorParameter.name, sourceFile, program, cancellationToken);
|
||||
return makeFix(setAccessorParameter, setAccessorParameter.name.getEnd(), type, program);
|
||||
}
|
||||
|
||||
function getCodeActionForGetAccessor(getAccessorDeclaration: GetAccessorDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Fix | undefined {
|
||||
if (!isIdentifier(getAccessorDeclaration.name)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const type = inferTypeForVariableFromUsage(getAccessorDeclaration.name, sourceFile, program, cancellationToken);
|
||||
const closeParenToken = getFirstChildOfKind(getAccessorDeclaration, sourceFile, SyntaxKind.CloseParenToken);
|
||||
return makeFix(getAccessorDeclaration, closeParenToken.getEnd(), type, program);
|
||||
}
|
||||
|
||||
function makeFix(declaration: Declaration, start: number, type: Type | undefined, program: Program): Fix | undefined {
|
||||
return type && { declaration, textChanges: [makeChange(declaration, start, type, program)] };
|
||||
}
|
||||
|
||||
function makeChange(declaration: Declaration, start: number, type: Type | undefined, program: Program): TextChange | undefined {
|
||||
const typeString = type && typeToString(type, declaration, program.getTypeChecker());
|
||||
return typeString === undefined ? undefined : { span: createTextSpan(start, 0), newText: `: ${typeString}` };
|
||||
}
|
||||
|
||||
function getReferences(token: PropertyName | Token<SyntaxKind.ConstructorKeyword>, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Identifier[] {
|
||||
const references = FindAllReferences.findReferencedSymbols(
|
||||
program,
|
||||
cancellationToken,
|
||||
program.getSourceFiles(),
|
||||
sourceFile,
|
||||
token.getStart(sourceFile));
|
||||
|
||||
Debug.assert(!!references, "Found no references!");
|
||||
Debug.assert(references.length === 1, "Found more references than expected");
|
||||
|
||||
return references[0].references.map(r => <Identifier>getTokenAtPosition(program.getSourceFile(r.fileName), r.textSpan.start, /*includeJsDocComment*/ false));
|
||||
}
|
||||
|
||||
function inferTypeForVariableFromUsage(token: Identifier, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): Type | undefined {
|
||||
return InferFromReference.inferTypeFromReferences(getReferences(token, sourceFile, program, cancellationToken), program.getTypeChecker(), cancellationToken);
|
||||
}
|
||||
|
||||
function inferTypeForParametersFromUsage(containingFunction: FunctionLikeDeclaration, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): (Type | undefined)[] | undefined {
|
||||
switch (containingFunction.kind) {
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
const isConstructor = containingFunction.kind === SyntaxKind.Constructor;
|
||||
const searchToken = isConstructor ?
|
||||
<Token<SyntaxKind.ConstructorKeyword>>getFirstChildOfKind(containingFunction, sourceFile, SyntaxKind.ConstructorKeyword) :
|
||||
containingFunction.name;
|
||||
if (searchToken) {
|
||||
return InferFromReference.inferTypeForParametersFromReferences(getReferences(searchToken, sourceFile, program, cancellationToken), containingFunction, program.getTypeChecker(), cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeAccessiblityWriter(checker: TypeChecker): StringSymbolWriter {
|
||||
let str = "";
|
||||
let typeIsAccessible = true;
|
||||
|
||||
const writeText: (text: string) => void = text => str += text;
|
||||
return {
|
||||
string: () => typeIsAccessible ? str : undefined,
|
||||
writeKeyword: writeText,
|
||||
writeOperator: writeText,
|
||||
writePunctuation: writeText,
|
||||
writeSpace: writeText,
|
||||
writeStringLiteral: writeText,
|
||||
writeParameter: writeText,
|
||||
writeProperty: writeText,
|
||||
writeSymbol: writeText,
|
||||
writeLine: () => writeText(" "),
|
||||
increaseIndent: noop,
|
||||
decreaseIndent: noop,
|
||||
clear: () => { str = ""; typeIsAccessible = true; },
|
||||
trackSymbol: (symbol, declaration, meaning) => {
|
||||
if (checker.isSymbolAccessible(symbol, declaration, meaning, /*shouldComputeAliasToMarkVisible*/ false).accessibility !== SymbolAccessibility.Accessible) {
|
||||
typeIsAccessible = false;
|
||||
}
|
||||
},
|
||||
reportInaccessibleThisError: () => { typeIsAccessible = false; },
|
||||
reportPrivateInBaseOfClassExpression: () => { typeIsAccessible = false; },
|
||||
reportInaccessibleUniqueSymbolError: () => { typeIsAccessible = false; }
|
||||
};
|
||||
}
|
||||
|
||||
function typeToString(type: Type, enclosingDeclaration: Declaration, checker: TypeChecker): string {
|
||||
const writer = getTypeAccessiblityWriter(checker);
|
||||
checker.getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration);
|
||||
return writer.string();
|
||||
}
|
||||
|
||||
namespace InferFromReference {
|
||||
|
||||
@@ -1883,7 +1883,7 @@ namespace ts {
|
||||
return [];
|
||||
}
|
||||
|
||||
function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[] {
|
||||
function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray<number>, formatOptions: FormatCodeSettings): ReadonlyArray<CodeFixAction> {
|
||||
synchronizeHostData();
|
||||
const sourceFile = getValidSourceFile(fileName);
|
||||
const span = createTextSpanFromBounds(start, end);
|
||||
@@ -1896,6 +1896,16 @@ namespace ts {
|
||||
});
|
||||
}
|
||||
|
||||
function getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions {
|
||||
synchronizeHostData();
|
||||
Debug.assert(scope.type === "file");
|
||||
const sourceFile = getValidSourceFile(scope.fileName);
|
||||
const newLineCharacter = getNewLineOrDefaultFromHost(host);
|
||||
const formatContext = formatting.getFormatContext(formatOptions);
|
||||
|
||||
return codefix.getAllFixes({ fixId, sourceFile, program, newLineCharacter, host, cancellationToken, formatContext });
|
||||
}
|
||||
|
||||
function applyCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
|
||||
function applyCodeActionCommand(action: CodeActionCommand[]): Promise<ApplyCodeActionCommandResult[]>;
|
||||
function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>;
|
||||
@@ -2190,6 +2200,7 @@ namespace ts {
|
||||
isValidBraceCompletionAtPosition,
|
||||
getSpanOfEnclosingComment,
|
||||
getCodeFixesAtPosition,
|
||||
getCombinedCodeFix,
|
||||
applyCodeActionCommand,
|
||||
getEmitOutput,
|
||||
getNonBoundSourceFile,
|
||||
|
||||
+39
-10
@@ -194,6 +194,9 @@ namespace ts.textChanges {
|
||||
export class ChangeTracker {
|
||||
private changes: Change[] = [];
|
||||
private readonly newLineCharacter: string;
|
||||
private readonly deletedNodesInLists: true[] = []; // Stores ids of nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`.
|
||||
// Map from class id to nodes to insert at the start
|
||||
private readonly nodesInsertedAtClassStarts = createMap<{ sourceFile: SourceFile, cls: ClassLikeDeclaration, members: ClassElement[] }>();
|
||||
|
||||
public static fromContext(context: TextChangesContext): ChangeTracker {
|
||||
return new ChangeTracker(context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.formatContext);
|
||||
@@ -220,14 +223,14 @@ namespace ts.textChanges {
|
||||
public deleteNode(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}) {
|
||||
const startPosition = getAdjustedStartPosition(sourceFile, node, options, Position.FullStart);
|
||||
const endPosition = getAdjustedEndPosition(sourceFile, node, options);
|
||||
this.changes.push({ kind: ChangeKind.Remove, sourceFile, range: { pos: startPosition, end: endPosition } });
|
||||
this.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
|
||||
return this;
|
||||
}
|
||||
|
||||
public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = {}) {
|
||||
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart);
|
||||
const endPosition = getAdjustedEndPosition(sourceFile, endNode, options);
|
||||
this.changes.push({ kind: ChangeKind.Remove, sourceFile, range: { pos: startPosition, end: endPosition } });
|
||||
this.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -245,6 +248,9 @@ namespace ts.textChanges {
|
||||
this.deleteNode(sourceFile, node);
|
||||
return this;
|
||||
}
|
||||
const id = getNodeId(node);
|
||||
Debug.assert(!this.deletedNodesInLists[id], "Deleting a node twice");
|
||||
this.deletedNodesInLists[id] = true;
|
||||
if (index !== containingList.length - 1) {
|
||||
const nextToken = getTokenAtPosition(sourceFile, node.end, /*includeJsDocComment*/ false);
|
||||
if (nextToken && isSeparator(node, nextToken)) {
|
||||
@@ -258,9 +264,17 @@ namespace ts.textChanges {
|
||||
}
|
||||
}
|
||||
else {
|
||||
const previousToken = getTokenAtPosition(sourceFile, containingList[index - 1].end, /*includeJsDocComment*/ false);
|
||||
if (previousToken && isSeparator(node, previousToken)) {
|
||||
this.deleteNodeRange(sourceFile, previousToken, node);
|
||||
const prev = containingList[index - 1];
|
||||
if (this.deletedNodesInLists[getNodeId(prev)]) {
|
||||
const pos = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
|
||||
const end = getAdjustedEndPosition(sourceFile, node, {});
|
||||
this.deleteRange(sourceFile, { pos, end });
|
||||
}
|
||||
else {
|
||||
const previousToken = getTokenAtPosition(sourceFile, containingList[index - 1].end, /*includeJsDocComment*/ false);
|
||||
if (previousToken && isSeparator(node, previousToken)) {
|
||||
this.deleteNodeRange(sourceFile, previousToken, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
@@ -350,11 +364,15 @@ namespace ts.textChanges {
|
||||
public insertNodeAtClassStart(sourceFile: SourceFile, cls: ClassLikeDeclaration, newElement: ClassElement, newLineCharacter: string): void {
|
||||
const firstMember = firstOrUndefined(cls.members);
|
||||
if (!firstMember) {
|
||||
const members = [newElement];
|
||||
const newCls = cls.kind === SyntaxKind.ClassDeclaration
|
||||
? updateClassDeclaration(cls, cls.decorators, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members)
|
||||
: updateClassExpression(cls, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members);
|
||||
this.replaceNode(sourceFile, cls, newCls, { useNonAdjustedEndPosition: true });
|
||||
const id = getNodeId(cls).toString();
|
||||
const newMembers = this.nodesInsertedAtClassStarts.get(id);
|
||||
if (newMembers) {
|
||||
Debug.assert(newMembers.sourceFile === sourceFile && newMembers.cls === cls);
|
||||
newMembers.members.push(newElement);
|
||||
}
|
||||
else {
|
||||
this.nodesInsertedAtClassStarts.set(id, { sourceFile, cls, members: [newElement] });
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.insertNodeBefore(sourceFile, firstMember, newElement, { suffix: newLineCharacter });
|
||||
@@ -526,7 +544,18 @@ namespace ts.textChanges {
|
||||
return this;
|
||||
}
|
||||
|
||||
private finishInsertNodeAtClassStart(): void {
|
||||
this.nodesInsertedAtClassStarts.forEach(({ sourceFile, cls, members }) => {
|
||||
const newCls = cls.kind === SyntaxKind.ClassDeclaration
|
||||
? updateClassDeclaration(cls, cls.decorators, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members)
|
||||
: updateClassExpression(cls, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members);
|
||||
this.replaceNode(sourceFile, cls, newCls, { useNonAdjustedEndPosition: true });
|
||||
});
|
||||
}
|
||||
|
||||
public getChanges(): FileTextChanges[] {
|
||||
this.finishInsertNodeAtClassStart();
|
||||
|
||||
const changesPerFile = createMap<Change[]>();
|
||||
// group changes per file
|
||||
for (const c of this.changes) {
|
||||
|
||||
+26
-1
@@ -295,7 +295,11 @@ namespace ts {
|
||||
|
||||
getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan;
|
||||
|
||||
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[];
|
||||
// TODO: GH#20538 return `ReadonlyArray<CodeFixAction>`
|
||||
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray<number>, formatOptions: FormatCodeSettings): ReadonlyArray<CodeAction>;
|
||||
// TODO: GH#20538
|
||||
/* @internal */
|
||||
getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions;
|
||||
applyCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
|
||||
applyCodeActionCommand(action: CodeActionCommand[]): Promise<ApplyCodeActionCommandResult[]>;
|
||||
applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>;
|
||||
@@ -323,6 +327,10 @@ namespace ts {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
// TODO: GH#20538
|
||||
/* @internal */
|
||||
export interface CombinedCodeFixScope { type: "file"; fileName: string; }
|
||||
|
||||
export interface GetCompletionsAtPositionOptions {
|
||||
includeExternalModuleExports: boolean;
|
||||
}
|
||||
@@ -410,6 +418,23 @@ namespace ts {
|
||||
commands?: CodeActionCommand[];
|
||||
}
|
||||
|
||||
// TODO: GH#20538
|
||||
/* @internal */
|
||||
export interface CodeFixAction extends CodeAction {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
fixId?: {};
|
||||
}
|
||||
|
||||
// TODO: GH#20538
|
||||
/* @internal */
|
||||
export interface CombinedCodeActions {
|
||||
changes: ReadonlyArray<FileTextChanges>;
|
||||
commands: ReadonlyArray<CodeActionCommand> | undefined;
|
||||
}
|
||||
|
||||
// Publicly, this type is just `{}`. Internally it is a union of all the actions we use.
|
||||
// See `commands?: {}[]` in protocol.ts
|
||||
export type CodeActionCommand = InstallPackageAction;
|
||||
|
||||
@@ -1099,6 +1099,24 @@ namespace ts {
|
||||
return !seen[id] && (seen[id] = true);
|
||||
};
|
||||
}
|
||||
|
||||
/** Add a value to a set, and return true if it wasn't already present. */
|
||||
export function addToSeen(seen: Map<true>, key: string | number): boolean {
|
||||
key = String(key);
|
||||
if (seen.has(key)) {
|
||||
return false;
|
||||
}
|
||||
seen.set(key, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function singleElementArray<T>(t: T | undefined): T[] {
|
||||
return t === undefined ? undefined : [t];
|
||||
}
|
||||
|
||||
export function getFirstChildOfKind(node: Node, sourceFile: SourceFile, kind: SyntaxKind): Node | undefined {
|
||||
return find(node.getChildren(sourceFile), c => c.kind === kind);
|
||||
}
|
||||
}
|
||||
|
||||
// Display-part writer helpers
|
||||
@@ -1320,15 +1338,6 @@ namespace ts {
|
||||
return position;
|
||||
}
|
||||
|
||||
export function getOpenBrace(constructor: ConstructorDeclaration, sourceFile: SourceFile) {
|
||||
// First token is the open curly, this is where we want to put the 'super' call.
|
||||
return constructor.body.getFirstToken(sourceFile);
|
||||
}
|
||||
|
||||
export function getOpenBraceOfClassLike(declaration: ClassLikeDeclaration, sourceFile: SourceFile) {
|
||||
return getTokenAtPosition(sourceFile, declaration.members.pos - 1, /*includeJsDocComment*/ false);
|
||||
}
|
||||
|
||||
export function getSourceFileImportLocation({ text }: SourceFile) {
|
||||
const shebang = getShebang(text);
|
||||
let position = 0;
|
||||
|
||||
+10
-8
@@ -660,30 +660,30 @@ declare namespace ts {
|
||||
}
|
||||
interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement, JSDocContainer {
|
||||
kind: SyntaxKind.Constructor;
|
||||
parent?: ClassDeclaration | ClassExpression;
|
||||
parent?: ClassLikeDeclaration;
|
||||
body?: FunctionBody;
|
||||
}
|
||||
/** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */
|
||||
interface SemicolonClassElement extends ClassElement {
|
||||
kind: SyntaxKind.SemicolonClassElement;
|
||||
parent?: ClassDeclaration | ClassExpression;
|
||||
parent?: ClassLikeDeclaration;
|
||||
}
|
||||
interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer {
|
||||
kind: SyntaxKind.GetAccessor;
|
||||
parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression;
|
||||
parent?: ClassLikeDeclaration | ObjectLiteralExpression;
|
||||
name: PropertyName;
|
||||
body?: FunctionBody;
|
||||
}
|
||||
interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer {
|
||||
kind: SyntaxKind.SetAccessor;
|
||||
parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression;
|
||||
parent?: ClassLikeDeclaration | ObjectLiteralExpression;
|
||||
name: PropertyName;
|
||||
body?: FunctionBody;
|
||||
}
|
||||
type AccessorDeclaration = GetAccessorDeclaration | SetAccessorDeclaration;
|
||||
interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement {
|
||||
kind: SyntaxKind.IndexSignature;
|
||||
parent?: ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeLiteralNode;
|
||||
parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
|
||||
}
|
||||
interface TypeNode extends Node {
|
||||
_typeNodeBrand: any;
|
||||
@@ -1279,7 +1279,7 @@ declare namespace ts {
|
||||
}
|
||||
interface HeritageClause extends Node {
|
||||
kind: SyntaxKind.HeritageClause;
|
||||
parent?: InterfaceDeclaration | ClassDeclaration | ClassExpression;
|
||||
parent?: InterfaceDeclaration | ClassLikeDeclaration;
|
||||
token: SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword;
|
||||
types: NodeArray<ExpressionWithTypeArguments>;
|
||||
}
|
||||
@@ -3984,7 +3984,7 @@ declare namespace ts {
|
||||
getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion;
|
||||
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean;
|
||||
getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan;
|
||||
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[];
|
||||
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray<number>, formatOptions: FormatCodeSettings): ReadonlyArray<CodeAction>;
|
||||
applyCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
|
||||
applyCodeActionCommand(action: CodeActionCommand[]): Promise<ApplyCodeActionCommandResult[]>;
|
||||
applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>;
|
||||
@@ -5311,7 +5311,7 @@ declare namespace ts.server.protocol {
|
||||
/**
|
||||
* Errorcodes we want to get the fixes for.
|
||||
*/
|
||||
errorCodes?: number[];
|
||||
errorCodes?: ReadonlyArray<number>;
|
||||
}
|
||||
interface ApplyCodeActionCommandRequestArgs {
|
||||
/** May also be an array of commands. */
|
||||
@@ -7089,10 +7089,12 @@ declare namespace ts.server {
|
||||
private getApplicableRefactors(args);
|
||||
private getEditsForRefactor(args, simplifiedResult);
|
||||
private getCodeFixes(args, simplifiedResult);
|
||||
private getCombinedCodeFix({scope, fixId}, simplifiedResult);
|
||||
private applyCodeActionCommand(args);
|
||||
private getStartAndEndPosition(args, scriptInfo);
|
||||
private mapCodeAction({description, changes: unmappedChanges, commands}, scriptInfo);
|
||||
private mapTextChangesToCodeEdits(project, textChanges);
|
||||
private mapTextChangesToCodeEditsUsingScriptinfo(textChanges, scriptInfo);
|
||||
private convertTextChangeToCodeEdit(change, scriptInfo);
|
||||
private getBraceMatching(args, simplifiedResult);
|
||||
private getDiagnosticsForProject(next, delay, fileName);
|
||||
|
||||
+7
-7
@@ -660,30 +660,30 @@ declare namespace ts {
|
||||
}
|
||||
interface ConstructorDeclaration extends FunctionLikeDeclarationBase, ClassElement, JSDocContainer {
|
||||
kind: SyntaxKind.Constructor;
|
||||
parent?: ClassDeclaration | ClassExpression;
|
||||
parent?: ClassLikeDeclaration;
|
||||
body?: FunctionBody;
|
||||
}
|
||||
/** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */
|
||||
interface SemicolonClassElement extends ClassElement {
|
||||
kind: SyntaxKind.SemicolonClassElement;
|
||||
parent?: ClassDeclaration | ClassExpression;
|
||||
parent?: ClassLikeDeclaration;
|
||||
}
|
||||
interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer {
|
||||
kind: SyntaxKind.GetAccessor;
|
||||
parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression;
|
||||
parent?: ClassLikeDeclaration | ObjectLiteralExpression;
|
||||
name: PropertyName;
|
||||
body?: FunctionBody;
|
||||
}
|
||||
interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, ClassElement, ObjectLiteralElement, JSDocContainer {
|
||||
kind: SyntaxKind.SetAccessor;
|
||||
parent?: ClassDeclaration | ClassExpression | ObjectLiteralExpression;
|
||||
parent?: ClassLikeDeclaration | ObjectLiteralExpression;
|
||||
name: PropertyName;
|
||||
body?: FunctionBody;
|
||||
}
|
||||
type AccessorDeclaration = GetAccessorDeclaration | SetAccessorDeclaration;
|
||||
interface IndexSignatureDeclaration extends SignatureDeclarationBase, ClassElement, TypeElement {
|
||||
kind: SyntaxKind.IndexSignature;
|
||||
parent?: ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeLiteralNode;
|
||||
parent?: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode;
|
||||
}
|
||||
interface TypeNode extends Node {
|
||||
_typeNodeBrand: any;
|
||||
@@ -1279,7 +1279,7 @@ declare namespace ts {
|
||||
}
|
||||
interface HeritageClause extends Node {
|
||||
kind: SyntaxKind.HeritageClause;
|
||||
parent?: InterfaceDeclaration | ClassDeclaration | ClassExpression;
|
||||
parent?: InterfaceDeclaration | ClassLikeDeclaration;
|
||||
token: SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword;
|
||||
types: NodeArray<ExpressionWithTypeArguments>;
|
||||
}
|
||||
@@ -3984,7 +3984,7 @@ declare namespace ts {
|
||||
getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion;
|
||||
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean;
|
||||
getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan;
|
||||
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[];
|
||||
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray<number>, formatOptions: FormatCodeSettings): ReadonlyArray<CodeAction>;
|
||||
applyCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
|
||||
applyCodeActionCommand(action: CodeActionCommand[]): Promise<ApplyCodeActionCommandResult[]>;
|
||||
applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>;
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,23 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////declare function foo(): (...args: any[]) => void;
|
||||
////class C {
|
||||
//// @foo
|
||||
//// bar() {}
|
||||
////
|
||||
//// @foo
|
||||
//// baz() {}
|
||||
////}
|
||||
|
||||
verify.codeFixAll({
|
||||
fixId: "addMissingInvocationForDecorator",
|
||||
newFileContent:
|
||||
`declare function foo(): (...args: any[]) => void;
|
||||
class C {
|
||||
@foo()
|
||||
bar() {}
|
||||
|
||||
@foo()
|
||||
baz() {}
|
||||
}`
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////class C {
|
||||
//// method() {
|
||||
//// this.x = 0;
|
||||
//// this.y();
|
||||
//// this.x = "";
|
||||
//// }
|
||||
////}
|
||||
|
||||
verify.codeFixAll({
|
||||
fixId: "addMissingMember",
|
||||
newFileContent:
|
||||
// TODO: GH#18445
|
||||
`class C {
|
||||
x: number;\r
|
||||
y(): any {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
method() {
|
||||
this.x = 0;
|
||||
this.y();
|
||||
this.x = "";
|
||||
}
|
||||
}`,
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
|
||||
// @Filename: /a.js
|
||||
////class C {
|
||||
//// constructor() {}
|
||||
//// method() {
|
||||
//// this.x;
|
||||
//// this.y();
|
||||
//// this.x;
|
||||
//// }
|
||||
////}
|
||||
|
||||
verify.codeFixAll({
|
||||
fixId: "addMissingMember",
|
||||
newFileContent:
|
||||
// TODO: GH#18445 GH#20073
|
||||
`class C {
|
||||
y() {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
constructor() {this.x = undefined;\r
|
||||
}
|
||||
method() {
|
||||
this.x;
|
||||
this.y();
|
||||
this.x;
|
||||
}
|
||||
}`,
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @moduleResolution: node
|
||||
// @noImplicitAny: true
|
||||
|
||||
// @Filename: /node_modules/abs/index.js
|
||||
////not read
|
||||
|
||||
// @Filename: /node_modules/zap/index.js
|
||||
////not read
|
||||
|
||||
// @Filename: /a.ts
|
||||
/////**/import * as abs from "abs";
|
||||
////import * as zap from "zap";
|
||||
|
||||
test.setTypesRegistry({
|
||||
"abs": undefined,
|
||||
"zap": undefined,
|
||||
});
|
||||
|
||||
goTo.marker();
|
||||
|
||||
verify.codeFixAll({
|
||||
fixId: "fixCannotFindModule",
|
||||
commands: [
|
||||
{ packageName: "@types/abs", file: "/a.ts", type: "install package" },
|
||||
{ packageName: "@types/zap", file: "/a.ts", type: "install package" },
|
||||
],
|
||||
newFileContent: `import * as abs from "abs";
|
||||
import * as zap from "zap";` // unchanged
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @strict: true
|
||||
////function f(a: ?number, b: string!) {}
|
||||
|
||||
verify.codeFixAll({
|
||||
fixId: "fixJSDocTypes_plain",
|
||||
newFileContent: "function f(a: number | null, b: string) {}",
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @strict: true
|
||||
////function f(a: ?number, b: string!) {}
|
||||
|
||||
verify.codeFixAll({
|
||||
fixId: "fixJSDocTypes_nullable",
|
||||
newFileContent: "function f(a: number | null | undefined, b: string) {}",
|
||||
})
|
||||
@@ -3,14 +3,18 @@
|
||||
////class A {
|
||||
//// f() {}
|
||||
////}
|
||||
////
|
||||
////let B = class implements A {[| |]}
|
||||
////let B = class implements A {}
|
||||
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'A'",
|
||||
// TODO: GH#18795
|
||||
newRangeContent: `f(): void {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
`
|
||||
newFileContent:
|
||||
`class A {
|
||||
f() {}
|
||||
}
|
||||
let B = class implements A {\r
|
||||
f(): void {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`
|
||||
});
|
||||
@@ -7,11 +7,20 @@
|
||||
//// return C;
|
||||
////}
|
||||
////
|
||||
////let B = class extends foo("s")<number> {[| |]}
|
||||
////let B = class extends foo("s")<number> {}
|
||||
|
||||
verify.codeFix({
|
||||
description: "Implement inherited abstract class",
|
||||
// TODO: GH#18795
|
||||
newRangeContent: `a: string | number;\r
|
||||
`
|
||||
newFileContent:
|
||||
`function foo<T>(a: T) {
|
||||
abstract class C<U> {
|
||||
abstract a: T | U;
|
||||
}
|
||||
return C;
|
||||
}
|
||||
|
||||
let B = class extends foo("s")<number> {\r
|
||||
a: string | number;\r
|
||||
}`
|
||||
});
|
||||
@@ -12,6 +12,15 @@
|
||||
verify.codeFix({
|
||||
description: "Implement inherited abstract class",
|
||||
// TODO: GH#18795
|
||||
newRangeContent: `a: string | number;\r
|
||||
`
|
||||
newFileContent:
|
||||
`function foo<T>(a: T) {
|
||||
abstract class C<U> {
|
||||
abstract a: T | U;
|
||||
}
|
||||
return C;
|
||||
}
|
||||
|
||||
class B extends foo("s")<number> {\r
|
||||
a: string | number;\r
|
||||
}`
|
||||
});
|
||||
@@ -18,17 +18,37 @@
|
||||
////// Don't need to add anything in this case.
|
||||
////abstract class B extends A {}
|
||||
////
|
||||
////class C extends A {[| |]}
|
||||
////class C extends A {}
|
||||
|
||||
verify.codeFix({
|
||||
description: "Implement inherited abstract class",
|
||||
// TODO: GH#18795
|
||||
newRangeContent: `a: string | number;\r
|
||||
b: this;\r
|
||||
c: A;\r
|
||||
d: string | number;\r
|
||||
e: this;\r
|
||||
f: A;\r
|
||||
g: string;\r
|
||||
`
|
||||
newFileContent:
|
||||
`abstract class A {
|
||||
private _a: string;
|
||||
|
||||
abstract get a(): number | string;
|
||||
abstract get b(): this;
|
||||
abstract get c(): A;
|
||||
|
||||
abstract set d(arg: number | string);
|
||||
abstract set e(arg: this);
|
||||
abstract set f(arg: A);
|
||||
|
||||
abstract get g(): string;
|
||||
abstract set g(newName: string);
|
||||
}
|
||||
|
||||
// Don't need to add anything in this case.
|
||||
abstract class B extends A {}
|
||||
|
||||
class C extends A {\r
|
||||
a: string | number;\r
|
||||
b: this;\r
|
||||
c: A;\r
|
||||
d: string | number;\r
|
||||
e: this;\r
|
||||
f: A;\r
|
||||
g: string;\r
|
||||
}`
|
||||
});
|
||||
@@ -8,20 +8,30 @@
|
||||
//// abstract foo(): number;
|
||||
////}
|
||||
////
|
||||
////class C extends A {[| |]}
|
||||
////class C extends A {}
|
||||
|
||||
verify.codeFix({
|
||||
description: "Implement inherited abstract class",
|
||||
// TODO: GH#18795
|
||||
newRangeContent: `f(a: number, b: string): boolean;\r
|
||||
f(a: number, b: string): this;\r
|
||||
f(a: string, b: number): Function;\r
|
||||
f(a: string): Function;\r
|
||||
f(a: any, b?: any) {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
foo(): number {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
`
|
||||
newFileContent:
|
||||
`abstract class A {
|
||||
abstract f(a: number, b: string): boolean;
|
||||
abstract f(a: number, b: string): this;
|
||||
abstract f(a: string, b: number): Function;
|
||||
abstract f(a: string): Function;
|
||||
abstract foo(): number;
|
||||
}
|
||||
|
||||
class C extends A {\r
|
||||
f(a: number, b: string): boolean;\r
|
||||
f(a: number, b: string): this;\r
|
||||
f(a: string, b: number): Function;\r
|
||||
f(a: string): Function;\r
|
||||
f(a: any, b?: any) {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
foo(): number {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`
|
||||
});
|
||||
@@ -4,13 +4,19 @@
|
||||
//// abstract f(): this;
|
||||
////}
|
||||
////
|
||||
////class C extends A {[| |]}
|
||||
////class C extends A {}
|
||||
|
||||
verify.codeFix({
|
||||
description: "Implement inherited abstract class",
|
||||
// TODO: GH#18795
|
||||
newRangeContent: `f(): this {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
`
|
||||
newFileContent:
|
||||
`abstract class A {
|
||||
abstract f(): this;
|
||||
}
|
||||
|
||||
class C extends A {\r
|
||||
f(): this {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`
|
||||
});
|
||||
+11
-5
@@ -4,13 +4,19 @@
|
||||
//// abstract f(x: T): T;
|
||||
////}
|
||||
////
|
||||
////class C extends A<number> {[| |]}
|
||||
////class C extends A<number> {}
|
||||
|
||||
verify.codeFix({
|
||||
description: "Implement inherited abstract class",
|
||||
// TODO: GH#18795
|
||||
newRangeContent: `f(x: number): number {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
`
|
||||
newFileContent:
|
||||
`abstract class A<T> {
|
||||
abstract f(x: T): T;
|
||||
}
|
||||
|
||||
class C extends A<number> {\r
|
||||
f(x: number): number {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`
|
||||
});
|
||||
@@ -4,13 +4,19 @@
|
||||
//// abstract f(x: T): T;
|
||||
////}
|
||||
////
|
||||
////class C<U> extends A<U> {[| |]}
|
||||
////class C<U> extends A<U> {}
|
||||
|
||||
verify.codeFix({
|
||||
description: "Implement inherited abstract class",
|
||||
// TODO: GH#18795
|
||||
newRangeContent: `f(x: U): U {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
`
|
||||
newFileContent:
|
||||
`abstract class A<T> {
|
||||
abstract f(x: T): T;
|
||||
}
|
||||
|
||||
class C<U> extends A<U> {\r
|
||||
f(x: U): U {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////abstract class A {
|
||||
//// abstract m(): void;
|
||||
////}
|
||||
////class B extends A {}
|
||||
////class C extends A {}
|
||||
|
||||
verify.codeFixAll({
|
||||
fixId: "fixClassDoesntImplementInheritedAbstractMember",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`abstract class A {
|
||||
abstract m(): void;
|
||||
}
|
||||
class B extends A {\r
|
||||
m(): void {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}
|
||||
class C extends A {\r
|
||||
m(): void {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -6,13 +6,21 @@
|
||||
//// abstract z: A;
|
||||
////}
|
||||
////
|
||||
////class C extends A {[| |]}
|
||||
////class C extends A {}
|
||||
|
||||
verify.codeFix({
|
||||
description: "Implement inherited abstract class",
|
||||
// TODO: GH#18795
|
||||
newRangeContent: `x: number;\r
|
||||
y: this;\r
|
||||
z: A;\r
|
||||
`
|
||||
newFileContent:
|
||||
`abstract class A {
|
||||
abstract x: number;
|
||||
abstract y: this;
|
||||
abstract z: A;
|
||||
}
|
||||
|
||||
class C extends A {\r
|
||||
x: number;\r
|
||||
y: this;\r
|
||||
z: A;\r
|
||||
}`
|
||||
});
|
||||
@@ -1,11 +1,20 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// abstract class A {
|
||||
////abstract class A {
|
||||
//// abstract x: this;
|
||||
//// }
|
||||
////}
|
||||
////
|
||||
//// class C extends A {[| |]}
|
||||
////class C extends A {[| |]}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
x: this;
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement inherited abstract class",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`abstract class A {
|
||||
abstract x: this;
|
||||
}
|
||||
|
||||
class C extends A {\r
|
||||
x: this;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,11 +1,20 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// abstract class A {
|
||||
////abstract class A {
|
||||
//// protected abstract x: number;
|
||||
//// }
|
||||
////}
|
||||
////
|
||||
//// class C extends A {[| |]}
|
||||
////class C extends A {[| |]}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
protected x: number;
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement inherited abstract class",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`abstract class A {
|
||||
protected abstract x: number;
|
||||
}
|
||||
|
||||
class C extends A {\r
|
||||
protected x: number;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,12 +1,20 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// abstract class A {
|
||||
////abstract class A {
|
||||
//// public abstract x: number;
|
||||
//// }
|
||||
////}
|
||||
////
|
||||
//// class C extends A {[| |]}
|
||||
////class C extends A {[| |]}
|
||||
|
||||
verify.codeFix({
|
||||
description: "Implement inherited abstract class",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`abstract class A {
|
||||
public abstract x: number;
|
||||
}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
public x: number;
|
||||
`);
|
||||
class C extends A {\r
|
||||
public x: number;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,20 +1,36 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// abstract class A {
|
||||
//// private _a: string;
|
||||
////
|
||||
//// abstract get a(): string;
|
||||
//// abstract set a(newName: string);
|
||||
////
|
||||
//// abstract get b(): number;
|
||||
////
|
||||
//// abstract set c(arg: number | string);
|
||||
//// }
|
||||
////abstract class A {
|
||||
//// private _a: string;
|
||||
////
|
||||
//// class C implements A {[| |]}
|
||||
//// abstract get a(): string;
|
||||
//// abstract set a(newName: string);
|
||||
////
|
||||
//// abstract get b(): number;
|
||||
////
|
||||
//// abstract set c(arg: number | string);
|
||||
////}
|
||||
////
|
||||
////class C implements A {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
a: string;
|
||||
b: number;
|
||||
c: string | number;
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'A'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`abstract class A {
|
||||
private _a: string;
|
||||
|
||||
abstract get a(): string;
|
||||
abstract set a(newName: string);
|
||||
|
||||
abstract get b(): number;
|
||||
|
||||
abstract set c(arg: number | string);
|
||||
}
|
||||
|
||||
class C implements A {\r
|
||||
a: string;\r
|
||||
b: number;\r
|
||||
c: string | number;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,13 +1,22 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// class A {
|
||||
//// f() {}
|
||||
//// }
|
||||
////class A {
|
||||
//// f() {}
|
||||
////}
|
||||
////
|
||||
//// class B implements A {[| |]}
|
||||
////class B implements A {[| |]}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
f(): void{
|
||||
throw new Error("Method not implemented.");
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'A'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`class A {
|
||||
f() {}
|
||||
}
|
||||
`);
|
||||
|
||||
class B implements A {\r
|
||||
f(): void {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,14 +1,23 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// class A {
|
||||
//// method(a: number, b: string): boolean;
|
||||
//// method(a: string | number, b?: string | number): boolean | Function { return true; }
|
||||
////
|
||||
//// class C implements A {[| |]}
|
||||
////class A {
|
||||
//// method(a: number, b: string): boolean;
|
||||
//// method(a: string | number, b?: string | number): boolean | Function { return true; }
|
||||
////}
|
||||
////class C implements A {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'A'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`class A {
|
||||
method(a: number, b: string): boolean;
|
||||
method(a: string | number, b?: string | number): boolean | Function {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
`);
|
||||
method(a: string | number, b?: string | number): boolean | Function { return true; }
|
||||
}
|
||||
class C implements A {\r
|
||||
method(a: number, b: string): boolean;\r
|
||||
method(a: string | number, b?: string | number): boolean | Function {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,18 +1,29 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// class A {
|
||||
//// method(a: any, b: string): boolean;
|
||||
//// method(a: string, b: number): Function;
|
||||
//// method(a: string): Function;
|
||||
//// method(a: string | number, b?: string | number): boolean | Function { return true; }
|
||||
////
|
||||
//// class C implements A {[| |]}
|
||||
////class A {
|
||||
//// method(a: any, b: string): boolean;
|
||||
//// method(a: string, b: number): Function;
|
||||
//// method(a: string): Function;
|
||||
//// method(a: string | number, b?: string | number): boolean | Function { return true; }
|
||||
////}
|
||||
////class C implements A {[| |]}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'A'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`class A {
|
||||
method(a: any, b: string): boolean;
|
||||
method(a: string, b: number): Function;
|
||||
method(a: string): Function;
|
||||
method(a: string | number, b?: string | number): boolean | Function {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
`);
|
||||
method(a: string | number, b?: string | number): boolean | Function { return true; }
|
||||
}
|
||||
class C implements A {\r
|
||||
method(a: any, b: string): boolean;\r
|
||||
method(a: string, b: number): Function;\r
|
||||
method(a: string): Function;\r
|
||||
method(a: string | number, b?: string | number): boolean | Function {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,16 +1,28 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// abstract class A {
|
||||
//// abstract x: number;
|
||||
//// private y: number;
|
||||
//// protected z: number;
|
||||
//// public w: number;
|
||||
//// }
|
||||
////abstract class A {
|
||||
//// abstract x: number;
|
||||
//// private y: number;
|
||||
//// protected z: number;
|
||||
//// public w: number;
|
||||
////}
|
||||
////
|
||||
//// class C implements A {[| |]}
|
||||
////class C implements A {[| |]}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
x: number;
|
||||
protected z: number;
|
||||
public w: number;
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'A'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`abstract class A {
|
||||
abstract x: number;
|
||||
private y: number;
|
||||
protected z: number;
|
||||
public w: number;
|
||||
}
|
||||
|
||||
class C implements A {\r
|
||||
x: number;\r
|
||||
protected z: number;\r
|
||||
public w: number;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,10 +1,18 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// class A {
|
||||
//// A: typeof A;
|
||||
//// }
|
||||
//// class D implements A {[| |]}
|
||||
////class A {
|
||||
//// A: typeof A;
|
||||
////}
|
||||
////class D implements A {[| |]}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'A'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`class A {
|
||||
A: typeof A;
|
||||
`);
|
||||
}
|
||||
class D implements A {\r
|
||||
A: typeof A;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,39 +1,37 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
|
||||
//// // Referenced throughout the inheritance chain.
|
||||
//// interface I0 { a: number }
|
||||
////
|
||||
//// class C1 implements I0 { a: number }
|
||||
//// interface I1 { b: number }
|
||||
//// interface I2 extends C1, I1 {}
|
||||
////
|
||||
//// class C2 { c: number }
|
||||
//// interface I3 {d: number}
|
||||
//// class C3 extends C2 implements I0, I2, I3 {
|
||||
//// a: number;
|
||||
//// b: number;
|
||||
//// d: number;
|
||||
//// }
|
||||
////
|
||||
//// interface I4 { e: number }
|
||||
//// interface I5 { f: number }
|
||||
//// class C4 extends C3 implements I0, I4, I5 {
|
||||
//// e: number;
|
||||
//// f: number;
|
||||
//// }
|
||||
////
|
||||
//// interface I6 extends C4 {}
|
||||
//// class C5 implements I6 {[| |]}
|
||||
|
||||
////// Referenced throughout the inheritance chain.
|
||||
////interface I0 { a: number }
|
||||
////
|
||||
////class C1 implements I0 { a: number }
|
||||
////interface I1 { b: number }
|
||||
////interface I2 extends C1, I1 {}
|
||||
////
|
||||
////class C2 { c: number }
|
||||
////interface I3 {d: number}
|
||||
////class C3 extends C2 implements I0, I2, I3 {
|
||||
//// a: number;
|
||||
//// b: number;
|
||||
//// d: number;
|
||||
////}
|
||||
////
|
||||
////interface I4 { e: number }
|
||||
////interface I5 { f: number }
|
||||
////class C4 extends C3 implements I0, I4, I5 {
|
||||
//// e: number;
|
||||
//// f: number;
|
||||
////}
|
||||
////
|
||||
////interface I6 extends C4 {}
|
||||
////class C5 implements I6 {}
|
||||
|
||||
/**
|
||||
* We want to check whether the search for member to replace actually searches through
|
||||
* the various possible paths of the inheritance chain correctly, and that We
|
||||
* don't issue duplicates for the same member.
|
||||
*
|
||||
*
|
||||
* Our class DAG:
|
||||
*
|
||||
*
|
||||
* C5
|
||||
* |-I6
|
||||
* |-C4
|
||||
@@ -50,13 +48,39 @@
|
||||
* |-C2
|
||||
*/
|
||||
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I6'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`// Referenced throughout the inheritance chain.
|
||||
interface I0 { a: number }
|
||||
|
||||
verify.rangeAfterCodeFix(
|
||||
`
|
||||
e: number;
|
||||
f: number;
|
||||
a: number;
|
||||
b: number;
|
||||
d: number;
|
||||
c: number;
|
||||
`);
|
||||
class C1 implements I0 { a: number }
|
||||
interface I1 { b: number }
|
||||
interface I2 extends C1, I1 {}
|
||||
|
||||
class C2 { c: number }
|
||||
interface I3 {d: number}
|
||||
class C3 extends C2 implements I0, I2, I3 {
|
||||
a: number;
|
||||
b: number;
|
||||
d: number;
|
||||
}
|
||||
|
||||
interface I4 { e: number }
|
||||
interface I5 { f: number }
|
||||
class C4 extends C3 implements I0, I4, I5 {
|
||||
e: number;
|
||||
f: number;
|
||||
}
|
||||
|
||||
interface I6 extends C4 {}
|
||||
class C5 implements I6 {\r
|
||||
e: number;\r
|
||||
f: number;\r
|
||||
a: number;\r
|
||||
b: number;\r
|
||||
d: number;\r
|
||||
c: number;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,9 +1,14 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I { x: number; }
|
||||
////
|
||||
//// export default class implements I {[| |]}
|
||||
////interface I { x: number; }
|
||||
////export default class implements I {[| |]}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
x: number;
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I { x: number; }
|
||||
export default class implements I {\r
|
||||
x: number;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,15 +1,26 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I {
|
||||
//// x: number[];
|
||||
//// y: Array<number>;
|
||||
//// z: [number, string, I];
|
||||
//// }
|
||||
////interface I {
|
||||
//// x: number[];
|
||||
//// y: Array<number>;
|
||||
//// z: [number, string, I];
|
||||
////}
|
||||
////
|
||||
//// class C implements I {[| |]}
|
||||
////class C implements I {[| |]}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
x: number[];
|
||||
y: number[];
|
||||
z: [number, string, I];
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I {
|
||||
x: number[];
|
||||
y: Array<number>;
|
||||
z: [number, string, I];
|
||||
}
|
||||
|
||||
class C implements I {\r
|
||||
x: number[];\r
|
||||
y: number[];\r
|
||||
z: [number, string, I];\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,10 +1,15 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
|
||||
//// interface I { x: number; }
|
||||
////
|
||||
//// new class implements I {[| |]};
|
||||
////interface I { x: number; }
|
||||
////new class implements I {};
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
x: number;
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I { x: number; }
|
||||
new class implements I {\r
|
||||
x: number;\r
|
||||
};`,
|
||||
});
|
||||
@@ -2,26 +2,45 @@
|
||||
|
||||
// @lib: es2017
|
||||
|
||||
//// namespace N {
|
||||
//// /**enum prefix */
|
||||
//// export enum /**enum identifier prefix */ E /**open-brace prefix*/ {
|
||||
//// /* literal prefix */ a /** comma prefix */,
|
||||
//// /* literal prefix */ b /** comma prefix */,
|
||||
//// /* literal prefix */ c
|
||||
//// /** close brace prefix */ }
|
||||
//// /** interface prefix */
|
||||
//// export interface /**interface name prefix */ I /**open-brace prefix*/ {
|
||||
//// /** property prefix */ a /** colon prefix */: /** enum literal prefix 1*/ E /** dot prefix */. /** enum literal prefix 2*/a;
|
||||
//// /** property prefix */ b /** colon prefix */: /** enum prefix */ E;
|
||||
//// /**method signature prefix */foo /**open angle prefix */< /**type parameter name prefix */ X /** closing angle prefix */> /**open paren prefix */(/** parameter prefix */ a/** colon prefix */: /** parameter type prefix */ X /** close paren prefix */) /** colon prefix */: /** return type prefix */ string /** semicolon prefix */;
|
||||
//// /**close-brace prefix*/ }
|
||||
//// /**close-brace prefix*/ }
|
||||
//// class C implements N.I {[| |]}
|
||||
////namespace N {
|
||||
//// /**enum prefix */
|
||||
//// export enum /**enum identifier prefix */ E /**open-brace prefix*/ {
|
||||
//// /* literal prefix */ a /** comma prefix */,
|
||||
//// /* literal prefix */ b /** comma prefix */,
|
||||
//// /* literal prefix */ c
|
||||
//// /** close brace prefix */ }
|
||||
//// /** interface prefix */
|
||||
//// export interface /**interface name prefix */ I /**open-brace prefix*/ {
|
||||
//// /** property prefix */ a /** colon prefix */: /** enum literal prefix 1*/ E /** dot prefix */. /** enum literal prefix 2*/a;
|
||||
//// /** property prefix */ b /** colon prefix */: /** enum prefix */ E;
|
||||
//// /**method signature prefix */foo /**open angle prefix */< /**type parameter name prefix */ X /** closing angle prefix */> /**open paren prefix */(/** parameter prefix */ a/** colon prefix */: /** parameter type prefix */ X /** close paren prefix */) /** colon prefix */: /** return type prefix */ string /** semicolon prefix */;
|
||||
//// /**close-brace prefix*/ }
|
||||
/////**close-brace prefix*/ }
|
||||
////class C implements N.I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
a: N.E.a;
|
||||
b: N.E;
|
||||
foo<X>(a: X): string {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'N.I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`namespace N {
|
||||
/**enum prefix */
|
||||
export enum /**enum identifier prefix */ E /**open-brace prefix*/ {
|
||||
/* literal prefix */ a /** comma prefix */,
|
||||
/* literal prefix */ b /** comma prefix */,
|
||||
/* literal prefix */ c
|
||||
/** close brace prefix */ }
|
||||
/** interface prefix */
|
||||
export interface /**interface name prefix */ I /**open-brace prefix*/ {
|
||||
/** property prefix */ a /** colon prefix */: /** enum literal prefix 1*/ E /** dot prefix */. /** enum literal prefix 2*/a;
|
||||
/** property prefix */ b /** colon prefix */: /** enum prefix */ E;
|
||||
/**method signature prefix */foo /**open angle prefix */< /**type parameter name prefix */ X /** closing angle prefix */> /**open paren prefix */(/** parameter prefix */ a/** colon prefix */: /** parameter type prefix */ X /** close paren prefix */) /** colon prefix */: /** return type prefix */ string /** semicolon prefix */;
|
||||
/**close-brace prefix*/ }
|
||||
/**close-brace prefix*/ }
|
||||
class C implements N.I {\r
|
||||
/** property prefix */ a /** colon prefix */: N.E.a;\r
|
||||
/** property prefix */ b /** colon prefix */: N.E;\r
|
||||
/**method signature prefix */ foo /**open angle prefix */<X>(a: X): string {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,21 +1,33 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I {
|
||||
//// ["foo"](o: any): boolean;
|
||||
//// ["x"]: boolean;
|
||||
//// [1](): string;
|
||||
//// [2]: boolean;
|
||||
//// }
|
||||
////interface I {
|
||||
//// ["foo"](o: any): boolean;
|
||||
//// ["x"]: boolean;
|
||||
//// [1](): string;
|
||||
//// [2]: boolean;
|
||||
////}
|
||||
////
|
||||
//// class C implements I {[| |]}
|
||||
////class C implements I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
["foo"](o: any): boolean {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I {
|
||||
["foo"](o: any): boolean;
|
||||
["x"]: boolean;
|
||||
[1](): string {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
[1](): string;
|
||||
[2]: boolean;
|
||||
`);
|
||||
}
|
||||
|
||||
class C implements I {\r
|
||||
["foo"](o: any): boolean {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
["x"]: boolean;\r
|
||||
[1](): string {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
[2]: boolean;\r
|
||||
}`,
|
||||
});
|
||||
+59
-39
@@ -2,50 +2,70 @@
|
||||
|
||||
// @lib: es2017
|
||||
|
||||
//// interface I<Species> {
|
||||
//// [Symbol.hasInstance](o: any): boolean;
|
||||
//// [Symbol.isConcatSpreadable]: boolean;
|
||||
//// [Symbol.iterator](): any;
|
||||
//// [Symbol.match]: boolean;
|
||||
//// [Symbol.replace](...args);
|
||||
//// [Symbol.search](str: string): number;
|
||||
//// [Symbol.species](): Species;
|
||||
//// [Symbol.split](str: string, limit?: number): string[];
|
||||
//// [Symbol.toPrimitive](hint: "number"): number;
|
||||
//// [Symbol.toPrimitive](hint: "default"): number;
|
||||
//// [Symbol.toPrimitive](hint: "string"): string;
|
||||
//// [Symbol.toStringTag]: string;
|
||||
//// [Symbol.unscopables]: any;
|
||||
//// }
|
||||
//// class C implements I<number> {[| |]}
|
||||
////interface I<Species> {
|
||||
//// [Symbol.hasInstance](o: any): boolean;
|
||||
//// [Symbol.isConcatSpreadable]: boolean;
|
||||
//// [Symbol.iterator](): any;
|
||||
//// [Symbol.match]: boolean;
|
||||
//// [Symbol.replace](...args);
|
||||
//// [Symbol.search](str: string): number;
|
||||
//// [Symbol.species](): Species;
|
||||
//// [Symbol.split](str: string, limit?: number): string[];
|
||||
//// [Symbol.toPrimitive](hint: "number"): number;
|
||||
//// [Symbol.toPrimitive](hint: "default"): number;
|
||||
//// [Symbol.toPrimitive](hint: "string"): string;
|
||||
//// [Symbol.toStringTag]: string;
|
||||
//// [Symbol.unscopables]: any;
|
||||
////}
|
||||
////class C implements I<number> {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
[Symbol.hasInstance](o: any): boolean {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I<number>'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I<Species> {
|
||||
[Symbol.hasInstance](o: any): boolean;
|
||||
[Symbol.isConcatSpreadable]: boolean;
|
||||
[Symbol.iterator]() {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
[Symbol.iterator](): any;
|
||||
[Symbol.match]: boolean;
|
||||
[Symbol.replace](...args: {}) {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
[Symbol.search](str: string): number {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
[Symbol.species](): number {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
[Symbol.split](str: string, limit?: number): {} {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
[Symbol.replace](...args);
|
||||
[Symbol.search](str: string): number;
|
||||
[Symbol.species](): Species;
|
||||
[Symbol.split](str: string, limit?: number): string[];
|
||||
[Symbol.toPrimitive](hint: "number"): number;
|
||||
[Symbol.toPrimitive](hint: "default"): number;
|
||||
[Symbol.toPrimitive](hint: "string"): string;
|
||||
[Symbol.toPrimitive](hint: any) {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
[Symbol.toStringTag]: string;
|
||||
[Symbol.unscopables]: any;
|
||||
`);
|
||||
}
|
||||
class C implements I<number> {\r
|
||||
[Symbol.hasInstance](o: any): boolean {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
[Symbol.isConcatSpreadable]: boolean;\r
|
||||
[Symbol.iterator]() {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
[Symbol.match]: boolean;\r
|
||||
[Symbol.replace](...args: {}) {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
[Symbol.search](str: string): number {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
[Symbol.species](): number {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
[Symbol.split](str: string, limit?: number): {} {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
[Symbol.toPrimitive](hint: "number"): number;\r
|
||||
[Symbol.toPrimitive](hint: "default"): number;\r
|
||||
[Symbol.toPrimitive](hint: "string"): string;\r
|
||||
[Symbol.toPrimitive](hint: any) {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
[Symbol.toStringTag]: string\;\r
|
||||
[Symbol.unscopables]: any;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,17 +1,32 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// namespace N1 {
|
||||
//// export interface I1 {
|
||||
//// f1():string;
|
||||
//// }
|
||||
//// }
|
||||
//// interface I1 {
|
||||
//// f1();
|
||||
//// }
|
||||
////namespace N1 {
|
||||
//// export interface I1 {
|
||||
//// f1():string;
|
||||
//// }
|
||||
////}
|
||||
////interface I1 {
|
||||
//// f1();
|
||||
////}
|
||||
////
|
||||
//// class C1 implements N1.I1 {[| |]}
|
||||
////class C1 implements N1.I1 {}
|
||||
|
||||
verify.rangeAfterCodeFix(`f1(): string{
|
||||
throw new Error("Method not implemented.");
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'N1.I1'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`namespace N1 {
|
||||
export interface I1 {
|
||||
f1():string;
|
||||
}
|
||||
}
|
||||
`);
|
||||
interface I1 {
|
||||
f1();
|
||||
}
|
||||
|
||||
class C1 implements N1.I1 {\r
|
||||
f1(): string {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,14 +1,24 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
|
||||
//// interface I {
|
||||
//// [x: number]: I;
|
||||
//// [y: string]: I;
|
||||
//// }
|
||||
////interface I {
|
||||
//// [x: number]: I;
|
||||
//// [y: string]: I;
|
||||
////}
|
||||
////
|
||||
//// class C implements I {[| |]}
|
||||
////class C implements I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I {
|
||||
[x: number]: I;
|
||||
[y: string]: I;
|
||||
`);
|
||||
}
|
||||
|
||||
class C implements I {\r
|
||||
[x: number]: I;\r
|
||||
[y: string]: I;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,10 +1,18 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I {
|
||||
//// [x: number]: I;
|
||||
//// }
|
||||
//// class C implements I {[| |]}
|
||||
////interface I {
|
||||
//// [x: number]: I;
|
||||
////}
|
||||
////class C implements I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I {
|
||||
[x: number]: I;
|
||||
`);
|
||||
}
|
||||
class C implements I {\r
|
||||
[x: number]: I;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,11 +1,20 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I<X> {
|
||||
//// [Ƚ: string]: X;
|
||||
//// }
|
||||
////interface I<X> {
|
||||
//// [Ƚ: string]: X;
|
||||
////}
|
||||
////
|
||||
//// class C implements I<number> {[| |]}
|
||||
////class C implements I<number> {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
[Ƚ: string]: number;
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I<number>'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I<X> {
|
||||
[Ƚ: string]: X;
|
||||
}
|
||||
|
||||
class C implements I<number> {\r
|
||||
[Ƚ: string]: number;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,10 +1,18 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I<X> {
|
||||
//// x: keyof X;
|
||||
//// }
|
||||
//// class C<Y> implements I<Y> {[| |]}
|
||||
////interface I<X> {
|
||||
//// x: keyof X;
|
||||
////}
|
||||
////class C<Y> implements I<Y> {[| |]}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
x: keyof Y;
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I<Y>'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I<X> {
|
||||
x: keyof X;
|
||||
}
|
||||
class C<Y> implements I<Y> {\r
|
||||
x: keyof Y;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,13 +1,24 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// abstract class C1 { }
|
||||
//// abstract class C2 {
|
||||
//// abstract fA<T extends number>();
|
||||
//// }
|
||||
//// interface I1 extends C1, C2 { }
|
||||
//// class C3 implements I1 {[| |]}
|
||||
////abstract class C1 { }
|
||||
////abstract class C2 {
|
||||
//// abstract fA<T extends number>();
|
||||
////}
|
||||
////interface I1 extends C1, C2 { }
|
||||
////class C3 implements I1 {[| |]}
|
||||
|
||||
verify.rangeAfterCodeFix(`fA<T extends number>(){
|
||||
throw new Error("Method not implemented.");
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I1'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`abstract class C1 { }
|
||||
abstract class C2 {
|
||||
abstract fA<T extends number>();
|
||||
}
|
||||
`);
|
||||
interface I1 extends C1, C2 { }
|
||||
class C3 implements I1 {\r
|
||||
fA<T extends number>() {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,10 +1,18 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I<X> {
|
||||
//// x: { readonly [K in keyof X]: X[K] };
|
||||
//// }
|
||||
//// class C<Y> implements I<Y> {[| |]}
|
||||
////interface I<X> {
|
||||
//// x: { readonly [K in keyof X]: X[K] };
|
||||
////}
|
||||
////class C<Y> implements I<Y> {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
x: { readonly [K in keyof X]: Y[K]; };
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I<Y>'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I<X> {
|
||||
x: { readonly [K in keyof X]: X[K] };
|
||||
}
|
||||
class C<Y> implements I<Y> {\r
|
||||
x: { readonly [K in keyof X]: Y[K]; };\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,16 +1,25 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// type Either<T> = { val: T } | Error;
|
||||
//// interface I {
|
||||
//// x: Either<Either<string>>;
|
||||
//// foo(x: Either<Either<string>>): void;
|
||||
//// }
|
||||
//// class C implements I {[| |]}
|
||||
////type Either<T> = { val: T } | Error;
|
||||
////interface I {
|
||||
//// x: Either<Either<string>>;
|
||||
//// foo(x: Either<Either<string>>): void;
|
||||
////}
|
||||
////class C implements I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`type Either<T> = { val: T } | Error;
|
||||
interface I {
|
||||
x: Either<Either<string>>;
|
||||
foo(x: Either<Either<string>>): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
`);
|
||||
|
||||
foo(x: Either<Either<string>>): void;
|
||||
}
|
||||
class C implements I {\r
|
||||
x: Either<Either<string>>;\r
|
||||
foo(x: Either<Either<string>>): void {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -2,57 +2,90 @@
|
||||
|
||||
// @lib: es2017
|
||||
|
||||
//// /** asdf */
|
||||
//// interface I {
|
||||
//// 1;
|
||||
//// 2;
|
||||
//// 3;
|
||||
//// 4;
|
||||
//// 5;
|
||||
//// 6;
|
||||
//// 7;
|
||||
//// 8;
|
||||
//// 9;
|
||||
//// 10;
|
||||
//// 11;
|
||||
//// 12;
|
||||
//// 13;
|
||||
//// 14;
|
||||
//// 15;
|
||||
//// 16;
|
||||
//// 17;
|
||||
//// 18;
|
||||
//// 19;
|
||||
//// 20;
|
||||
//// 21;
|
||||
//// 22;
|
||||
//// /** a nice safe prime */
|
||||
//// 23;
|
||||
//// }
|
||||
//// class C implements I {[| |]}
|
||||
/////** asdf */
|
||||
////interface I {
|
||||
//// 1;
|
||||
//// 2;
|
||||
//// 3;
|
||||
//// 4;
|
||||
//// 5;
|
||||
//// 6;
|
||||
//// 7;
|
||||
//// 8;
|
||||
//// 9;
|
||||
//// 10;
|
||||
//// 11;
|
||||
//// 12;
|
||||
//// 13;
|
||||
//// 14;
|
||||
//// 15;
|
||||
//// 16;
|
||||
//// 17;
|
||||
//// 18;
|
||||
//// 19;
|
||||
//// 20;
|
||||
//// 21;
|
||||
//// 22;
|
||||
//// /** a nice safe prime */
|
||||
//// 23;
|
||||
////}
|
||||
////class C implements I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
1: any;
|
||||
2: any;
|
||||
3: any;
|
||||
4: any;
|
||||
5: any;
|
||||
6: any;
|
||||
7: any;
|
||||
8: any;
|
||||
9: any;
|
||||
10: any;
|
||||
11: any;
|
||||
12: any;
|
||||
13: any;
|
||||
14: any;
|
||||
15: any;
|
||||
16: any;
|
||||
17: any;
|
||||
18: any;
|
||||
19: any;
|
||||
20: any;
|
||||
21: any;
|
||||
22: any;
|
||||
23: any;
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`/** asdf */
|
||||
interface I {
|
||||
1;
|
||||
2;
|
||||
3;
|
||||
4;
|
||||
5;
|
||||
6;
|
||||
7;
|
||||
8;
|
||||
9;
|
||||
10;
|
||||
11;
|
||||
12;
|
||||
13;
|
||||
14;
|
||||
15;
|
||||
16;
|
||||
17;
|
||||
18;
|
||||
19;
|
||||
20;
|
||||
21;
|
||||
22;
|
||||
/** a nice safe prime */
|
||||
23;
|
||||
}
|
||||
class C implements I {\r
|
||||
1: any;\r
|
||||
2: any;\r
|
||||
3: any;\r
|
||||
4: any;\r
|
||||
5: any;\r
|
||||
6: any;\r
|
||||
7: any;\r
|
||||
8: any;\r
|
||||
9: any;\r
|
||||
10: any;\r
|
||||
11: any;\r
|
||||
12: any;\r
|
||||
13: any;\r
|
||||
14: any;\r
|
||||
15: any;\r
|
||||
16: any;\r
|
||||
17: any;\r
|
||||
18: any;\r
|
||||
19: any;\r
|
||||
20: any;\r
|
||||
21: any;\r
|
||||
22: any;\r
|
||||
/** a nice safe prime */\r
|
||||
23: any;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,12 +1,19 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// type MyType = [string, number];
|
||||
//// interface I { x: MyType; test(a: MyType): void; }
|
||||
//// class C implements I {[| |]}
|
||||
////type MyType = [string, number];
|
||||
////interface I { x: MyType; test(a: MyType): void; }
|
||||
////class C implements I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
x: [string, number];
|
||||
test(a: [string, number]): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`type MyType = [string, number];
|
||||
interface I { x: MyType; test(a: MyType): void; }
|
||||
class C implements I {\r
|
||||
x: [string, number];\r
|
||||
test(a: [string, number]): void {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,13 +1,22 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I {
|
||||
//// f(x: number, y: this): I
|
||||
//// }
|
||||
////interface I {
|
||||
//// f(x: number, y: this): I
|
||||
////}
|
||||
////
|
||||
//// class C implements I {[| |]}
|
||||
////class C implements I {[| |]}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
f(x: number,y: this): I {
|
||||
throw new Error("Method not implemented.");
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I {
|
||||
f(x: number, y: this): I
|
||||
}
|
||||
`);
|
||||
|
||||
class C implements I {\r
|
||||
f(x: number, y: this): I {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,16 +1,26 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I {
|
||||
//// f(i: any): i is I;
|
||||
//// f(): this is I;
|
||||
//// }
|
||||
////
|
||||
//// class C implements I {[| |]}
|
||||
////interface I {
|
||||
//// f(i: any): i is I;
|
||||
//// f(): this is I;
|
||||
////}
|
||||
////
|
||||
////class C implements I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
f(i: any): i is I;
|
||||
f(): this is I;
|
||||
f(i?: any) {
|
||||
throw new Error("Method not implemented.");
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I {
|
||||
f(i: any): i is I;
|
||||
f(): this is I;
|
||||
}
|
||||
`);
|
||||
|
||||
class C implements I {\r
|
||||
f(i: any): i is I;\r
|
||||
f(): this is I;\r
|
||||
f(i?: any) {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
+36
-22
@@ -1,27 +1,41 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I1 {
|
||||
//// x: number,
|
||||
//// y: number
|
||||
//// z: number;
|
||||
//// f(),
|
||||
//// g()
|
||||
//// h();
|
||||
//// }
|
||||
////interface I1 {
|
||||
//// x: number,
|
||||
//// y: number
|
||||
//// z: number;
|
||||
//// f(),
|
||||
//// g()
|
||||
//// h();
|
||||
////}
|
||||
////
|
||||
//// class C1 implements I1 {[| |]}
|
||||
////class C1 implements I1 {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
f() {
|
||||
throw new Error("Method not implemented.");
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I1'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I1 {
|
||||
x: number,
|
||||
y: number
|
||||
z: number;
|
||||
f(),
|
||||
g()
|
||||
h();
|
||||
}
|
||||
g() {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
h() {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
`);
|
||||
|
||||
class C1 implements I1 {\r
|
||||
x: number;\r
|
||||
y: number;\r
|
||||
z: number;\r
|
||||
f() {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
g() {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
h() {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,18 +1,29 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I {
|
||||
//// method(a: number, b: string): boolean;
|
||||
//// method(a: string, b: number): Function;
|
||||
//// method(a: string): Function;
|
||||
//// }
|
||||
////interface I {
|
||||
//// method(a: number, b: string): boolean;
|
||||
//// method(a: string, b: number): Function;
|
||||
//// method(a: string): Function;
|
||||
////}
|
||||
////
|
||||
//// class C implements I {[| |]}
|
||||
////class C implements I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I {
|
||||
method(a: number, b: string): boolean;
|
||||
method(a: string, b: number): Function;
|
||||
method(a: string): Function;
|
||||
method(a: any, b?: any) {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
class C implements I {\r
|
||||
method(a: number, b: string): boolean;\r
|
||||
method(a: string, b: number): Function;\r
|
||||
method(a: string): Function;\r
|
||||
method(a: any, b?: any) {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,18 +1,29 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I {
|
||||
//// method(a: number, ...b: string[]): boolean;
|
||||
//// method(a: string, ...b: number[]): Function;
|
||||
//// method(a: string): Function;
|
||||
//// }
|
||||
////interface I {
|
||||
//// method(a: number, ...b: string[]): boolean;
|
||||
//// method(a: string, ...b: number[]): Function;
|
||||
//// method(a: string): Function;
|
||||
////}
|
||||
////
|
||||
//// class C implements I {[| |]}
|
||||
////class C implements I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I {
|
||||
method(a: number, ...b: string[]): boolean;
|
||||
method(a: string, ...b: number[]): Function;
|
||||
method(a: string): Function;
|
||||
method(a: any, ...b?: any[]) {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
class C implements I {\r
|
||||
method(a: number, ...b: string[]): boolean;\r
|
||||
method(a: string, ...b: number[]): Function;\r
|
||||
method(a: string): Function;\r
|
||||
method(a: any, ...b?: any[]) {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,18 +1,29 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I {
|
||||
//// method(a: number, ...b: string[]): boolean;
|
||||
//// method(a: string, b: number): Function;
|
||||
//// method(a: string): Function;
|
||||
//// }
|
||||
////interface I {
|
||||
//// method(a: number, ...b: string[]): boolean;
|
||||
//// method(a: string, b: number): Function;
|
||||
//// method(a: string): Function;
|
||||
////}
|
||||
////
|
||||
//// class C implements I {[| |]}
|
||||
////class C implements I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I {
|
||||
method(a: number, ...b: string[]): boolean;
|
||||
method(a: string, b: number): Function;
|
||||
method(a: string): Function;
|
||||
method(a: any, b?: any, ...rest?: any[]) {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
class C implements I {\r
|
||||
method(a: number, ...b: string[]): boolean;\r
|
||||
method(a: string, b: number): Function;\r
|
||||
method(a: string): Function;\r
|
||||
method(a: any, b?: any, ...rest?: any[]) {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,15 +1,24 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// namespace N1 {
|
||||
//// export interface I1 {
|
||||
//// x: number;
|
||||
//// }
|
||||
//// }
|
||||
//// interface I1 {
|
||||
//// f1();
|
||||
//// }
|
||||
//// class C1 implements N1.I1 {[| |]}
|
||||
////namespace N1 {
|
||||
//// export interface I1 { x: number; }
|
||||
////}
|
||||
////interface I1 {
|
||||
//// f1();
|
||||
////}
|
||||
////class C1 implements N1.I1 {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
x: number;
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'N1.I1'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`namespace N1 {
|
||||
export interface I1 { x: number; }
|
||||
}
|
||||
interface I1 {
|
||||
f1();
|
||||
}
|
||||
class C1 implements N1.I1 {\r
|
||||
x: number;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,13 +1,21 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface IPerson {
|
||||
//// name: string;
|
||||
//// birthday?: string;
|
||||
//// }
|
||||
////
|
||||
//// class Person implements IPerson {[| |]}
|
||||
////interface IPerson {
|
||||
//// name: string;
|
||||
//// birthday?: string;
|
||||
////}
|
||||
////class Person implements IPerson {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'IPerson'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface IPerson {
|
||||
name: string;
|
||||
birthday?: string;
|
||||
`);
|
||||
}
|
||||
class Person implements IPerson {\r
|
||||
name: string;\r
|
||||
birthday?: string;\r
|
||||
}`,
|
||||
});
|
||||
@@ -2,18 +2,30 @@
|
||||
|
||||
// @lib: es2017
|
||||
|
||||
//// enum E { a,b,c }
|
||||
//// interface I {
|
||||
//// x: E;
|
||||
//// y: E.a
|
||||
//// z: symbol;
|
||||
//// w: object;
|
||||
//// }
|
||||
//// class C implements I {[| |]}
|
||||
////enum E { a,b,c }
|
||||
////interface I {
|
||||
//// x: E;
|
||||
//// y: E.a
|
||||
//// z: symbol;
|
||||
//// w: object;
|
||||
////}
|
||||
////class C implements I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`enum E { a,b,c }
|
||||
interface I {
|
||||
x: E;
|
||||
y: E.a;
|
||||
y: E.a
|
||||
z: symbol;
|
||||
w: object;
|
||||
`);
|
||||
}
|
||||
class C implements I {\r
|
||||
x: E;\r
|
||||
y: E.a;\r
|
||||
z: symbol;\r
|
||||
w: object;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,34 +1,56 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I {
|
||||
//// a0: {};
|
||||
//// a1: { (b1: number, c1: string): number; };
|
||||
//// a2: (b2: number, c2: string) => number;
|
||||
//// a3: { (b3: number, c3: string): number, x: number };
|
||||
////
|
||||
//// a4: { new (b1: number, c1: string): number; };
|
||||
//// a5: new (b2: number, c2: string) => number;
|
||||
//// a6: { new (b3: number, c3: string): number, x: number };
|
||||
////
|
||||
//// a7: { foo(b7: number, c7: string): number };
|
||||
////
|
||||
//// a8: { (b81: number, c81: string): number, new (b82: number, c82: string): number; };
|
||||
////
|
||||
//// a9: { (b9: number, c9: string): number; [d9: number]: I };
|
||||
//// a10: { (b10: number, c10: string): number; [d10: string]: I };
|
||||
//// }
|
||||
//// class C implements I {[| |]}
|
||||
////interface I {
|
||||
//// a0: {};
|
||||
//// a1: { (b1: number, c1: string): number; };
|
||||
//// a2: (b2: number, c2: string) => number;
|
||||
//// a3: { (b3: number, c3: string): number, x: number };
|
||||
////
|
||||
//// a4: { new (b1: number, c1: string): number; };
|
||||
//// a5: new (b2: number, c2: string) => number;
|
||||
//// a6: { new (b3: number, c3: string): number, x: number };
|
||||
////
|
||||
//// a7: { foo(b7: number, c7: string): number };
|
||||
////
|
||||
//// a8: { (b81: number, c81: string): number, new (b82: number, c82: string): number; };
|
||||
////
|
||||
//// a9: { (b9: number, c9: string): number; [d9: number]: I };
|
||||
//// a10: { (b10: number, c10: string): number; [d10: string]: I };
|
||||
////}
|
||||
////class C implements I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I {
|
||||
a0: {};
|
||||
a1: (b1: number, c1: string) => number;
|
||||
a1: { (b1: number, c1: string): number; };
|
||||
a2: (b2: number, c2: string) => number;
|
||||
a3: { (b3: number, c3: string): number; x: number; };
|
||||
a4: new (b1: number, c1: string) => number;
|
||||
a3: { (b3: number, c3: string): number, x: number };
|
||||
|
||||
a4: { new (b1: number, c1: string): number; };
|
||||
a5: new (b2: number, c2: string) => number;
|
||||
a6: { new (b3: number, c3: string): number; x: number; };
|
||||
a7: { foo(b7: number, c7: string): number; };
|
||||
a8: { (b81: number, c81: string): number; new (b82: number, c82: string): number; };
|
||||
a9: { (b9: number, c9: string): number; [d9: number]: I; };
|
||||
a10: { (b10: number, c10: string): number; [d10: string]: I; };
|
||||
`);
|
||||
a6: { new (b3: number, c3: string): number, x: number };
|
||||
|
||||
a7: { foo(b7: number, c7: string): number };
|
||||
|
||||
a8: { (b81: number, c81: string): number, new (b82: number, c82: string): number; };
|
||||
|
||||
a9: { (b9: number, c9: string): number; [d9: number]: I };
|
||||
a10: { (b10: number, c10: string): number; [d10: string]: I };
|
||||
}
|
||||
class C implements I {\r
|
||||
a0: {};\r
|
||||
a1: (b1: number, c1: string) => number;\r
|
||||
a2: (b2: number, c2: string) => number;\r
|
||||
a3: { (b3: number, c3: string): number; x: number; };\r
|
||||
a4: new (b1: number, c1: string) => number;\r
|
||||
a5: new (b2: number, c2: string) => number;\r
|
||||
a6: { new(b3: number, c3: string): number; x: number; };\r
|
||||
a7: { foo(b7: number, c7: string): number; };\r
|
||||
a8: { (b81: number, c81: string): number; new(b82: number, c82: string): number; };\r
|
||||
a9: { (b9: number, c9: string): number;[d9: number]: I; };\r
|
||||
a10: { (b10: number, c10: string): number;[d10: string]: I; };\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,12 +1,18 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// namespace N {
|
||||
//// export interface I {
|
||||
//// y: I;
|
||||
//// }
|
||||
//// }
|
||||
//// class C1 implements N.I {[| |]}
|
||||
////namespace N {
|
||||
//// export interface I { y: I; }
|
||||
////}
|
||||
////class C1 implements N.I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
y: N.I;
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'N.I'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`namespace N {
|
||||
export interface I { y: I; }
|
||||
}
|
||||
class C1 implements N.I {\r
|
||||
y: N.I;\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,11 +1,18 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I<T> {
|
||||
////interface I<T> {
|
||||
//// x: { y: T, z: T[] };
|
||||
//// }
|
||||
////
|
||||
//// class C implements I<number> {[| |]}
|
||||
////}
|
||||
////class C implements I<number> {[| |]}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
x: { y: number; z: number[]; };
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I<number>'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I<T> {
|
||||
x: { y: T, z: T[] };
|
||||
}
|
||||
class C implements I<number> {\r
|
||||
x: { y: number; z: number[]; };\r
|
||||
}`,
|
||||
});
|
||||
@@ -1,11 +1,14 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I<T> {
|
||||
//// x: T;
|
||||
//// }
|
||||
////
|
||||
//// class C implements I<number> {[| |]}
|
||||
////interface I<T> { x: T; }
|
||||
////class C implements I<number> {[| |]}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
x: number;
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I<number>'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I<T> { x: T; }
|
||||
class C implements I<number> {\r
|
||||
x: number;\r
|
||||
}`
|
||||
});
|
||||
@@ -1,11 +1,14 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I<T> {
|
||||
//// x: T;
|
||||
//// }
|
||||
////
|
||||
//// class C<T> implements I<T> {[| |]}
|
||||
////interface I<T> { x: T; }
|
||||
////class C<T> implements I<T> {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
x: T;
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I<T>'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I<T> { x: T; }
|
||||
class C<T> implements I<T> {\r
|
||||
x: T;\r
|
||||
}`
|
||||
});
|
||||
@@ -1,11 +1,14 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I<T> {
|
||||
//// x: T;
|
||||
//// }
|
||||
////
|
||||
//// class C<U> implements I<U> {[| |]}
|
||||
////interface I<T> { x: T; }
|
||||
////class C<U> implements I<U> {}
|
||||
|
||||
verify.rangeAfterCodeFix(`
|
||||
x: U;
|
||||
`);
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I<U>'",
|
||||
// TODO: GH#18445
|
||||
newFileContent:
|
||||
`interface I<T> { x: T; }
|
||||
class C<U> implements I<U> {\r
|
||||
x: U;\r
|
||||
}`
|
||||
});
|
||||
@@ -1,12 +1,20 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
//// interface I {
|
||||
//// f<T extends number>(x: T);
|
||||
//// }
|
||||
////
|
||||
//// class C implements I {[| |]}
|
||||
////interface I {
|
||||
//// f<T extends number>(x: T);
|
||||
////}
|
||||
////class C implements I {}
|
||||
|
||||
verify.rangeAfterCodeFix(`f<T extends number>(x: T){
|
||||
throw new Error("Method not implemented.");
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'I'",
|
||||
newFileContent:
|
||||
// TODO: GH#18445
|
||||
`interface I {
|
||||
f<T extends number>(x: T);
|
||||
}
|
||||
`);
|
||||
class C implements I {\r
|
||||
f<T extends number>(x: T) {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////interface I { i(): void; }
|
||||
////interface J { j(): void; }
|
||||
////class C implements I, J {}
|
||||
////class D implements J {}
|
||||
|
||||
verify.codeFixAll({
|
||||
fixId: "fixClassIncorrectlyImplementsInterface",
|
||||
// TODO: GH#20073 GH#18445
|
||||
newFileContent:
|
||||
`interface I { i(): void; }
|
||||
interface J { j(): void; }
|
||||
class C implements I, J {\r
|
||||
i(): void {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
j(): void {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}
|
||||
class D implements J {\r
|
||||
j(): void {\r
|
||||
throw new Error("Method not implemented.");\r
|
||||
}\r
|
||||
}`,
|
||||
});
|
||||
File renamed without changes.
@@ -0,0 +1,33 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////class C extends Object {
|
||||
//// constructor() {
|
||||
//// this;
|
||||
//// this;
|
||||
//// super();
|
||||
//// }
|
||||
////}
|
||||
////class D extends Object {
|
||||
//// constructor() {
|
||||
//// this;
|
||||
//// super();
|
||||
//// }
|
||||
////}
|
||||
|
||||
verify.codeFixAll({
|
||||
fixId: "classSuperMustPrecedeThisAccess",
|
||||
newFileContent: `class C extends Object {
|
||||
constructor() {
|
||||
super();\r
|
||||
this;
|
||||
this;
|
||||
}
|
||||
}
|
||||
class D extends Object {
|
||||
constructor() {
|
||||
super();\r
|
||||
this;
|
||||
}
|
||||
}`,
|
||||
});
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,23 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////class C extends Object {
|
||||
//// constructor() {}
|
||||
////}
|
||||
////class D extends Object {
|
||||
//// constructor() {}
|
||||
////}
|
||||
|
||||
verify.codeFixAll({
|
||||
fixId: "constructorForDerivedNeedSuperCall",
|
||||
// TODO: GH#18445
|
||||
newFileContent: `class C extends Object {
|
||||
constructor() {\r
|
||||
super();\r
|
||||
}
|
||||
}
|
||||
class D extends Object {
|
||||
constructor() {\r
|
||||
super();\r
|
||||
}
|
||||
}`,
|
||||
});
|
||||
File renamed without changes.
@@ -0,0 +1,17 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////interface Foo {
|
||||
//// bar: string;
|
||||
////}
|
||||
////const x: Foo.bar = "";
|
||||
////const y: Foo.bar = "";
|
||||
|
||||
verify.codeFixAll({
|
||||
fixId: "correctQualifiedNameToIndexedAccessType",
|
||||
newFileContent:
|
||||
`interface Foo {
|
||||
bar: string;
|
||||
}
|
||||
const x: Foo["bar"] = "";
|
||||
const y: Foo["bar"] = "";`,
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @allowjs: true
|
||||
// @checkJs: true
|
||||
// @noEmit: true
|
||||
|
||||
// @Filename: a.js
|
||||
////let x = "";
|
||||
////x = 1;
|
||||
////x = true;
|
||||
|
||||
verify.codeFixAll({
|
||||
fixId: "disableJsDiagnostics",
|
||||
newFileContent:
|
||||
`let x = "";
|
||||
// @ts-ignore\r
|
||||
x = 1;
|
||||
// @ts-ignore\r
|
||||
x = true;`,
|
||||
});
|
||||
Loaded 100 of 119 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user