It would be nice to have unused detection expanded, capable of detecting unused methods, exports, etc.
Code Example
_Shape.js_
class Shape {
constructor() {
this.color = 'red';
}
colorLog() {
console.log(this.color);
}
}
export default Shape;
_Circle.js_
import Shape from "./Shape";
class Circle extends Shape {
constructor() {
super();
// FIXME: description should show as unused in VSCode, as it does in WebStorm.
this.description = 'A circle shape';
}
// FIXME: circleLog should show as unused in VSCode, as it does in WebStorm.
circleLog() {
// NOTE: both VSCode and WebStorm have detected an unused variable.
const num = 2;
// NOTE: colorLog is being used, so no unused code errors in Shape.js file.
super.colorLog();
}
}
// FIXME: export should show as unused in VSCode, as it does in WebStorm.
export default Circle;
VSCode Screen

WebStorm Screen

We currently assume that exported members form a public API and therefore are always used. We could try to detect that a certain set of files are an API entrypoints, and then mark unused internal exports. Not sure if there are any issue already tracking this
Customer demand for this feature mentioned in https://dev.to/mokkapps/why-i-switched-from-visual-studio-code-to-jetbrains-webstorm-939.
what about exported but unused
is there any news about this issue?
Update: much faster after adding dummy getProjectVersion implementation assuming the project doesn't change during execution.
My naive solution below. It's pretty slow even though it should only run a full compilation once. I'll post updates if I improve it.
const fs = require("fs");
const ts = require("typescript");
const path = require("path");
// Copied from https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API#incremental-build-support-using-the-language-services
function getLanguageService(rootFileNames, options) {
const files = {};
// initialize the list of files
rootFileNames.forEach(fileName => {
files[fileName] = { version: 0 };
});
// Create the language service host to allow the LS to communicate with the host
const servicesHost = {
getScriptFileNames: () => rootFileNames,
getScriptVersion: fileName => files[fileName] && files[fileName].version.toString(),
getScriptSnapshot: fileName => {
if (!fs.existsSync(fileName)) {
return undefined;
}
return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString());
},
getCurrentDirectory: () => process.cwd(),
getCompilationSettings: () => options,
getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
getProjectVersion: () => 1,
fileExists: ts.sys.fileExists,
readFile: ts.sys.readFile,
readDirectory: ts.sys.readDirectory,
};
// Create the language service files
const services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
return services;
}
const tsconfigPath = "tsconfig.gen.json";
const basePath = path.resolve(path.dirname(tsconfigPath));
const parseJsonResult = ts.parseConfigFileTextToJson(tsconfigPath, fs.readFileSync(tsconfigPath, { encoding: "utf8" }));
const tsConfig = ts.parseJsonConfigFileContent(parseJsonResult.config, ts.sys, basePath);
const services = getLanguageService(tsConfig.fileNames, tsConfig.options);
// For each non-typings file
tsConfig.fileNames
.filter(f => !f.endsWith(".d.ts"))
.forEach(file => {
const source = ts.createSourceFile(file, fs.readFileSync(file, { encoding: "utf8" }));
ts.forEachChild(source, node => {
if (ts.isClassDeclaration(node)) {
// For each class member
node.members.forEach(member => {
// If member is marked as public or protected and not a constructor
if (
(ts.getCombinedModifierFlags(member) & ts.ModifierFlags.Public ||
ts.getCombinedModifierFlags(member) & ts.ModifierFlags.Protected) &&
member.kind !== ts.SyntaxKind.Constructor
) {
const references = services.findReferences(file, member.name.pos + 1);
// Fail if every reference is a definition and not in a typings file
if (
references.every(
reference =>
reference.references.length === 1 &&
reference.references[0].isDefinition &&
!reference.definition.fileName.endsWith(".d.ts")
)
) {
console.error(`File: ${file} , Member: ${member.name.text}`);
}
}
});
}
});
});
I was wondering if we could use custom rules to detect and fix this kind of problem
I would like this because using top-level exports, and destructuring imports can clutter your files quite a lot. E.g. I would like to make a Util class I can use the default import on, without worrying about forgetting to delete unused functions a year down the road.
Or would namespaces be a solution to this problem? (does unused code detection work on namespaces?)
I am also interested to remove the kind of dead code mentioned by zpdDG4gta8XKpMCd .
Maybe Patricio Zavolinsky (pzavolinsky), creator of https://www.npmjs.com/package/ts-unused-exports, can help the Visual Code team to implement it?
Another vote for this feature, first priority IMO would be some kind of indication for unused variables - grey/red squiggles, don't care much.
Most helpful comment
I am also interested to remove the kind of dead code mentioned by zpdDG4gta8XKpMCd .
Maybe Patricio Zavolinsky (pzavolinsky), creator of https://www.npmjs.com/package/ts-unused-exports, can help the Visual Code team to implement it?