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
collectionsImpl.ts
namespace collections {
    export interface SortOptions<T> {
        comparer: (a: T, b: T) => number;
        sort: "insertion" | "comparison";
    }

    export class SortedMap<K, V> {
        private _comparer: (a: K, b: K) => number;
        private _keys: K[] = [];
        private _values: V[] = [];
        private _order: number[] | undefined;
        private _version = 0;
        private _copyOnWrite = false;

        constructor(comparer: ((a: K, b: K) => number) | SortOptions<K>, iterable?: Iterable<[K, V]>) {
            this._comparer = typeof comparer === "object" ? comparer.comparer : comparer;
            this._order = typeof comparer === "object" && comparer.sort === "insertion" ? [] : undefined;
            if (iterable) {
                const iterator = getIterator(iterable);
                try {
                    for (let i = nextResult(iterator); i; i = nextResult(iterator)) {
                        const [key, value] = i.value;
                        this.set(key, value);
                    }
                }
                finally {
                    closeIterator(iterator);
                }
            }
        }

        public get size() {
            return this._keys.length;
        }

        public get comparer() {
            return this._comparer;
        }

        public get [Symbol.toStringTag]() {
            return "SortedMap";
        }

        public has(key: K) {
            return ts.binarySearch(this._keys, key, ts.identity, this._comparer) >= 0;
        }

        public get(key: K) {
            const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
            return index >= 0 ? this._values[index] : undefined;
        }

        public getEntry(key: K): [ K, V ] | undefined {
            const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
            return index >= 0 ? [ this._keys[index], this._values[index] ] : undefined;
        }

        public set(key: K, value: V) {
            const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
            if (index >= 0) {
                this._values[index] = value;
            }
            else {
                this.writePreamble();
                insertAt(this._keys, ~index, key);
                insertAt(this._values, ~index, value);
                if (this._order) insertAt(this._order, ~index, this._version);
                this.writePostScript();
            }
            return this;
        }

        public delete(key: K) {
            const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
            if (index >= 0) {
                this.writePreamble();
                ts.orderedRemoveItemAt(this._keys, index);
                ts.orderedRemoveItemAt(this._values, index);
                if (this._order) ts.orderedRemoveItemAt(this._order, index);
                this.writePostScript();
                return true;
            }
            return false;
        }

        public clear() {
            if (this.size > 0) {
                this.writePreamble();
                this._keys.length = 0;
                this._values.length = 0;
                if (this._order) this._order.length = 0;
                this.writePostScript();
            }
        }

        public forEach(callback: (value: V, key: K, collection: this) => void, thisArg?: any) {
            const keys = this._keys;
            const values = this._values;
            const indices = this.getIterationOrder();
            const version = this._version;
            this._copyOnWrite = true;
            try {
                if (indices) {
                    for (const i of indices) {
                        callback.call(thisArg, values[i], keys[i], this);
                    }
                }
                else {
                    for (let i = 0; i < keys.length; i++) {
                        callback.call(thisArg, values[i], keys[i], this);
                    }
                }
            }
            finally {
                if (version === this._version) {
                    this._copyOnWrite = false;
                }
            }
        }

        public * keys() {
            const keys = this._keys;
            const indices = this.getIterationOrder();
            const version = this._version;
            this._copyOnWrite = true;
            try {
                if (indices) {
                    for (const i of indices) {
                        yield keys[i];
                    }
                }
                else {
                    yield* keys;
                }
            }
            finally {
                if (version === this._version) {
                    this._copyOnWrite = false;
                }
            }
        }

        public * values() {
            const values = this._values;
            const indices = this.getIterationOrder();
            const version = this._version;
            this._copyOnWrite = true;
            try {
                if (indices) {
                    for (const i of indices) {
                        yield values[i];
                    }
                }
                else {
                    yield* values;
                }
            }
            finally {
                if (version === this._version) {
                    this._copyOnWrite = false;
                }
            }
        }

