Show unused public properties and methods
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
- modules
- classes
- types
- interfaces/props
- members (enum | union)
- functions
- constants/variables?
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.
No updates about this feature yet? This is present is most modern IDEs. It is a shame that VSCode doesn't support it.
This would be tremendously useful. Detecting and eliminating unused public methods, exports, etc. is something I feel Code is really missing at this point. Any updates on this feature? I do understand it's a difficult task to undertake at this point, though.
Until there is an official version, I created my own extension to find unused exports. If you want to give it a try please install it from https://marketplace.visualstudio.com/items?itemName=iulian-radu-at.find-unused-exports There are already requests to extend its functionality so if you feel the same, any help in this direction is much appreciated.
Update: much faster after adding dummy
getProjectVersionimplementation 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}`); } } }); } }); });
Any ideas how do the same for composed types transformed with Pick?
For example:
export type SessionStreamConfigurationFragment = (
{ __typename?: 'Session' }
& Pick<Session, 'showRecording'>
& { oneWayStreamConfiguration?: Maybe<(
{ __typename?: 'OneWayStreamConfiguration' }
& OneWayStreamConfigurationFragment
)> }
);
I had a similar query about unused exports which ive raised here: https://github.com/microsoft/TypeScript/issues/30517
this would be huge. I have an angular project with loads of classes with static methods that are probably not used anymore. It's too much to audit method by method.
It would be very interesting to see this feature. I'm currently working on NestJS projects that would benefit from something like this.
Ah having a huge #Angular repo, would definitely need this in vscode 🥺
this would be huge. I have an angular project with loads of classes with static methods that are probably not used anymore. It's too much to audit method by method.
Did you get any solution by any chance ?
+1
+1
+1000000
One of my engineering peers keeps pointing out stupid errors I'm making in PR reviews because he's got way better static code analysis tools in WebStorm than I have in VSCode which I've been a loyal user of for years.
This is embarrassing and making me look like an even worse programmer than I already am - please fix it!
It has been more than 4 years since the issues opened. Is any work going on this issue?
Come on guys, we really need this one :)
bump*
Another bump 👊
Any progress ?
Would love to have this feature :)
We have lots of dead public properties on our shared classes, it's painstaking to audit them, I have to use 'find all references' for each property in vscode.
Any updates?