Skip to content

Commit bef8e4d

Browse files
authored
fix: isConstruct is wrong when symlinking libraries (#955)
`Construct.isConstruct` was still using (and recommending) `instanceof`, even though `instanceof` can never be made to work reliably. When we thought `instanceof` was safe to use again, it's because we thought that `npm install` combined with `peerDependencies` would make sure only one copy of `constructs` would ever be installed. That's correct, but monorepos and users using `npm link` can still mess up that guarantee, so we cannot rely on it after all.
1 parent 8eee317 commit bef8e4d

File tree

2 files changed

+40
-4
lines changed

2 files changed

+40
-4
lines changed

API.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,24 @@ toString(): string
8080
__Returns__:
8181
* <code>string</code>
8282

83-
#### *static* isConstruct(x)⚠️ <a id="constructs-construct-isconstruct"></a>
83+
#### *static* isConstruct(x) <a id="constructs-construct-isconstruct"></a>
8484

8585
Checks if `x` is a construct.
8686

87+
Use this method instead of `instanceof` to properly detect `Construct`
88+
instances, even when the construct library is symlinked.
89+
90+
Explanation: in JavaScript, multiple copies of the `constructs` library on
91+
disk are seen as independent, completely different libraries. As a
92+
consequence, the class `Construct` in each copy of the `constructs` library
93+
is seen as a different class, and an instance of one class will not test as
94+
`instanceof` the other class. `npm install` will not create installations
95+
like this, but users may manually symlink construct libraries together or
96+
use a monorepo tool: in those cases, multiple copies of the `constructs`
97+
library can be accidentally installed, and `instanceof` will behave
98+
unpredictably. It is safest to avoid using `instanceof`, and using
99+
this type-testing method instead.
100+
87101
```ts
88102
static isConstruct(x: any): boolean
89103
```

src/construct.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { MetadataEntry } from './metadata';
33
import { captureStackTrace } from './private/stack-trace';
44
import { addressOf } from './private/uniqueid';
55

6+
const CONSTRUCT_SYM = Symbol.for('constructs.Construct');
7+
68
/**
79
* Represents a construct.
810
*/
@@ -422,13 +424,26 @@ export class Node {
422424
export class Construct implements IConstruct {
423425
/**
424426
* Checks if `x` is a construct.
427+
*
428+
* Use this method instead of `instanceof` to properly detect `Construct`
429+
* instances, even when the construct library is symlinked.
430+
*
431+
* Explanation: in JavaScript, multiple copies of the `constructs` library on
432+
* disk are seen as independent, completely different libraries. As a
433+
* consequence, the class `Construct` in each copy of the `constructs` library
434+
* is seen as a different class, and an instance of one class will not test as
435+
* `instanceof` the other class. `npm install` will not create installations
436+
* like this, but users may manually symlink construct libraries together or
437+
* use a monorepo tool: in those cases, multiple copies of the `constructs`
438+
* library can be accidentally installed, and `instanceof` will behave
439+
* unpredictably. It is safest to avoid using `instanceof`, and using
440+
* this type-testing method instead.
441+
*
425442
* @returns true if `x` is an object created from a class which extends `Construct`.
426443
* @param x Any object
427-
*
428-
* @deprecated use `x instanceof Construct` instead
429444
*/
430445
public static isConstruct(x: any): x is Construct {
431-
return x instanceof Construct;
446+
return x && typeof x === 'object' && x[CONSTRUCT_SYM];
432447
}
433448

434449
/**
@@ -536,3 +551,10 @@ export interface MetadataOptions {
536551
*/
537552
readonly traceFromFunction?: any;
538553
}
554+
555+
// Mark all instances of 'Construct'
556+
Object.defineProperty(Construct.prototype, CONSTRUCT_SYM, {
557+
value: true,
558+
enumerable: false,
559+
writable: false,
560+
});

0 commit comments

Comments
 (0)