Skip to content

Commit ffc19b0

Browse files
antfulukastaegert
andauthored
feat: treeshake for optional chaining (#4797)
* feat: treeshake for optional chaining * chore: typo * chore: fix * chore: clean up * fix: improve coverage * fix: remove solo Co-authored-by: Lukas Taegert-Atkinson <[email protected]>
1 parent b26a37f commit ffc19b0

4 files changed

Lines changed: 66 additions & 1 deletion

File tree

src/ast/nodes/ChainExpression.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,42 @@
1+
import type { DeoptimizableEntity } from '../DeoptimizableEntity';
2+
import type { HasEffectsContext } from '../ExecutionContext';
3+
import { SHARED_RECURSION_TRACKER } from '../utils/PathTracker';
4+
import { EMPTY_PATH } from '../utils/PathTracker';
15
import type CallExpression from './CallExpression';
26
import type MemberExpression from './MemberExpression';
37
import type * as NodeType from './NodeType';
8+
import type { LiteralValueOrUnknown } from './shared/Expression';
9+
import { UnknownValue } from './shared/Expression';
410
import { NodeBase } from './shared/Node';
511

6-
export default class ChainExpression extends NodeBase {
12+
const unset = Symbol('unset');
13+
14+
export default class ChainExpression extends NodeBase implements DeoptimizableEntity {
715
declare expression: CallExpression | MemberExpression;
816
declare type: NodeType.tChainExpression;
17+
private objectValue: LiteralValueOrUnknown | typeof unset = unset;
18+
19+
deoptimizeCache(): void {
20+
this.objectValue = UnknownValue;
21+
}
22+
23+
getLiteralValueAtPath(): LiteralValueOrUnknown {
24+
if (this.getObjectValue() == null) return undefined;
25+
return UnknownValue;
26+
}
27+
28+
hasEffects(context: HasEffectsContext): boolean {
29+
if (this.getObjectValue() == null) return false;
30+
return this.expression.hasEffects(context);
31+
}
32+
33+
private getObjectValue() {
34+
if (this.objectValue === unset) {
35+
let object =
36+
this.expression.type === 'CallExpression' ? this.expression.callee : this.expression.object;
37+
if (object.type === 'MemberExpression') object = (object as MemberExpression).object;
38+
this.objectValue = object.getLiteralValueAtPath(EMPTY_PATH, SHARED_RECURSION_TRACKER, this);
39+
}
40+
return this.objectValue;
41+
}
942
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
description: 'tree-shake expressions optional chaining with literal values'
3+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// remains
2+
exist?.foo?.();
3+
4+
if (exist?.z) {
5+
console.log("remains");
6+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
undefined?.foo();
2+
undefined?.foo?.bar?.();
3+
undefined?.();
4+
5+
if (undefined?.foo) {
6+
console.log("shaked");
7+
}
8+
9+
if (undefined?.foo?.bar || false) {
10+
console.log("shaked");
11+
}
12+
13+
(undefined || null)?.foo();
14+
(undefined ?? null ?? undefined?.bar)?.foo();
15+
16+
null?.bar();
17+
18+
// remains
19+
exist?.foo?.();
20+
21+
if (exist?.z) {
22+
console.log("remains")
23+
}

0 commit comments

Comments
 (0)