Remove unused variables with the Babel plugin

- functions were already removed
- variables can be removed when their initializer does not have side effects
- classes can be removed when they have no static blocks

Fixes #21337
This commit is contained in:
Nicolò Ribaudo 2026-05-26 14:37:40 +02:00
parent 03eda70d7e
commit 2691868904
11 changed files with 82 additions and 25 deletions

View File

@ -47,18 +47,31 @@ function handlePreprocessorAction(ctx, actionName, args, path) {
}
function babelPluginPDFJSPreprocessor(babel, ctx) {
function removeUnusedFunctions(path) {
function removeUnusedFunctionsAndVariables(path) {
const { scope } = path;
let removed;
do {
removed = false;
path.scope.crawl();
for (const name in path.scope.bindings) {
const binding = path.scope.bindings[name];
scope.crawl();
for (const name in scope.bindings) {
const binding = scope.bindings[name];
if (!binding.referenced) {
const { path: bindingPath } = binding;
if (bindingPath.isFunctionDeclaration()) {
bindingPath.remove();
removed = true;
} else if (
bindingPath.isClassDeclaration() &&
!bindingPath.node.body.body.some(m => m.type === "StaticBlock")
) {
bindingPath.remove();
removed = true;
} else if (
bindingPath.isVariableDeclarator() &&
scope.isPure(bindingPath.node.init, false)
) {
bindingPath.remove();
removed = true;
}
}
}
@ -113,24 +126,28 @@ function babelPluginPDFJSPreprocessor(babel, ctx) {
LogicalExpression: {
exit(path) {
const { node } = path;
if (!t.isBooleanLiteral(node.left)) {
return;
let leftIsTruthy = false;
switch (node.left.type) {
case "BooleanLiteral":
case "NumericLiteral":
case "StringLiteral":
case "NullLiteral":
leftIsTruthy = Boolean(node.left.value);
break;
default:
return;
}
switch (node.operator) {
case "&&":
// true && expr => expr
// false && expr => false
path.replaceWith(
node.left.value === true ? node.right : node.left
);
path.replaceWith(leftIsTruthy ? node.right : node.left);
break;
case "||":
// true || expr => true
// false || expr => expr
path.replaceWith(
node.left.value === true ? node.left : node.right
);
path.replaceWith(leftIsTruthy ? node.left : node.right);
break;
}
},
@ -258,7 +275,7 @@ function babelPluginPDFJSPreprocessor(babel, ctx) {
body.pop();
}
removeUnusedFunctions(path);
removeUnusedFunctionsAndVariables(path);
},
},
ClassMethod: {
@ -284,7 +301,7 @@ function babelPluginPDFJSPreprocessor(babel, ctx) {
Program: {
exit(path) {
if (path.node.sourceType === "module") {
removeUnusedFunctions(path);
removeUnusedFunctionsAndVariables(path);
}
},
},

View File

@ -13,3 +13,6 @@ var l = true;
var m = false;
var n = false;
var o = true;
var p = null;
var q = 1;
use(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q);

View File

@ -13,3 +13,7 @@ var l = 'test' !== 'test2';
var m = '1' === true;
var n = !true;
var o = !false;
var p = null && 1;
var q = null || 1;
use(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q);

View File

@ -18,6 +18,8 @@ class E extends A {
class F {
constructor() {
var a = 0;
use(a);
}
}
class G {}
use(B, C, D, E, F, G);

View File

@ -24,6 +24,7 @@ class F {
constructor() {
if (PDFJSDev.test('TRUE')) {
var a = 0;
use(a);
}
}
}
@ -32,6 +33,9 @@ class G {
constructor() {
if (PDFJSDev.test('FALSE')) {
var a = 0;
use(a);
}
}
}
use(B, C, D, E, F, G);

View File

@ -5,12 +5,12 @@ function f2() {
}
f2();
function f3() {
var i = 0;
before();
throw "test";
}
f3();
function f4() {
var i = 0;
before();
}
f4();
var obj = {
@ -23,3 +23,4 @@ class C {
}
var arrow1 = () => {};
var arrow2 = () => {};
use(obj, C, arrow1, arrow2);

View File

@ -1,41 +1,43 @@
function f1() {
return;
var i = 0;
after();
}
f1();
function f2() {
return 1;
var i = 0;
after();
}
f2();
function f3() {
var i = 0;
before();
throw "test";
var j = 0;
after();
}
f3();
function f4() {
var i = 0;
before();
if (true) {
return;
}
throw "test";
var j = 0;
after();
}
f4();
var obj = {
method1() { return; var i = 0; },
method1() { return; after(); },
method2() { return; },
};
class C {
method1() { return; var i = 0; }
method1() { return; after(); }
method2() { return; }
}
var arrow1 = () => { return; var i = 0; };
var arrow1 = () => { return; after(); };
var arrow2 = () => { return; };
use(obj, C, arrow1, arrow2);

View File

@ -16,3 +16,4 @@ var j = {
};
var k = false;
var l = true;
use(a, b, c, d, e, f, g, i, j, k, l);

View File

@ -9,3 +9,5 @@ var i = typeof PDFJSDev === 'undefined' ? PDFJSDev.eval('FALSE') : '0';
var j = typeof PDFJSDev !== 'undefined' ? PDFJSDev.eval('OBJ.obj') : '0';
var k = !PDFJSDev.test('TRUE');
var l = !PDFJSDev.test('FALSE');
use(a, b, c, d, e, f, g, i, j, k, l);

View File

@ -0,0 +1,7 @@
let c = 4;
let e = sideEffect();
let f = 5;
use(c);
{
use(f);
}

View File

@ -0,0 +1,14 @@
let a = 1;
let b = a, c = 4;
let d = null && sideEffect();
let e = null || sideEffect();
let f = 5;
let g = 6;
use(c);
if (PDFJSDev.test('TRUE')) {
use(f);
} else {
use(g);
}