TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Show unused public properties and methods

Open jabacchetta opened this issue 6 years ago • 41 comments

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

screen shot 2019-01-04 at 9 23 48 pm

WebStorm Screen

screen shot 2019-01-04 at 9 23 07 pm

jabacchetta avatar Jan 05 '19 03:01 jabacchetta

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

mjbvz avatar Jan 07 '19 20:01 mjbvz

Customer demand for this feature mentioned in https://dev.to/mokkapps/why-i-switched-from-visual-studio-code-to-jetbrains-webstorm-939.

auchenberg avatar Feb 23 '19 00:02 auchenberg

what about exported but unused

  • modules
  • classes
  • types
  • interfaces/props
  • members (enum | union)
  • functions
  • constants/variables?

zpdDG4gta8XKpMCd avatar Mar 01 '19 05:03 zpdDG4gta8XKpMCd

is there any news about this issue?

fatihturgut avatar Sep 18 '19 07:09 fatihturgut

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}`);
						}
					}
				});
			}
		});
	});

jp7837 avatar Oct 16 '19 05:10 jp7837

I was wondering if we could use custom rules to detect and fix this kind of problem

alanhe421 avatar Mar 11 '20 15:03 alanhe421

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?)

Bogidon avatar Mar 25 '20 04:03 Bogidon

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?

iulianraduat avatar Mar 25 '20 08:03 iulianraduat

Another vote for this feature, first priority IMO would be some kind of indication for unused variables - grey/red squiggles, don't care much.

SigmaOutdoors avatar May 12 '20 14:05 SigmaOutdoors

No updates about this feature yet? This is present is most modern IDEs. It is a shame that VSCode doesn't support it.

delucca avatar Jul 05 '21 22:07 delucca

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.

bencun avatar Jul 06 '21 09:07 bencun

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.

iulianraduat avatar Jul 06 '21 09:07 iulianraduat

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}`);
						}
					}
				});
			}
		});
	});

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
  )> }
);

ivan-myronov-windmill avatar Jan 19 '22 14:01 ivan-myronov-windmill

I had a similar query about unused exports which ive raised here: https://github.com/microsoft/TypeScript/issues/30517

jasonwilliams avatar Jun 10 '22 12:06 jasonwilliams

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.

jpike88 avatar Jul 05 '22 01:07 jpike88

It would be very interesting to see this feature. I'm currently working on NestJS projects that would benefit from something like this.

douglas-pires avatar Aug 25 '22 20:08 douglas-pires

Ah having a huge #Angular repo, would definitely need this in vscode 🥺

gethari avatar Dec 11 '22 16:12 gethari

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 ?

gethari avatar Dec 11 '22 16:12 gethari

+1

TroelsWibergJensen avatar Jan 20 '23 12:01 TroelsWibergJensen

+1

vincentole avatar Mar 03 '23 10:03 vincentole

+1000000

chris-dallaire avatar Apr 18 '23 13:04 chris-dallaire

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!

chris-dallaire avatar Apr 18 '23 13:04 chris-dallaire

It has been more than 4 years since the issues opened. Is any work going on this issue?

osenvosem avatar Jun 08 '23 18:06 osenvosem

Come on guys, we really need this one :)

Shagon1k avatar Aug 10 '23 20:08 Shagon1k

bump*

metalsadman avatar Aug 23 '23 04:08 metalsadman

Another bump 👊

gethari avatar Aug 28 '23 02:08 gethari

Any progress ?

eternal-eager-beaver avatar Sep 15 '23 10:09 eternal-eager-beaver

Would love to have this feature :)

eliasrmz01 avatar Sep 20 '23 15:09 eliasrmz01

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.

jpike88 avatar Oct 25 '23 08:10 jpike88

Any updates?

tlcpack avatar Feb 21 '24 11:02 tlcpack