Skip to content

Commit

Permalink
fix: use internal map of decoded to encoded keys
Browse files Browse the repository at this point in the history
  • Loading branch information
shetzel committed Oct 11, 2023
1 parent 8c4a544 commit ec7ec72
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 105 deletions.
80 changes: 49 additions & 31 deletions src/collections/decodeableMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,39 @@
*/

/**
* This is an extension of the Map class that treats keys as the same by matching first normally,
* then decoded. Decoding the key before comparing can solve some edge cases in component fullNames
* such as Layouts. See: https://github.com/forcedotcom/cli/issues/1683
* This is an extension of the Map class that can match keys whether they are encoded or decoded.
* Decoding the key can solve some edge cases in component fullNames such as Layouts and Profiles.
* See: https://github.com/forcedotcom/cli/issues/1683
*
* Examples:
*
* Given a map with entries:
* ```javascript
* 'layout#Layout__Broker__c-v1%2E1 Broker Layout' : {...}
* 'layout#Layout__Broker__c-v9.2 Broker Layout' : {...}
* 'layout#Layout__Broker__c-v1.1 Broker Layout' : {...}
* 'layout#Layout__Broker__c-v9%2E2 Broker Layout' : {...}
* ```
*
* `decodeableMap.has('layout#Layout__Broker__c-v1.1 Broker Layout')` --> returns `true`
* `decodeableMap.has('layout#Layout__Broker__c-v9%2E2 Broker Layout')` --> returns `true`
* `decodeableMap.has('layout#Layout__Broker__c-v1%2E1 Broker Layout')` --> returns `true`
* `decodeableMap.has('layout#Layout__Broker__c-v9.2 Broker Layout')` --> returns `true`
*/
export class DecodeableMap<K extends string, V> extends Map<string, V> {
// Internal map of decoded keys to encoded keys.
// E.g., { 'foo-v1.3 Layout': 'foo-v1%2E3 Layout' }
// This is initialized in the `keysMap` getter.
private internalkeysMap!: Map<string, string>;

private get keysMap(): Map<string, string> {
if (!this.internalkeysMap) {
this.internalkeysMap = new Map();
}
return this.internalkeysMap;
}

/**
* boolean indicating whether an element with the specified key (matching decoded) exists or not.
*/
public has(key: K): boolean {
return super.has(key) || this.hasDecoded(key);
return !!this.getExistingKey(key);
}

/**
Expand All @@ -35,43 +47,49 @@ export class DecodeableMap<K extends string, V> extends Map<string, V> {
* that object and any change made to that object will effectively modify it inside the Map.
*/
public get(key: K): V | undefined {
return super.get(key) ?? this.getDecoded(key);
const existingKey = this.getExistingKey(key);
return existingKey ? super.get(existingKey) : undefined;
}

/**
* Adds a new element with a specified key and value to the Map. If an element with the
* same key (matching decoded) already exists, the element will be updated.
* same key (encoded or decoded) already exists, the element will be updated.
*/
public set(key: K, value: V): this {
const sKey = this.getExistingKey(key) ?? key;
return super.set(sKey, value);
return super.set(this.getExistingKey(key, true) ?? key, value);
}

/**
* true if an element in the Map existed (matching decoded) and has been removed, or false
* if the element does not exist.
* true if an element in the Map existed (matching encoded or decoded key) and has been
* removed, or false if the element does not exist.
*/
public delete(key: K): boolean {
const sKey = this.getExistingKey(key) ?? key;
return super.delete(sKey);
}

// Returns true if the passed `key` matches an existing key entry when both keys are decoded.
private hasDecoded(key: string): boolean {
return !!this.getExistingKey(key);
}

// Returns the value of an entry matching on decoded keys.
private getDecoded(key: string): V | undefined {
const existingKey = this.getExistingKey(key);
return existingKey ? super.get(existingKey) : undefined;
return existingKey ? super.delete(existingKey) : false;
}

// Returns the key as it is in the map, matching on decoded keys.
private getExistingKey(key: string): string | undefined {
for (const compKey of this.keys()) {
if (decodeURIComponent(compKey) === decodeURIComponent(key)) {
return compKey;
// Optimistically looks for an existing key. If the key is not found, detect if the
// key is encoded. If encoded, try using the decoded key. If decoded, look for an
// encoded entry in the internal map to use for the lookup.
private getExistingKey(key: K, setInKeysMap = false): string | undefined {
if (super.has(key)) {
return key;
} else {
const decodedKey = decodeURIComponent(key);
if (key !== decodedKey) {
// The key is encoded; If this is part of a set operation,
// set the { decodedKey : encodedKey } in the internal map.
if (setInKeysMap) {
this.keysMap.set(decodedKey, key);
}
if (super.has(decodedKey)) {
return decodedKey;
}
} else {
const encodedKey = this.keysMap.get(decodedKey);
if (encodedKey && super.has(encodedKey)) {
return encodedKey;
}
}
}
}
Expand Down
Loading

0 comments on commit ec7ec72

Please sign in to comment.