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:
Andy authored and GitHub committed 2017-12-07 12:14:18 -08:00
1 parent 4902d85479
commit 19ea189b42
119 files changed
+2942 -1789

No files matched your search

+15 -2
View File
@@ -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);
+6 -6
View File
@@ -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>;
}
+32 -9
View File
@@ -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;
+2 -2
View File
@@ -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);
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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));
},
+70 -19
View File
@@ -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);
}
}
+39 -39
View File
@@ -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}`
}]
}]
}];
}
}
+169 -183
View File
@@ -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);
}
}
+28 -21
View File
@@ -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));
}
}
+80 -50
View File
@@ -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
}]
}],
};
}
}
+29 -20
View File
@@ -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 {
+217 -189
View File
@@ -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);
}
}
}
}
+48 -96
View File
@@ -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);
}
+7 -2
View File
@@ -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
};
+228 -242
View File
@@ -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 {
+12 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+18 -9
View File
@@ -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
View File
@@ -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
View File
@@ -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[]>;
@@ -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
}`
});
@@ -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
}`,
});
@@ -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
}`,
});
@@ -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
}`,
});
@@ -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;
}
}`,
});
@@ -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
}
}`,
});
@@ -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