Skip to content

Commit

Permalink
Add support for enum and class members
Browse files Browse the repository at this point in the history
  • Loading branch information
webpro committed Sep 27, 2024
1 parent 9e861e4 commit e788811
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 7 deletions.
20 changes: 20 additions & 0 deletions packages/knip/fixtures/fix-members/class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export class Rectangle {
constructor(
public width: number,
public height: number
) {}

static Key = 1;

public get unusedGetter(): string {
return 'unusedGetter';
}

private set unusedSetter(w: number) {
this.width = w;
}

area() {
return this.width * this.height;
}
}
11 changes: 11 additions & 0 deletions packages/knip/fixtures/fix-members/enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export enum Directions {
North = 1,
East = 2,
South = 3,
West = 4,
}

export enum Fruits {
apple = 'apple',
orange = 'orange',
}
9 changes: 9 additions & 0 deletions packages/knip/fixtures/fix-members/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Rectangle } from './class.js';
import { Directions, Fruits } from './enums.js';

Directions.East;

Fruits.apple;

const rect = new Rectangle();
rect.area();
3 changes: 3 additions & 0 deletions packages/knip/fixtures/fix-members/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "@fixtures/fix-members"
}
2 changes: 1 addition & 1 deletion packages/knip/src/IssueFixer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class IssueFixer {
const relPath = relative(filePath);

const types = [
...(this.isFixUnusedTypes ? (['types', 'nsTypes'] as const) : []),
...(this.isFixUnusedTypes ? (['types', 'nsTypes', 'classMembers', 'enumMembers'] as const) : []),
...(this.isFixUnusedExports ? (['exports', 'nsExports'] as const) : []),
];

Expand Down
8 changes: 6 additions & 2 deletions packages/knip/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ export const main = async (unresolvedConfiguration: CommandLineOptions) => {
if (!isReferenced) {
if (isIgnored) continue;

collector.addIssue({
const isIssueAdded = collector.addIssue({
type: 'enumMembers',
filePath,
workspace: workspace.name,
Expand All @@ -455,6 +455,8 @@ export const main = async (unresolvedConfiguration: CommandLineOptions) => {
line: member.line,
col: member.col,
});

if (isFix && isIssueAdded && member.fix) fixer.addUnusedTypeNode(filePath, [member.fix]);
} else if (isIgnored) {
for (const tagName of exportedItem.jsDocTags) {
if (tags[1].includes(tagName.replace(/^\@/, ''))) {
Expand All @@ -481,7 +483,7 @@ export const main = async (unresolvedConfiguration: CommandLineOptions) => {
continue;
}

collector.addIssue({
const isIssueAdded = collector.addIssue({
type: 'classMembers',
filePath,
workspace: workspace.name,
Expand All @@ -491,6 +493,8 @@ export const main = async (unresolvedConfiguration: CommandLineOptions) => {
line: member.line,
col: member.col,
});

if (isFix && isIssueAdded && member.fix) fixer.addUnusedTypeNode(filePath, [member.fix]);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export default visit(
// Naive, but [does.the.job()]
pos: member.name.getStart() + (ts.isComputedPropertyName(member.name) ? 1 : 0),
type: SymbolType.MEMBER,
fix: undefined,
fix: isFixTypes ? ([member.getStart(), member.getEnd(), FIX_FLAGS.NONE] as Fix) : undefined,
}))
: [];

Expand Down Expand Up @@ -130,7 +130,7 @@ export default visit(
identifier: stripQuotes(member.name.getText()),
pos: member.name.getStart(),
type: SymbolType.MEMBER,
fix: undefined,
fix: isFixTypes ? ([member.getStart(), member.getEnd(), FIX_FLAGS.OBJECT_BINDING] as Fix) : undefined,
}));

return { node, identifier, type: SymbolType.ENUM, pos, members, fix };
Expand Down
4 changes: 2 additions & 2 deletions packages/knip/src/util/remove-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export const removeExport = ({ text, start, end, flags }: FixerOptions) => {

if (flags % FIX_FLAGS.NONE) return beforeStart + afterEnd;

const exportKeyword = text.substring(start, end).trim();
if (exportKeyword === 'export' || exportKeyword === 'export default') return beforeStart + afterEnd;
const subject = text.substring(start, end).trim();
if (subject === 'export' || subject === 'export default') return beforeStart + afterEnd;

let closingBracketOffset = -1;
let commaOffset = -1;
Expand Down
80 changes: 80 additions & 0 deletions packages/knip/test/fix-members.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { test } from 'bun:test';
import assert from 'node:assert/strict';
import { readFile, writeFile } from 'node:fs/promises';
import { main } from '../src/index.js';
import { join, resolve } from '../src/util/path.js';
import baseArguments from './helpers/baseArguments.js';

const cwd = resolve('fixtures/fix-members');

const readContents = async (fileName: string) => await readFile(join(cwd, fileName), 'utf8');

test('Remove exports and dependencies', async () => {
const tests = [
[
'class.ts',
await readContents('class.ts'),
`export class Rectangle {
constructor(
public width: number,
public height: number
) {}
` +
' \n\n ' +
`
private set unusedSetter(w: number) {
this.width = w;
}
area() {
return this.width * this.height;
}
}
`,
],
[
'enums.ts',
await readContents('enums.ts'),
`export enum Directions {
` +
' ' +
`
East = 2,
` +
' \n ' +
`
}
export enum Fruits {
apple = 'apple',
` +
' ' +
`
}
`,
],
];

const { issues } = await main({
...baseArguments,
cwd,
includedIssueTypes: ['classMembers'],
isFix: true,
});

assert(issues.enumMembers['enums.ts']['orange']);
assert(issues.enumMembers['enums.ts']['North']);
assert(issues.enumMembers['enums.ts']['South']);
assert(issues.enumMembers['enums.ts']['West']);
assert(issues.classMembers['class.ts']['Key']);
assert(issues.classMembers['class.ts']['unusedGetter']);

for (const [fileName, before, after] of tests) {
const filePath = join(cwd, fileName);
const originalFile = await readFile(filePath);
assert.equal(String(originalFile), after);
await writeFile(filePath, before);
}
});
17 changes: 17 additions & 0 deletions packages/knip/test/util/remove-export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,23 @@ test('Clean export (FIX_FLAGS.OBJECT_BINDING)', () => {
}
});

test('Clean export (FIX_FLAGS.OBJECT_BINDING)', () => {
{
const text = 'export enum E { AB = 1, CD = 2 }';
assert.deepEqual(removeExport(getOpts(text, 'CD = 2', 1)), 'export enum E { AB = 1, }');
}

{
const text = "export enum E { AB = 'AB', CD = 'CD', }";
assert.deepEqual(removeExport(getOpts(text, "AB = 'AB'", 1)), "export enum E { CD = 'CD', }");
}

{
const text = 'export const { AB: A_B, CD: C_D } = fn();';
assert.deepEqual(removeExport(getOpts(text, 'AB: A_B', 1)), 'export const { CD: C_D } = fn();');
}
});

test('Clean export (FIX_FLAGS.EMPTY_DECLARATION)', () => {
{
const text = 'export { AB }';
Expand Down

0 comments on commit e788811

Please sign in to comment.