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
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);
}
}
}
Computing file changes ...