Remove unused javascript code based on coverage report
Asked Answered
E

7

27

There is a big javascript library (~ 40 000 lines of code) and an application which uses less than 50% of the library's code.

There is a test which utilizes all the needed functionality from the library and can produce coverage report.

How to remove programmatically every unused line of code relying on the test?

Note: coverage report contains indices of lines which were executed but it is not accurate: closing braces are ignored, lines with method names are marked as executed even if the method body was not etc.

Engel answered 20/3, 2018 at 10:32 Comment(2)
If the library is written in es6, you can use tree shaking of webpack.Huddersfield
At the time of writing this comment, there is not a single answer to this question that is actually a valid answer (even the one that was awarded). They all answer another question which is about how to do static dead code analysis. This is a totally different topic.Mellisa
P
7

You can try to use:

npm install -g fixmyjs
fixmyjs <filename or folder>

This is the fixmyjs project

it is a great tool for cleanup, it appears to lack compatibility with some versions of ecmascript

Paniagua answered 23/3, 2018 at 17:50 Comment(3)
But it does not provide any coverage-related clean-up features. Does it?Engel
Yes, the provide clean up features, also if you use Atom editor you can install it there and the tool will show you all the error and unused code. github.com/addyosmani/sublime-fixmyjsPaniagua
Also, if you use grunt you can make manually the maintinance github.com/jonschlinkert/grunt-fixmyjsPaniagua
V
5

Closure Compiler provides some quite advanced unused code clean up features. Some examples:

Removing a dead code block

function hello(name) {
  alert('Hello, ' + name);
}

function hi(name) {
    alert('Hi, ' + name);
}

hello('New user 1');
hello('New user 2');

Compiles to:

alert("Hello, New user 1");
alert("Hello, New user 2");

completely stripping away the hi function and inlining hello. (live demo)

Moving to a more complicated case

As the code gets more complicated, it finds new ways to optimize. For example:

let greeted = 0;

function hello(name) {
  greeted += 1;
  alert('Hello, ' + name);
}

function hi(name) {
  greeted += 1;
  alert('Hi, ' + name);
}

hello('New user ' + greeted);
hello('New user ' + greeted);

Becomes:

var a = 0;
function b() {
  var c = "New user " + a;
  a += 1;
  alert("Hello, " + c);
}
b();
b();

(live demo)

Make sure you turn on the ADVANCED_OPTIMIZATIONS compilation level to enable dead code removal.

Vitebsk answered 26/3, 2018 at 9:53 Comment(8)
None of these answers actually answer the question. All answers are about static code analysis whereas the question specifically asks for tools that use code coverage results. That's a whole different story. It can happen that even perfect static code analysis cannot find a single line of code that may be removed, whereas code coverage could show that actually everything could be removed because even the topmost entrypoints are never actually called.Mellisa
Not sure if I agree... In the context of having an application paired to a library, doing a static analysis to see what is actually referenced is really the best you can do. If you attach a lib. method in an event listener, you simply can't delete that piece of code, even if the event is never triggered by a user. Note that closure compiler returns an empty program if there are no side effects! It's really very "smart" and quite dynamic in that sense.Vitebsk
I agree with you. Doing static analysis is probably the best choice you have. But this does not at all answer the question which was specifically about doing it differently. The answer is good, but it's not an answer to the question which is: "How to remove programmatically every unused line of code relying on the [code coverage] test?"Mellisa
I don't understand... My answer allows you to import the lib and the mentioned test, and returns a version of the library without the code not required to run the test. Have you played around with the sandbox I linked?Vitebsk
Maybe this is the problem: You don't seem to understand the difference between code coverage tests and tests :-) - I don't see how I could use code coverage results to remove untouched (not dead!) code with your answer. Dead code in this case is code that was not reached at runtime maybe even for reasons that involved randomness or time of execution. Not code that static code analysis determined to be not executable. if (date.now() < 500) console.log("hello") will not be removed by your code, but it will be after code coverage.Mellisa
I don't think the question is as explicit as you're interpreting it.Vitebsk
Than we can agree to disagree - your answer does not use code coverage, however the title, the tags, and the question refers to code coverage 5 times and the author specifically asked for this feature in comments to other answers 7 times if I count correctly.Mellisa
Modified sample where this approach obviously fails: Try this tinyurl.com/requirescoverage - cannot post as link because of posting restrictions. However URL is too long for comment.Mellisa
T
4

