Dead code designates all code that will never be executed during the app lifecycle. It can be lines of code after a return or throw instruction, or unused exported functions and components.
The former kind of dead code can be easily spotted on your IDE, but the latter is trickier: you usually don't see when an export is not used anywhere in your code anymore, and it generates all kinds of waste:
As the best kind of code is the one that is not there, in this article you will find a strategy to never keep unused exports in your Typescript project.
ts-prune is a utility that finds and lists unused exports in your project, based on your tsconfig.json. It allows you to specify files to ignore, because some of them would inevitably count as dead code:
++pre> ++code> ts-prune -i App.tsx -e # will raise an error if dead code exists
ts-prune -i App.tsx # will still list dead code, but end as a success ++/pre> ++/code>
The dead code list will look like this:
++pre> ++code> src/navigation/RootNavigator.tsx:33 - RootStack (used in module)
src/navigation/RootNavigator.tsx:58 - RootNavigator
src/modules/Core/lib/theme/colors.ts:36 - default
src/modules/Core/redux/LoadingStatus/index.ts:2 - loadingStatusReducer
src/modules/Core/redux/LoadingStatus/index.ts:19 - setError ++/pre> ++/code>
(used in module) means that the code it used inside of its own file but there is no use for the export keyword.
Be careful though: as dead code designates unused exports, removing one line of dead code can reveal plenty others! I recommend you to launch ts-prune multiple times to attain your goal of cleaning your codebase.
So now we can seek and destroy dead code. But it will still reappear over time. If only we had a way toprevent my team to merge dead code...
Your favorite CI is here to save the day! Just execute ts-prune with the ++code>-e++/code> option during a the pull/merge request CI job and it will fail if the author has generated an unused export.
But sometimes, you can have too much dead code to remove in one go. In that case let's operate gradually:
1. Use a custom script to specify a maximum amount of dead code you should never reach. Like this one:
++pre> ++code> #!/bin/sh
# Let's suppose you use yarn and you have created yarn deadcode script.
OUTPUT=`yarn --silent deadcode | wc -l`; # Count the number of unused exports
MAX_DEAD_CODE_LINES="20"; # Remember to update it regularly until it reaches 0
echo "$OUTPUT lines remaining, maximum set to $MAX_DEAD_CODE_LINES"
if [ "$OUTPUT" -gt "$MAX_DEAD_CODE_LINES" ];
then
echo "Error, you added dead code, please remove some."
exit 1
fi++/pre> ++/code>
2. Regularly update this maximum until it reaches zero.
3. At this point you can finally switch to the good old ++code>ts-prune -e++/code>.
This method will quickly lead to dead code eradication. You can even use the script in a pre-push Git hook to avoid using too much CI time.
We took our sweet time the first time I implemented it in a project but the results are here: no export was left unused.
1. Install ts-prune;
2. Use it in your CI to prevent anyone from generating unused exports;
3. Enjoy a codebase rid of useless functions or components.
That process works well for the most part, but it can fail to detect dead code in a particular case: when your unused function/component is tested. Indeed, the export is used in the test file - and only there, but ts-prune cannot deduce it is dead code at this point. A solution could be to entirely skip tests from time to time:
++pre> ++code> ts-prune -s '.*\.test\.tsx?'++/pre> ++/code>
Yup, you're looking at a regex.
This means ts-prune will not check imports inside test files. But that also means all your test utils (mocks, stubs, utility functions) will appear as dead code, so maybe you will have to ignore a couple more files to make the actual dead code easier to spot:
++pre> ++code> ts-prune -s '.*\.test\.tsx?' -i '.*/mocks\.tsx?' # or something like that++/pre> ++/code>
More regexes.
Also, there is a last kind of dead code that tools will have a hard time to detect. For example, I worked on an app that once had features for anonymous users, but finally decided to grant access to signed-in users only. But a lot of code pertaining to anonymous users stayed in the codebase even if was not of use anymore, because it was not easy to remove! If you work with spaghetti code, there is a pretty good chance actual working code and half-removed old features will be intertwined, leaving a lot of exports undetected by ts-prune.
I call it "zombie code", and if you stumble upon it, maybe it is time for a little hunt… or simply a rework !