forked from microsoft/rushstack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTypeUuid.ts
More file actions
94 lines (84 loc) · 3.91 KB
/
Copy pathTypeUuid.ts
File metadata and controls
94 lines (84 loc) · 3.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { InternalError } from './InternalError';
const classPrototypeUuidSymbol: symbol = Symbol.for('TypeUuid.classPrototypeUuid');
/**
* Provides a version-independent implementation of the JavaScript `instanceof` operator.
*
* @remarks
* The JavaScript `instanceof` operator normally only identifies objects from a particular library instance.
* For example, suppose the NPM package `example-lib` has two published versions 1.2.0 and 1.3.0, and
* it exports a class called `A`. Suppose some code consumes version `1.3.0` of the library, but it receives
* an object that was constructed using version `1.2.0`. In this situation `a instanceof A` will return `false`,
* even though `a` is an instance of `A`. The reason is that there are two prototypes for `A`; one for each
* version.
*
* The `TypeUuid` facility provides a way to make `a instanceof A` return true for both prototypes of `A`,
* by instead using a universally unique identifier (UUID) to detect object instances.
*
* You can use `Symbol.hasInstance` to enable the system `instanceof` operator to recognize type UUID equivalence:
* ```ts
* const uuidWidget: string = '9c340ef0-d29f-4e2e-a09f-42bacc59024b';
* class Widget {
* public static [Symbol.hasInstance](instance: object): boolean {
* return TypeUuid.isInstanceOf(instance, uuidWidget);
* }
* }
* ```
* // Example usage:
* ```ts
* import { Widget as Widget1 } from 'v1-of-library';
* import { Widget as Widget2 } from 'v2-of-library';
* const widget = new Widget2();
* console.log(widget instanceof Widget1); // prints true
* ```
*
* @public
*/
export class TypeUuid {
private static _uuidRegExp: RegExp = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
/**
* Registers a JavaScript class as having a type identified by the specified UUID.
* @privateRemarks
* We cannot use a construct signature for `targetClass` because it may be an abstract class.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public static registerClass(targetClass: any, typeUuid: string): void {
if (typeof targetClass !== 'function') {
throw new Error('The targetClass parameter must be a JavaScript class');
}
if (!TypeUuid._uuidRegExp.test(typeUuid)) {
throw new Error(`The type UUID must be specified as lowercase hexadecimal with dashes: "${typeUuid}"`);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const targetClassPrototype: any = targetClass.prototype;
if (Object.hasOwnProperty.call(targetClassPrototype, classPrototypeUuidSymbol)) {
const existingUuid: string = targetClassPrototype[classPrototypeUuidSymbol];
throw new InternalError(
`Cannot register the target class ${targetClass.name || ''} typeUuid=${typeUuid}` +
` because it was already registered with typeUuid=${existingUuid}`
);
}
targetClassPrototype[classPrototypeUuidSymbol] = typeUuid;
}
/**
* Returns true if the `targetObject` is an instance of a JavaScript class that was previously
* registered using the specified `typeUuid`. Base classes are also considered.
*/
public static isInstanceOf(targetObject: unknown, typeUuid: string): boolean {
if (targetObject === undefined || targetObject === null) {
return false;
}
let objectPrototype: {} = Object.getPrototypeOf(targetObject);
while (objectPrototype !== undefined && objectPrototype !== null) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const registeredUuid: string = (objectPrototype as any)[classPrototypeUuidSymbol];
if (registeredUuid === typeUuid) {
return true;
}
// Walk upwards an examine base class prototypes
objectPrototype = Object.getPrototypeOf(objectPrototype);
}
return false;
}
}