There are two techniques to eliminate dead code and it is possible using javascript build systems- webpack.

  1. Dead code elimination (DCE) : compiler optimisation- It excludes which is not needed in the program.

  2. Tree Shaking It works in reverse direction, includes only what is actually needed in the program.

Click here for detailed configuration.

Thermograph answered 29/3, 2018 at 0:40 Comment(0)
U
2

You can use some java script automation tools to remove your unwanted codes, first you need to install either one of the below js liblary(node must).
tree-shaking

UglifyJS2

visit any one sites. or you can remove using chrome dev tools(but be careful, test many cases before you identify unwanted codes, because this procees will identify the unwanted code means, which codes did not executed by you way of process or your test cases)

Remove Ugly JS code by chrome dev

This worked fine for my case(but my js code less than 10k lines)

Ulund answered 23/3, 2018 at 6:19 Comment(1)
Thanks for the answer. Chrome coverage tool could have been more helpful if it had coverage report export feature. There is «jscoverage» tool which does this. Yet none of the mentioned tools provide line-wise-clean code coverage report (meaning every non-hit line can be safely removed and every hit line was executed) and no means to programmatically remove unused code.Engel
C
2

In order to automatically remove unused code from bundle, we have:

  1. Tree shaking
  2. Ugliy and Minification tools such as uglifyjs, Terser
  3. Google closure compiler (best results)

However, in order to find the unused assets, to remove manually, you can use deadfile library: https://m-izadmehr.github.io/deadfile/

It can simply find unused files, in any JS project.

Without any config, it supports ES6, JSX, and Vue files: enter image description here

Combined answered 30/10, 2019 at 0:1 Comment(0)
T
1

This approach will not work I fear. Not that easy and not with the data you have available at least.

  1. The coverage report for your test which utilizes all the needed functionality is using which coverage metric? Does it excercise all values, conditions and possible combinations thereof? If not, you may miss out on usage of some part of the code.

  2. If your coverage report is not accurate you cannot rely on it for removal actions. Although braces

Given a sufficiently good test suite you can use the code coverage reports for hints however. Remove code that is reported as unused, re-run your tests and check whether they still pass. Repeat until nore more code snippets can be removed.

Triptolemus answered 20/3, 2018 at 23:20 Comment(1)
Let's assume the test does exercise all values, conditions, possible combinations etc. The question is not about test coverage quality but about how to automatically clean the code having such a test.Engel
B
0

You can process the report by leaving only the lines specified in the "ranges" in the "text"

const coverageReport = [
  {
    "url": "http://127.0.0.1:8080/index.js",
    "ranges": [
      {
        "start": 96,
        "end": 197
      },
      {
        "start": 294,
        "end": 434
      },
      {
        "start": 469,
        "end": 482
      },
      {
        "start": 511,
        "end": 574
      },
      {
        "start": 598,
        "end": 606
      },
      {
        "start": 630,
        "end": 655
      }
    ],
    "text": "function unused() {\n  console.log('unused1');console.log('unused2');\n  console.log('unused3');\n}\n\nfunction used() {\n  console.log('used1');console.log('used2');\n  console.log('used3');\n}\n\nused();\n\nfunction unused2() {\n  console.log('unused1');console.log('unused2');\n  console.log('unused3');\n}\n\nfunction used2() {\n  console.log('used1');console.log('used2');\n  console.log('used3');\n}\n\nused2();\n\nif (true) {\n  console.log('used4');\n} else {\n  console.log('unused4');\n}\n\nif (false) {\n  console.log('unused5');\n} else {\n  console.log('used5');\n}\n\ntrue ? console.log('used6') : console.log('unused6');\nfalse ? console.log('unused7') : console.log('used7');\n"
  }
];

const deleteUnusedCode = (fileObjs) => {
  const outFileObjs = [];
  for (const fileObj of fileObjs) {
    const {url, ranges, text} = fileObj;
    let outText = '';
    for (const {start, end} of ranges) {
      outText += text.slice(start, end);
    }
    outFileObjs.push({url, text: outText})
  }
  return outFileObjs
}

const showFileObjs = (fileObjs) => {
  for (const {url, text} of fileObjs) {
    console.log(url);
    console.log(text);
    console.log();
  }
}

console.log('before:');
showFileObjs(coverageReport);

console.log('after:');
showFileObjs(deleteUnusedCode(coverageReport));

Yes, after this we have only the js code used, but unfortunately it is not valid. You can manually fix the errors and then everything will be ok.

I wrote a project in which I showed the use of a coverage report to generate files based on it: https://github.com/fosemberg/coverage-report-handler

Blasted answered 20/10, 2021 at 11:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.