        public * entries() {
            const keys = this._keys;
            const values = this._values;
            const indices = this.getIterationOrder();
            const version = this._version;
            this._copyOnWrite = true;
            try {
                if (indices) {
                    for (const i of indices) {
                        yield [keys[i], values[i]] as [K, V];
                    }
                }
                else {
                    for (let i = 0; i < keys.length; i++) {
                        yield [keys[i], values[i]] as [K, V];
                    }
                }
            }
            finally {
                if (version === this._version) {
                    this._copyOnWrite = false;
                }
            }
        }

        public [Symbol.iterator]() {
            return this.entries();
        }

        private writePreamble() {
            if (this._copyOnWrite) {
                this._keys = this._keys.slice();
                this._values = this._values.slice();
                if (this._order) this._order = this._order.slice();
                this._copyOnWrite = false;
            }
        }

        private writePostScript() {
            this._version++;
        }

        private getIterationOrder() {
            if (this._order) {
                const order = this._order;
                return this._order
                    .map((_, i) => i)
                    .sort((x, y) => order[x] - order[y]);
            }
            return undefined;
        }
    }

    export function insertAt<T>(array: T[], index: number, value: T): void {
        if (index === 0) {
            array.unshift(value);
        }
        else if (index === array.length) {
            array.push(value);
        }
        else {
            for (let i = array.length; i > index; i--) {
                array[i] = array[i - 1];
            }
            array[index] = value;
        }
    }

    export function getIterator<T>(iterable: Iterable<T>): Iterator<T> {
        return iterable[Symbol.iterator]();
    }

    export function nextResult<T>(iterator: Iterator<T>): IteratorResult<T> | undefined {
        const result = iterator.next();
        return result.done ? undefined : result;
    }

    export function closeIterator<T>(iterator: Iterator<T>) {
        const fn = iterator.return;
        if (typeof fn === "function") fn.call(iterator);
    }

    /**
     * A collection of metadata that supports inheritance.
     */
    export class Metadata {
        private static readonly _undefinedValue = {};
        private _parent: Metadata | undefined;
        private _map: { [key: string]: any };
        private _version = 0;
        private _size = -1;
        private _parentVersion: number | undefined;

        constructor(parent?: Metadata) {
            this._parent = parent;
            this._map = Object.create(parent ? parent._map : null); // eslint-disable-line no-null/no-null
        }

        public get size(): number {
            if (this._size === -1 || (this._parent && this._parent._version !== this._parentVersion)) {
                let size = 0;
                for (const _ in this._map) size++;
                this._size = size;
                if (this._parent) {
                    this._parentVersion = this._parent._version;
                }
            }
            return this._size;
        }

        public get parent() {
            return this._parent;
        }

        public has(key: string): boolean {
            return this._map[Metadata._escapeKey(key)] !== undefined;
        }

        public get(key: string): any {
            const value = this._map[Metadata._escapeKey(key)];
            return value === Metadata._undefinedValue ? undefined : value;
        }

        public set(key: string, value: any): this {
            this._map[Metadata._escapeKey(key)] = value === undefined ? Metadata._undefinedValue : value;
            this._size = -1;
            this._version++;
            return this;
        }

        public delete(key: string): boolean {
            const escapedKey = Metadata._escapeKey(key);
            if (this._map[escapedKey] !== undefined) {
                delete this._map[escapedKey];
                this._size = -1;
                this._version++;
                return true;
            }
            return false;
        }

        public clear(): void {
            this._map = Object.create(this._parent ? this._parent._map : null); // eslint-disable-line no-null/no-null
            this._size = -1;
            this._version++;
        }

        public forEach(callback: (value: any, key: string, map: this) => void) {
            for (const key in this._map) {
                callback(this._map[key], Metadata._unescapeKey(key), this);
            }
        }

        private static _escapeKey(text: string) {
            return (text.length >= 2 && text.charAt(0) === "_" && text.charAt(1) === "_" ? "_" + text : text);
        }

        private static _unescapeKey(text: string) {
            return (text.length >= 3 && text.charAt(0) === "_" && text.charAt(1) === "_" && text.charAt(2) === "_" ? text.slice(1) : text);
        }
    }
}
back to top