minify icon indicating copy to clipboard operation
minify copied to clipboard

dead-code-elimination: doesn't remove the value of unused const (let/var too?)

Open tunnckoCore opened this issue 6 years ago • 2 comments

Describe the bug

I'm just experimenting with set of plugins and presets, plus terser to see what is happening.

In one of the if branches that I have, I have unused const variable, what dead-code-elimination is doing is that it's removing the const val = an the value remains.

To Reproduce

Minimal code to reproduce the bug

const foo = (str: string) => {
  const pro = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/

  if (process.env.NODE === '123') {
    return 1234
  }
  if (process.env.WAS === str) {
    const someRe = new RegExp(`

    # A regular expression for date.

    (?<year>\\d{4})-    # year part of a date
    (?<month>\\d{2})-   # month part of a date
    (?<day>\\d{2})      # day part of a date

  `, 'x');

    return pro
  }

  return process.env.BAR || process.env.QUX === 'sas'
}

console.log(foo('was was'))

with config shown below.

And run it with

WAS='was was' babel index.ts -d dist -x .ts

Actual Output

Which outputs

const foo = str => {
  const pro = /(\d{4})-(\d{2})-(\d{2})/;

  if ("was was" === str) {
    /(\d{4})-(\d{2})-(\d{2})/;
    return pro;
  }

  return void 0 || void 0 === 'sas';
};

console.log(foo('was was'));

Now, you can see it's clearly not correct - the regex remains and changes the meaning completely. Only the const someRe = is removed. It's not some collision of the modern-regexp transform. When we remove that transform it still just removes the const someRe= instead of the whole regex thing block.

That is a problem, because the above output later can be minified with terser to a thing that means totally different thing:

const foo=o=>{return"was was"===o&&/(\d{4})-(\d{2})-(\d{2})/}

Where you can clearly see that it is not correct.

Expected Output

If this dead-code-elimination plugin was working right it would remove the const and the whole regex so the terser's output will be something like

const foo=o=>{return /(\d{4})-(\d{2})-(\d{2})/}

and the following when not minified

const foo = str => {
  const pro = /(\d{4})-(\d{2})-(\d{2})/;

  if ("was was" === str) {
    return /(\d{4})-(\d{2})-(\d{2});
  }

  return void 0 || void 0 === 'sas';
};

console.log(foo('was was'));

Stack Trace

none

Configuration

Using pieces of babel-minify.

babel.config.js

const modernRegex = false

module.exports = {
  presets: [
    '@babel/preset-typescript',
    '@babel/preset-modules'
  ],
  plugins: [
    'babel-plugin-annotate-pure-calls',
    'babel-plugin-dev-expression',
    'babel-plugin-minify-builtins',
    'babel-plugin-transform-inline-environment-variables',
    'babel-plugin-transform-modern-regexp',
    'babel-plugin-transform-node-env-inline',
    'babel-plugin-transform-undefined-to-void',
    'babel-plugin-minify-dead-code-elimination',
    'babel-plugin-unassert',
  ].filter(Boolean)
}

How are you using babel-minify?

Babel CLI

  "dependencies": {
    "@babel/cli": "^7.8.0",
    "@babel/core": "^7.8.0",
    "@babel/preset-modules": "^0.1.2",
    "@babel/preset-typescript": "^7.8.0",
    "@types/node": "^13.1.6",
    "babel-plugin-annotate-pure-calls": "^0.4.0",
    "babel-plugin-dev-expression": "^0.2.2",
    "babel-plugin-minify-builtins": "^0.5.0",
    "babel-plugin-minify-dead-code-elimination": "^0.5.1",
    "babel-plugin-transform-inline-environment-variables": "^0.4.3",
    "babel-plugin-transform-modern-regexp": "^0.0.6",
    "babel-plugin-transform-node-env-inline": "^0.4.3",
    "babel-plugin-transform-undefined-to-void": "^6.9.4",
    "babel-plugin-unassert": "^3.0.1",
    "terser": "^4.6.2"
  }

Possible solution

Don't know.

tunnckoCore avatar Jan 12 '20 18:01 tunnckoCore

If we experiment further and add const foobie = 'dasdasdasd'; in that WAS if block, before the someRe, the whole thing gets removed.

const foo = (str: string) => {
   const pro = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/

   if (process.env.NODE === '123') {
     return 1234
   }
   if (process.env.WAS === str) {
+    const foobie = 'dasdasdasd';
     const someRe = new RegExp(`

     # A regular expression for date.

     (?<year>\\d{4})-    # year part of a date
     (?<month>\\d{2})-   # month part of a date
     (?<day>\\d{2})      # day part of a date

   `, 'x');

     return pro
   }

   return process.env.BAR || process.env.QUX === 'sas'
}

with correctly removing the whole foobie thing.

const foo = str => {
  const pro = /(\d{4})-(\d{2})-(\d{2})/;

  if ("was was" === str) {
    /(\d{4})-(\d{2})-(\d{2})/;
    return pro;
  }

  return void 0 || void 0 === 'sas';
};

console.log(foo('was was'));

So it's seems it's some problem with regexes for that transform. But probably okay since Terser alone has that problem too.

tunnckoCore avatar Jan 12 '20 18:01 tunnckoCore

And it's not because of the xFlag.

If you have basic regex instead, it still happens

const foo = (str: string) => {
  const pro = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/

  if (process.env.NODE === '123') {
    return 1234
  }
  if (process.env.WAS === str) {
    const foobie = 'dasdasdasd';
    const someRe = /(\d{4})/

    return pro
  }

  return process.env.BAR || process.env.QUX === 'sas'
}

console.log(foo('was was'))

output

const foo = str => {
  const pro = /(\d{4})-(\d{2})-(\d{2})/;

  if ("was was" === str) {
    /(\d{4})/;
    return pro;
  }

  return void 0 || void 0 === 'sas';
};

console.log(foo('was was'));

tunnckoCore avatar Jan 12 '20 18:01 tunnckoCore