diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2ec99d2fa2b..1ab88880d9e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2291,7 +2291,7 @@ namespace ts { function typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string { const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName); - Debug.assert(typeNode !== undefined, "should always get typenode?"); + Debug.assert(typeNode !== undefined, "should always get typenode"); const options = { removeComments: true }; const writer = createTextWriter(""); const printer = createPrinter(options); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index e8ad8a033ff..09391d233a7 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3559,11 +3559,11 @@ "category": "Message", "code": 90015 }, - "Add declaration for missing property '{0}'.": { + "Declare property '{0}'.": { "category": "Message", "code": 90016 }, - "Add index signature for missing property '{0}'.": { + "Add index signature for property '{0}'.": { "category": "Message", "code": 90017 }, @@ -3587,7 +3587,15 @@ "category": "Message", "code": 90022 }, - + "Declare method '{0}'.": { + "category": "Message", + "code": 90023 + }, + "Declare static method '{0}'.": { + "category": "Message", + "code": 90024 + }, + "Convert function to an ES2015 class": { "category": "Message", "code": 95001 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 83aa50fb803..f16140cdf8c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2535,7 +2535,6 @@ namespace ts { getNonNullableType(type: Type): Type; /** Note that the resulting nodes cannot be checked. */ - typeToTypeNode(type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): TypeNode; /** Note that the resulting nodes cannot be checked. */ signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): SignatureDeclaration; diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 4b5af197a36..07aacf3c8a7 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2263,23 +2263,22 @@ namespace FourSlash { } /** - * Compares expected text to the text that would be in the sole range - * (ie: [|...|]) in the file after applying the codefix sole codefix - * in the source file. - * - * Because codefixes are only applied on the working file, it is unsafe - * to apply this more than once (consider a refactoring across files). + * Finds and applies a code action corresponding to the supplied parameters. + * If index is undefined, applies the unique code action available. + * @param errorCode The error code that generated the code action. + * @param index The nth (0-index-based) codeaction available generated by errorCode. */ - public verifyRangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number) { + public getAndApplyCodeActions(errorCode?: number, index?: number) { + const fileName = this.activeFile.fileName; + this.applyCodeActions(this.getCodeFixActions(fileName, errorCode), index); + } + + public verifyRangeIs(expectedText: string, includeWhiteSpace?: boolean) { const ranges = this.getRanges(); if (ranges.length !== 1) { this.raiseError("Exactly one range should be specified in the testfile."); } - const fileName = this.activeFile.fileName; - - this.applyCodeAction(fileName, this.getCodeFixActions(fileName, errorCode), index); - const actualText = this.rangeText(ranges[0]); const result = includeWhiteSpace @@ -2291,6 +2290,16 @@ namespace FourSlash { } } + /** + * Compares expected text to the text that would be in the sole range + * (ie: [|...|]) in the file after applying the codefix sole codefix + * in the source file. + */ + public verifyRangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number) { + this.getAndApplyCodeActions(errorCode, index); + this.verifyRangeIs(expectedText, includeWhiteSpace); + } + /** * Applies fixes for the errors in fileName and compares the results to * expectedContents after all fixes have been applied. @@ -2303,7 +2312,7 @@ namespace FourSlash { public verifyFileAfterCodeFix(expectedContents: string, fileName?: string) { fileName = fileName ? fileName : this.activeFile.fileName; - this.applyCodeAction(fileName, this.getCodeFixActions(fileName)); + this.applyCodeActions(this.getCodeFixActions(fileName)); const actualContents: string = this.getFileContent(fileName); if (this.removeWhitespace(actualContents) !== this.removeWhitespace(expectedContents)) { @@ -2341,11 +2350,10 @@ namespace FourSlash { return actions; } - private applyCodeAction(fileName: string, actions: ts.CodeAction[], index?: number): void { + private applyCodeActions(actions: ts.CodeAction[], index?: number): void { if (index === undefined) { if (!(actions && actions.length === 1)) { - const actionText = (actions && actions.length) ? JSON.stringify(actions) : "none"; - this.raiseError(`Should find exactly one codefix, but found ${actionText}`); + this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found.`); } index = 0; } @@ -2355,12 +2363,11 @@ namespace FourSlash { } } - const fileChanges = ts.find(actions[index].changes, change => change.fileName === fileName); - if (!fileChanges) { - this.raiseError("The CodeFix found doesn't provide any changes in this file."); - } + const changes = actions[index].changes; - this.applyEdits(fileChanges.fileName, fileChanges.textChanges, /*isFormattingEdit*/ false); + for (const change of changes) { + this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false); + } } public verifyImportFixAtPosition(expectedTextArray: string[], errorCode?: number) { @@ -2748,7 +2755,7 @@ namespace FourSlash { const codeActions = this.languageService.getRefactorCodeActions(this.activeFile.fileName, formattingOptions, markerPos, refactorNameToApply); - this.applyCodeAction(this.activeFile.fileName, codeActions); + this.applyCodeActions(codeActions); const actualContent = this.getFileContent(this.activeFile.fileName); if (this.normalizeNewlines(actualContent) !== this.normalizeNewlines(expectedContent)) { @@ -3795,6 +3802,14 @@ namespace FourSlashInterface { this.state.verifyFileAfterApplyingRefactorAtMarker(markerName, expectedContent, refactorNameToApply, formattingOptions); } + public rangeIs(expectedText: string, includeWhiteSpace?: boolean): void { + this.state.verifyRangeIs(expectedText, includeWhiteSpace); + } + + public getAndApplyCodeFix(errorCode?: number, index?: number): void { + this.state.getAndApplyCodeActions(errorCode, index); + } + public importFixAtPosition(expectedTextArray: string[], errorCode?: number): void { this.state.verifyImportFixAtPosition(expectedTextArray, errorCode); } diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 64e7e560094..1587b47c01b 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -8,107 +8,161 @@ namespace ts.codefix { function getActionsForAddMissingMember(context: CodeFixContext): CodeAction[] | undefined { - const sourceFile = context.sourceFile; + const tokenSourceFile = context.sourceFile; const start = context.span.start; - // This is the identifier of the missing property. eg: + // The identifier of the missing property. eg: // this.missing = 1; // ^^^^^^^ - const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false); + const token = getTokenAtPosition(tokenSourceFile, start, /*includeJsDocComment*/ false); if (token.kind !== SyntaxKind.Identifier) { return undefined; } - if (!isPropertyAccessExpression(token.parent) || token.parent.expression.kind !== SyntaxKind.ThisKeyword) { + if (!isPropertyAccessExpression(token.parent)) { return undefined; } - const classMemberDeclaration = getThisContainer(token, /*includeArrowFunctions*/ false); - if (!isClassElement(classMemberDeclaration)) { - return undefined; + const tokenName = token.getText(tokenSourceFile); + + let makeStatic = false; + let classDeclaration: ClassLikeDeclaration; + + if (token.parent.expression.kind === SyntaxKind.ThisKeyword) { + const containingClassMemberDeclaration = getThisContainer(token, /*includeArrowFunctions*/ false); + if (!isClassElement(containingClassMemberDeclaration)) { + return undefined; + } + + classDeclaration = containingClassMemberDeclaration.parent; + + // Property accesses on `this` in a static method are accesses of a static member. + makeStatic = classDeclaration && hasModifier(containingClassMemberDeclaration, ModifierFlags.Static); + } + 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 && symbol.declarations[0]; + if (leftExpressionType !== checker.getDeclaredTypeOfSymbol(symbol)) { + // The expression is a class symbol but the type is not the instance-side. + makeStatic = true; + } + } + } } - const classDeclaration = classMemberDeclaration.parent; if (!classDeclaration || !isClassLike(classDeclaration)) { return undefined; } - const isStatic = hasModifier(classMemberDeclaration, ModifierFlags.Static); + const classDeclarationSourceFile = getSourceFileOfNode(classDeclaration); + const classOpenBrace = getOpenBraceOfClassLike(classDeclaration, classDeclarationSourceFile); - return isInJavaScriptFile(sourceFile) ? getActionsForAddMissingMemberInJavaScriptFile() : getActionsForAddMissingMemberInTypeScriptFile(); + return isInJavaScriptFile(classDeclarationSourceFile) ? + getActionsForAddMissingMemberInJavaScriptFile(classDeclaration, makeStatic) : + getActionsForAddMissingMemberInTypeScriptFile(classDeclaration, makeStatic); - function getActionsForAddMissingMemberInJavaScriptFile(): CodeAction[] | undefined { - const memberName = token.getText(); + function getActionsForAddMissingMemberInJavaScriptFile(classDeclaration: ClassLikeDeclaration, makeStatic: boolean): CodeAction[] | undefined { + let actions: CodeAction[]; - if (isStatic) { + const methodCodeAction = getActionForMethodDeclaration(/*includeTypeScriptSyntax*/ false); + if (methodCodeAction) { + actions = [methodCodeAction]; + } + + if (makeStatic) { if (classDeclaration.kind === SyntaxKind.ClassExpression) { - return undefined; + return actions; } const className = classDeclaration.name.getText(); - return [{ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_static_property_0), [memberName]), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { start: classDeclaration.getEnd(), length: 0 }, - newText: `${context.newLineCharacter}${className}.${memberName} = undefined;${context.newLineCharacter}` - }] - }] - }]; + const staticInitialization = createStatement(createAssignment( + createPropertyAccess(createIdentifier(className), tokenName), + createIdentifier("undefined"))); + const staticInitializationChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); + staticInitializationChangeTracker.insertNodeAfter( + classDeclarationSourceFile, + classDeclaration, + staticInitialization, + { 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 undefined; + return actions; } - return [{ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Initialize_property_0_in_the_constructor), [memberName]), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { start: classConstructor.body.getEnd() - 1, length: 0 }, - newText: `this.${memberName} = undefined;${context.newLineCharacter}` - }] - }] - }]; + const propertyInitialization = createStatement(createAssignment( + createPropertyAccess(createThis(), tokenName), + createIdentifier("undefined"))); + + const propertyInitializationChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); + propertyInitializationChangeTracker.insertNodeAt( + classDeclarationSourceFile, + classConstructor.body.getEnd() - 1, + propertyInitialization, + { prefix: context.newLineCharacter, 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(): CodeAction[] | undefined { - let typeNode: TypeNode; + function getActionsForAddMissingMemberInTypeScriptFile(classDeclaration: ClassLikeDeclaration, makeStatic: boolean): CodeAction[] | undefined { + let actions: CodeAction[]; - if (token.parent.parent.kind === SyntaxKind.BinaryExpression) { - const binaryExpression = token.parent.parent as BinaryExpression; - - const checker = context.program.getTypeChecker(); - const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(binaryExpression.right))); - typeNode = checker.typeToTypeNode(widenedType, classDeclaration); + 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 openBrace = getOpenBraceOfClassLike(classDeclaration, sourceFile); - const property = createProperty( /*decorators*/undefined, - /*modifiers*/ isStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined, - token.getText(sourceFile), + /*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined, + tokenName, /*questionToken*/ undefined, typeNode, /*initializer*/ undefined); const propertyChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); - propertyChangeTracker.insertNodeAfter(sourceFile, openBrace, property, { suffix: context.newLineCharacter }); + propertyChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, property, { suffix: context.newLineCharacter }); - const actions = [{ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_declaration_for_missing_property_0), [token.getText()]), + (actions || (actions = [])).push({ + description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Declare_property_0), [tokenName]), changes: propertyChangeTracker.getChanges() - }]; + }); - if (!isStatic) { + if (!makeStatic) { + // Index signatures cannot have the static modifier. const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword); const indexingParameter = createParameter( /*decorators*/ undefined, @@ -125,15 +179,32 @@ namespace ts.codefix { typeNode); const indexSignatureChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); - indexSignatureChangeTracker.insertNodeAfter(sourceFile, openBrace, indexSignature, { suffix: context.newLineCharacter }); + indexSignatureChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, indexSignature, { suffix: context.newLineCharacter }); actions.push({ - description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_missing_property_0), [token.getText()]), + 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 = token.parent.parent; + const methodDeclaration = createMethodFromCallExpression(callExpression, tokenName, includeTypeScriptSyntax, makeStatic); + + const methodDeclarationChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); + methodDeclarationChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, methodDeclaration, { suffix: context.newLineCharacter }); + return { + description: formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? + Diagnostics.Declare_method_0 : + Diagnostics.Declare_static_method_0), + [tokenName]), + changes: methodDeclarationChangeTracker.getChanges() + }; + } + } } } diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 39d8e13dde9..81aae4c0e5e 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -142,6 +142,50 @@ namespace ts.codefix { } } + 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); + } + } + + const newMethod = 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; + } + + function createDummyParameters(argCount: number, names: string[] | undefined, minArgumentCount: number | undefined, addAnyType: boolean) { + const parameters: ParameterDeclaration[] = []; + for (let i = 0; i < argCount; i++) { + const newParameter = createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + /*name*/ names && names[i] || `arg${i}`, + /*questionToken*/ minArgumentCount !== undefined && i >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined, + /*type*/ addAnyType ? createKeywordTypeNode(SyntaxKind.AnyKeyword) : undefined, + /*initializer*/ undefined); + parameters.push(newParameter); + } + + return parameters; + } + function createMethodImplementingSignatures(signatures: Signature[], name: PropertyName, optional: boolean, modifiers: Modifier[] | undefined): MethodDeclaration { /** This is *a* signature with the maximal number of arguments, * such that if there is a "maximal" signature without rest arguments, @@ -163,19 +207,7 @@ namespace ts.codefix { const maxNonRestArgs = maxArgsSignature.parameters.length - (maxArgsSignature.hasRestParameter ? 1 : 0); const maxArgsParameterSymbolNames = maxArgsSignature.parameters.map(symbol => symbol.getName()); - const parameters: ParameterDeclaration[] = []; - for (let i = 0; i < maxNonRestArgs; i++) { - const anyType = createKeywordTypeNode(SyntaxKind.AnyKeyword); - const newParameter = createParameter( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - maxArgsParameterSymbolNames[i], - /*questionToken*/ i >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined, - anyType, - /*initializer*/ undefined); - parameters.push(newParameter); - } + const parameters = createDummyParameters(maxNonRestArgs, maxArgsParameterSymbolNames, minArgumentCount, /*addAnyType*/ true); if (someSigHasRestParameter) { const anyArrayType = createArrayTypeNode(createKeywordTypeNode(SyntaxKind.AnyKeyword)); diff --git a/tests/cases/fourslash/codeFixAddMissingMember5.ts b/tests/cases/fourslash/codeFixAddMissingMember5.ts index cbabc0ba6c5..db893cb61d3 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember5.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember5.ts @@ -11,9 +11,11 @@ ////} ////|] -verify.rangeAfterCodeFix(`class C { +verify.getAndApplyCodeFix(/*errorCode*/ undefined, /*index*/ 0); +verify.currentFileContentIs(`class C { static method() { ()=>{ this.foo === 10 }; } } -C.foo = undefined;`, /*includeWhiteSpace*/false, /*errorCode*/ undefined, /*index*/ 0); \ No newline at end of file +C.foo = undefined; +`); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixAddMissingMember7.ts b/tests/cases/fourslash/codeFixAddMissingMember7.ts index 0e937a3ab6e..8ac7f2b5aff 100644 --- a/tests/cases/fourslash/codeFixAddMissingMember7.ts +++ b/tests/cases/fourslash/codeFixAddMissingMember7.ts @@ -9,7 +9,9 @@ ////} ////|] -verify.rangeAfterCodeFix(`class C { +verify.getAndApplyCodeFix(/*errorCode*/ undefined, /*index*/ 2) +verify.currentFileContentIs(`class C { static p = ()=>{ this.foo === 10 }; } -C.foo = undefined;`, /*includeWhiteSpace*/false, /*errorCode*/ undefined, /*index*/ 2); \ No newline at end of file +C.foo = undefined; +`); diff --git a/tests/cases/fourslash/codeFixUndeclaredAcrossFiles1.ts b/tests/cases/fourslash/codeFixUndeclaredAcrossFiles1.ts new file mode 100644 index 00000000000..435b78ba3fa --- /dev/null +++ b/tests/cases/fourslash/codeFixUndeclaredAcrossFiles1.ts @@ -0,0 +1,34 @@ +/// + +// @allowJs: true +// @checkJs: true + +// @Filename: f2.js +//// import * as X from "./f1"; +//// X.C.m0(1, "", []); +//// X.C.x; +//// let c = new X.C; +//// c.m1(); +//// c.y = {}; + +// @Filename: f1.ts +//// export class C {[| +//// |]x: number; +//// static y: string; +//// } + +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); + +verify.rangeIs(` + y: { [x: string]: any; }; + m1(): any { + throw new Error("Method not implemented."); + } + static x: any; + static m0(arg0: any, arg1: any, arg2: any): any { + throw new Error("Method not implemented."); + } +`); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixUndeclaredAcrossFiles2.ts b/tests/cases/fourslash/codeFixUndeclaredAcrossFiles2.ts new file mode 100644 index 00000000000..04e2afc667c --- /dev/null +++ b/tests/cases/fourslash/codeFixUndeclaredAcrossFiles2.ts @@ -0,0 +1,39 @@ +/// + +// @allowJs: true +// @checkJs: true + +// @Filename: f2.ts +//// import * as X from "./f1"; +//// X.C.m0(1, "", []); +//// X.C.x; +//// let c = new X.C; +//// c.m1(); +//// c.y = {}; + +// @Filename: f1.js +//// [|export class C { +//// constructor() { } +//// } +//// +//// |] + +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); + +verify.rangeIs(` +export class C { + m1() { + throw new Error("Method not implemented."); + } + static m0(arg0, arg1, arg2) { + throw new Error("Method not implemented."); + } + constructor() { + this.y = undefined; + } +} +C.x = undefined; +`); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixUndeclaredInStaticMethod.ts b/tests/cases/fourslash/codeFixUndeclaredInStaticMethod.ts new file mode 100644 index 00000000000..75be5420e43 --- /dev/null +++ b/tests/cases/fourslash/codeFixUndeclaredInStaticMethod.ts @@ -0,0 +1,26 @@ +/// + +//// class A {[| +//// |]static foo0() { +//// this.m1(1,2,3); +//// A.m2(1,2); +//// this.prop1 = 10; +//// A.prop2 = "asdf"; +//// } +//// } + +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); + +verify.rangeIs(` + static prop2: string; + static prop1: number; + static m2(arg0: any, arg1: any): any { + throw new Error("Method not implemented."); + } + static m1(arg0: any, arg1: any, arg2: any): any { + throw new Error("Method not implemented."); + } +`); diff --git a/tests/cases/fourslash/codeFixUndeclaredMethod.ts b/tests/cases/fourslash/codeFixUndeclaredMethod.ts new file mode 100644 index 00000000000..6a61f8421aa --- /dev/null +++ b/tests/cases/fourslash/codeFixUndeclaredMethod.ts @@ -0,0 +1,27 @@ +/// + +//// class A {[| +//// |]constructor() { +//// this.foo1(1,2,3); +//// // 7 type args +//// this.foo2<1,2,3,4,5,6,7>(); +//// // 8 type args +//// this.foo3<1,2,3,4,5,6,7,8>(); +//// } +//// } + +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); +verify.getAndApplyCodeFix(/*errorCode*/undefined, 0); + +verify.rangeIs(` + foo3(): any { + throw new Error("Method not implemented."); + } + foo2(): any { + throw new Error("Method not implemented."); + } + foo1(arg0: any, arg1: any, arg2: any): any { + throw new Error("Method not implemented."); + } +`); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixUndeclaredPropertyAccesses.ts b/tests/cases/fourslash/codeFixUndeclaredPropertyAccesses.ts new file mode 100644 index 00000000000..ee05d2c0a9c --- /dev/null +++ b/tests/cases/fourslash/codeFixUndeclaredPropertyAccesses.ts @@ -0,0 +1,17 @@ +/// + +//// interface I { x: number; } +//// let i: I; +//// i.y; +//// i.foo(); +//// enum E { a,b } +//// let e: typeof E; +//// e.a; +//// e.c; +//// let obj = { a: 1, b: "asdf"}; +//// obj.c; +//// type T = I | U; +//// let t: T; +//// t.x; + +verify.not.codeFixAvailable(); \ No newline at end of file diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 3fada673f35..3e80b005625 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -236,7 +236,9 @@ declare namespace FourSlashInterface { noMatchingBracePositionInCurrentFile(bracePosition: number): void; DocCommentTemplate(expectedText: string, expectedOffset: number, empty?: boolean): void; noDocCommentTemplate(): void; - rangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number): void; + rangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number): void + getAndApplyCodeFix(errorCode?: number, index?: number): void; + rangeIs(expectedText: string, includeWhiteSpace?: boolean): void; fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, formattingOptions?: FormatCodeOptions): void; importFixAtPosition(expectedTextArray: string[], errorCode?: number): void;