Revision f24f74eb8908ceb955b013547908dc83b0231772 authored by Babak K. Shandiz on 16 August 2022, 16:27:29 UTC, committed by GitHub on 16 August 2022, 16:27:29 UTC
* ⚗️ Add test to verify code fix works when implementing a mapped type with indirect keyof

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

* 🔨 Add property as implementation for symbols that has no declaration

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts
index 94b64e57..a4c11fd5 100644
--- a/src/services/codefixes/helpers.ts
+++ b/src/services/codefixes/helpers.ts
@@ -60,21 +60,19 @@ namespace ts.codefix {
         isAmbient = false,
     ): void {
         const declarations = symbol.getDeclarations();
-        if (!(declarations && declarations.length)) {
-            return undefined;
-        }
+        const declaration = declarations ? declarations[0] : undefined;
         const checker = context.program.getTypeChecker();
         const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions());
-        const declaration = declarations[0];
+        const kind = declaration?.kind ?? SyntaxKind.PropertySignature;
         const name = getSynthesizedDeepClone(getNameOfDeclaration(declaration), /*includeTrivia*/ false) as PropertyName;
-        const visibilityModifier = createVisibilityModifier(getEffectiveModifierFlags(declaration));
+        const visibilityModifier = createVisibilityModifier(declaration ? getEffectiveModifierFlags(declaration) : ModifierFlags.None);
         const modifiers = visibilityModifier ? factory.createNodeArray([visibilityModifier]) : undefined;
         const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration));
         const optional = !!(symbol.flags & SymbolFlags.Optional);
         const ambient = !!(enclosingDeclaration.flags & NodeFlags.Ambient) || isAmbient;
         const quotePreference = getQuotePreference(sourceFile, preferences);

-        switch (declaration.kind) {
+        switch (kind) {
             case SyntaxKind.PropertySignature:
             case SyntaxKind.PropertyDeclaration:
                 const flags = quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : undefined;
@@ -88,13 +86,16 @@ namespace ts.codefix {
                 }
                 addClassElement(factory.createPropertyDeclaration(
                     modifiers,
-                    name,
+                    declaration ? name : symbol.getName(),
                     optional && (preserveOptional & PreserveOptionalFlags.Property) ? factory.createToken(SyntaxKind.QuestionToken) : undefined,
                     typeNode,
                     /*initializer*/ undefined));
                 break;
             case SyntaxKind.GetAccessor:
             case SyntaxKind.SetAccessor: {
+                if (!declarations) {
+                    break;
+                }
                 let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context));
                 const allAccessors = getAllAccessorDeclarations(declarations, declaration as AccessorDeclaration);
                 const orderedAccessors = allAccessors.secondAccessor
@@ -138,6 +139,10 @@ namespace ts.codefix {
                 // If there is more than one overload but no implementation signature
                 // (eg: an abstract method or interface declaration), there is a 1-1
                 // correspondence of declarations and signatures.
+                if (!declarations) {
+                    break;
+                }
+
                 const signatures = checker.getSignaturesOfType(type, SignatureKind.Call);
                 if (!some(signatures)) {
                     break;

* 🔨 Improve code readability

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts
index 2f5c8703ab..aea0206a8a 100644
--- a/src/services/codefixes/helpers.ts
+++ b/src/services/codefixes/helpers.ts
@@ -60,7 +60,7 @@ namespace ts.codefix {
         isAmbient = false,
     ): void {
         const declarations = symbol.getDeclarations();
-        const declaration = declarations ? declarations[0] : undefined;
+        const declaration = declarations?.[0];
         const checker = context.program.getTypeChecker();
         const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions());
         const kind = declaration?.kind ?? SyntaxKind.PropertySignature;
@@ -93,9 +93,7 @@ namespace ts.codefix {
                 break;
             case SyntaxKind.GetAccessor:
             case SyntaxKind.SetAccessor: {
-                if (!declarations) {
-                    break;
-                }
+                Debug.assertIsDefined(declarations);
                 let typeNode = checker.typeToTypeNode(type, enclosingDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context));
                 const allAccessors = getAllAccessorDeclarations(declarations, declaration as AccessorDeclaration);
                 const orderedAccessors = allAccessors.secondAccessor
@@ -139,10 +137,7 @@ namespace ts.codefix {
                 // If there is more than one overload but no implementation signature
                 // (eg: an abstract method or interface declaration), there is a 1-1
                 // correspondence of declarations and signatures.
-                if (!declarations) {
-                    break;
-                }
-
+                Debug.assertIsDefined(declarations);
                 const signatures = type.isUnion() ? flatMap(type.types, t => t.getCallSignatures()) : type.getCallSignatures();
                 if (!some(signatures)) {
                     break;

* 📜 Add comment regarding mapped type children's  missing declaration

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>

Signed-off-by: Babak K. Shandiz <babak.k.shandiz@gmail.com>
Co-authored-by: Andrew Branch <andrew@wheream.io>
1 parent 90fa1c7
Raw File
harnessGlobals.ts
// Block scoped definitions work poorly for global variables, temporarily enable var
/* eslint-disable no-var */

// this will work in the browser via browserify
declare var assert: typeof _chai.assert;
declare var expect: typeof _chai.expect;
var _chai: typeof import("chai") = require("chai");
globalThis.assert = _chai.assert;
{
    // chai's builtin `assert.isFalse` is featureful but slow - we don't use those features,
    // so we'll just overwrite it as an alterative to migrating a bunch of code off of chai
    assert.isFalse = (expr: any, msg: string) => {
        if (expr !== false) throw new Error(msg);
    };

    const assertDeepImpl = assert.deepEqual;
    assert.deepEqual = (a, b, msg) => {
        if (ts.isArray(a) && ts.isArray(b)) {
            assertDeepImpl(arrayExtraKeysObject(a), arrayExtraKeysObject(b), "Array extra keys differ");
        }
        assertDeepImpl(a, b, msg);

        function arrayExtraKeysObject(a: readonly unknown[]): object {
            const obj: { [key: string]: unknown } = {};
            for (const key in a) {
                if (Number.isNaN(Number(key))) {
                    obj[key] = a[key];
                }
            }
            return obj;
        }
    };
}
globalThis.expect = _chai.expect;
/* eslint-enable no-var */
// empty ts namespace so this file is included in the `ts.ts` namespace file generated by the module swapover
// This way, everything that ends up importing `ts` downstream also imports this file and picks up its augmentation
namespace ts {}
back to top