Skip to content

Commit

Permalink
Tree-shake unused declarations while keeping initializer side-effects
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Jan 22, 2021
1 parent e23bb35 commit 90b3fcd
Show file tree
Hide file tree
Showing 136 changed files with 252 additions and 2,135 deletions.
2 changes: 1 addition & 1 deletion src/ast/nodes/ForInStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default class ForInStatement extends StatementBase {

include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) {
this.included = true;
this.left.includeWithAllDeclaredVariables(includeChildrenRecursively, context);
this.left.includeWithAllDeclaredVariables(context, includeChildrenRecursively);
this.left.deoptimizePath(EMPTY_PATH);
this.right.include(context, includeChildrenRecursively);
const { brokenFlow } = context;
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/ForOfStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default class ForOfStatement extends StatementBase {

include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) {
this.included = true;
this.left.includeWithAllDeclaredVariables(includeChildrenRecursively, context);
this.left.includeWithAllDeclaredVariables(context, includeChildrenRecursively);
this.left.deoptimizePath(EMPTY_PATH);
this.right.include(context, includeChildrenRecursively);
const { brokenFlow } = context;
Expand Down
28 changes: 13 additions & 15 deletions src/ast/nodes/VariableDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function areAllDeclarationsIncludedAndNotExported(
exportNamesByVariable: Map<Variable, string[]>
): boolean {
for (const declarator of declarations) {
if (!declarator.included) return false;
if (!declarator.id.included) return false;
if (declarator.id.type === NodeType.Identifier) {
if (exportNamesByVariable.has(declarator.id.variable!)) return false;
} else {
Expand Down Expand Up @@ -69,12 +69,13 @@ export default class VariableDeclaration extends NodeBase {
}

includeWithAllDeclaredVariables(
includeChildrenRecursively: IncludeChildren,
context: InclusionContext
) {
context: InclusionContext,
includeChildrenRecursively: IncludeChildren
): void {
this.included = true;
for (const declarator of this.declarations) {
declarator.include(context, includeChildrenRecursively);
declarator.id.included = true;
declarator.includeWithAllDeclaredVariables(context, includeChildrenRecursively);
}
}

Expand Down Expand Up @@ -158,11 +159,7 @@ export default class VariableDeclaration extends NodeBase {
this.end - (code.original.charCodeAt(this.end - 1) === 59 /*";"*/ ? 1 : 0)
);
let actualContentEnd: number | undefined, renderedContentEnd: number;
if (/\n\s*$/.test(code.slice(this.start, separatedNodes[0].start))) {
renderedContentEnd = this.start + this.kind.length;
} else {
renderedContentEnd = separatedNodes[0].start;
}
renderedContentEnd = findNonWhiteSpace(code.original, this.start + this.kind.length);
let lastSeparatorPos = renderedContentEnd - 1;
code.remove(this.start, lastSeparatorPos);
let isInDeclaration = false;
Expand All @@ -187,11 +184,12 @@ export default class VariableDeclaration extends NodeBase {
leadingString = '';
nextSeparatorString = '';
if (
node.id instanceof Identifier &&
isReassignedExportsMember(
(node.id as IdentifierWithVariable).variable,
options.exportNamesByVariable
)
!node.id.included ||
(node.id instanceof Identifier &&
isReassignedExportsMember(
(node.id as IdentifierWithVariable).variable,
options.exportNamesByVariable
))
) {
if (hasRenderedContent) {
separatorString += ';';
Expand Down
52 changes: 51 additions & 1 deletion src/ast/nodes/VariableDeclarator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import MagicString from 'magic-string';
import { BLANK } from '../../utils/blank';
import {
findFirstOccurrenceOutsideComment,
findNonWhiteSpace,
RenderOptions
} from '../../utils/renderHelpers';
import { HasEffectsContext, InclusionContext } from '../ExecutionContext';
import { ObjectPath } from '../utils/PathTracker';
import { UNDEFINED_EXPRESSION } from '../values';
import * as NodeType from './NodeType';
import { ExpressionNode, NodeBase } from './shared/Node';
import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node';
import { PatternNode } from './shared/Pattern';

export default class VariableDeclarator extends NodeBase {
Expand All @@ -16,4 +24,46 @@ export default class VariableDeclarator extends NodeBase {
deoptimizePath(path: ObjectPath) {
this.id.deoptimizePath(path);
}

hasEffects(context: HasEffectsContext): boolean {
return this.id.hasEffects(context) || (this.init !== null && this.init.hasEffects(context));
}

include(context: InclusionContext, includeChildrenRecursively: IncludeChildren) {
this.included = true;
if (includeChildrenRecursively || this.id.shouldBeIncluded(context)) {
this.id.include(context, includeChildrenRecursively);
}
if (this.init) {
this.init.include(context, includeChildrenRecursively);
}
}

includeWithAllDeclaredVariables(
context: InclusionContext,
includeChildrenRecursively: IncludeChildren
): void {
this.included = true;
this.id.include(context, includeChildrenRecursively);
if (this.init) {
this.init.include(context, includeChildrenRecursively);
}
}

render(code: MagicString, options: RenderOptions) {
const renderId = this.id.included;
if (renderId) {
this.id.render(code, options);
} else {
const operatorPos = findFirstOccurrenceOutsideComment(code.original, '=', this.id.end);
code.remove(this.start, findNonWhiteSpace(code.original, operatorPos + 1));
}
if (this.init) {
this.init.render(
code,
options,
renderId ? BLANK : { renderedParentType: NodeType.ExpressionStatement }
);
}
}
}
10 changes: 5 additions & 5 deletions src/ast/nodes/shared/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ export interface Node extends Entity {
* for for-loops that do not use a declared loop variable.
*/
includeWithAllDeclaredVariables(
includeChildrenRecursively: IncludeChildren,
context: InclusionContext
context: InclusionContext,
includeChildrenRecursively: IncludeChildren
): void;
render(code: MagicString, options: RenderOptions, nodeRenderOptions?: NodeRenderOptions): void;

Expand Down Expand Up @@ -216,9 +216,9 @@ export class NodeBase implements ExpressionNode {
}

includeWithAllDeclaredVariables(
includeChildrenRecursively: IncludeChildren,
context: InclusionContext
) {
context: InclusionContext,
includeChildrenRecursively: IncludeChildren
): void {
this.include(context, includeChildrenRecursively);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ define(['require', 'exports'], function (require, exports) { 'use strict';

const shared = 'shared';

const dynamic = new Promise(function (resolve, reject) { require(['./generated-dynamic'], resolve, reject) });
new Promise(function (resolve, reject) { require(['./generated-dynamic'], resolve, reject) });

globalThis.sharedStatic = shared;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });

const shared = 'shared';

const dynamic = Promise.resolve().then(function () { return require('./generated-dynamic.js'); });
Promise.resolve().then(function () { return require('./generated-dynamic.js'); });

globalThis.sharedStatic = shared;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const shared = 'shared';

const dynamic = import('./generated-dynamic.js');
import('./generated-dynamic.js');

globalThis.sharedStatic = shared;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ System.register([], function (exports, module) {

const shared = exports('s', 'shared');

const dynamic = module.import('./generated-dynamic.js');
module.import('./generated-dynamic.js');

globalThis.sharedStatic = shared;

Expand Down
30 changes: 15 additions & 15 deletions test/form/samples/builtin-prototypes/array-expression/_expected.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
const map2 = [ 1 ].map( x => console.log( 1 ) );
const map4 = [ 1 ].map( x => x ).map( x => console.log( 1 ) );
const map5 = [ 1 ].map( x => console.log( 1 ) ).map( x => x );
const map7 = [ 1 ].map( x => x ).map( x => x ).map( x => console.log( 1 ) );
const map8 = [ 1 ].map( x => x ).map( x => console.log( 1 ) ).map( x => x );
[ 1 ].map( x => console.log( 1 ) );
[ 1 ].map( x => x ).map( x => console.log( 1 ) );
[ 1 ].map( x => console.log( 1 ) ).map( x => x );
[ 1 ].map( x => x ).map( x => x ).map( x => console.log( 1 ) );
[ 1 ].map( x => x ).map( x => console.log( 1 ) ).map( x => x );

[]();
const _everyEffect = [ 1 ].every( () => console.log( 1 ) || true );
const _filterEffect = [ 1 ].filter( () => console.log( 1 ) || true );
const _findEffect = [ 1 ].find( () => console.log( 1 ) || true );
const _findIndexEffect = [ 1 ].findIndex( () => console.log( 1 ) || true );
const _forEachEffect = [ 1 ].forEach( () => console.log( 1 ) || true );
const _mapEffect = [ 1 ].map( () => console.log( 1 ) || 1 );
const _reduceEffect = [ 1 ].reduce( () => console.log( 1 ) || 1, 1 );
const _reduceRightEffect = [ 1 ].reduceRight( () => console.log( 1 ) || 1, 1 );
const _someEffect = [ 1 ].some( () => console.log( 1 ) || true );
[ 1 ].every( () => console.log( 1 ) || true );
[ 1 ].filter( () => console.log( 1 ) || true );
[ 1 ].find( () => console.log( 1 ) || true );
[ 1 ].findIndex( () => console.log( 1 ) || true );
[ 1 ].forEach( () => console.log( 1 ) || true );
[ 1 ].map( () => console.log( 1 ) || 1 );
[ 1 ].reduce( () => console.log( 1 ) || 1, 1 );
[ 1 ].reduceRight( () => console.log( 1 ) || 1, 1 );
[ 1 ].some( () => console.log( 1 ) || true );

// mutator methods
const exported = [ 1 ];
Expand All @@ -23,7 +23,7 @@ exported.pop();
exported.push( 0 );
exported.reverse();
exported.shift();
const _sortEffect = [ 1 ].sort( () => console.log( 1 ) || 0 );
[ 1 ].sort( () => console.log( 1 ) || 0 );
exported.sort();
exported.splice( 0 );
exported.unshift( 0 );
Expand Down
8 changes: 4 additions & 4 deletions test/form/samples/builtin-prototypes/literal/_expected.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const _replaceEffect = 'ab'.replace( 'a', () => console.log( 1 ) || 'b' );
'ab'.replace( 'a', () => console.log( 1 ) || 'b' );

// deep property access is forbidden
const deepBoolean = true.x.y;
const deepNumber = (1).x.y;
const deepString = 'ab'.x.y;
true.x.y;
(1).x.y;
'ab'.x.y;

// due to strict mode, extension is forbidden
true.x = 1;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const object = {};
const propertyIsEnumerable1 = object.propertyIsEnumerable( 'toString' );
const propertyIsEnumerable2 = {}.propertyIsEnumerable( 'toString' );
const propertyIsEnumerable3 = {}.propertyIsEnumerable( 'toString' ).valueOf();
object.propertyIsEnumerable( 'toString' );
{}.propertyIsEnumerable( 'toString' );
{}.propertyIsEnumerable( 'toString' ).valueOf();

const _hasOwnProperty = {}.hasOwnProperty( 'toString' ).valueOf();
const _isPrototypeOf = {}.isPrototypeOf( {} ).valueOf();
const _propertyIsEnumerable = {}.propertyIsEnumerable( 'toString' ).valueOf();
const _toLocaleString = {}.toLocaleString().trim();
const _toString = {}.toString().trim();
const _valueOf = {}.valueOf();
{}.hasOwnProperty( 'toString' ).valueOf();
{}.isPrototypeOf( {} ).valueOf();
{}.propertyIsEnumerable( 'toString' ).valueOf();
{}.toLocaleString().trim();
{}.toString().trim();
{}.valueOf();
24 changes: 12 additions & 12 deletions test/form/samples/conditional-expression-paths/_expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@ var bar = { x: () => {}, y: {} };
var baz = { x: () => console.log('effect') };

// unknown branch without side-effects
var a1 = (unknownValue ? foo : bar).y.z;
var b1 = (unknownValue ? foo : bar).x();
(unknownValue ? foo : bar).y.z;
(unknownValue ? foo : bar).x();

// unknown branch with side-effect
var a2 = (unknownValue ? foo : baz).y.z;
var b2 = (unknownValue ? foo : baz).x();
(unknownValue ? foo : baz).y.z;
(unknownValue ? foo : baz).x();

// known branch without side-effects
var a3 = ( foo ).y.z;
var b3 = ( foo).y.z;
var c3 = ( foo ).x();
var d3 = ( foo).x();
( foo ).y.z;
( foo).y.z;
( foo ).x();
( foo).x();

// known branch with side-effect
var a4 = ( baz ).y.z;
var b4 = ( baz).y.z;
var c4 = ( baz ).x();
var d4 = ( baz).x();
( baz ).y.z;
( baz).y.z;
( baz ).x();
( baz).x();
var baz3 = {};
( baz3 ).y.z = 1;
( baz3).y.z = 1;
17 changes: 17 additions & 0 deletions test/form/samples/conditional-expression/_expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// side-effect in condition
foo() ? 1 : 2;

var unknownValue = bar();

// unknown branch with side-effect
unknownValue ? foo() : 2;
unknownValue ? 1 : foo();
(unknownValue ? function () {} : function () {this.x = 1;})();

// known side-effect
foo() ;
( function () {this.x = 1;} )();
( () => () => console.log( 'effect' ) )()();
foo();
( function () {this.x = 1;})();
( () => () => console.log( 'effect' ))()();
21 changes: 0 additions & 21 deletions test/form/samples/conditional-expression/_expected/amd.js

This file was deleted.

19 changes: 0 additions & 19 deletions test/form/samples/conditional-expression/_expected/cjs.js

This file was deleted.

17 changes: 0 additions & 17 deletions test/form/samples/conditional-expression/_expected/es.js

This file was deleted.

0 comments on commit 90b3fcd

Please sign in to comment.