diff --git a/packages/knip/fixtures/rules/exports.ts b/packages/knip/fixtures/rules/exports.ts new file mode 100644 index 000000000..ad7c73b4c --- /dev/null +++ b/packages/knip/fixtures/rules/exports.ts @@ -0,0 +1,16 @@ +export const used = 1; +export const unused = 1; + +export type UsedType = unknown; +export type UnusedType = unknown; + +export default used; + +export class MyClass { + unused: 1; +} + +export enum MyEnum { + used = 1, + unused = 1, +} diff --git a/packages/knip/fixtures/rules/index.ts b/packages/knip/fixtures/rules/index.ts new file mode 100644 index 000000000..3867735d6 --- /dev/null +++ b/packages/knip/fixtures/rules/index.ts @@ -0,0 +1,10 @@ +import 'used'; +import '@dev/used'; +import 'optional-peer-dep'; +import './unresolved'; +import 'unlisted'; +import * as NS from './ns'; +import default_, { used, type UsedType, MyClass, MyEnum } from './exports'; + +const x: UsedType | NS.UsedType = [default_, used, MyEnum.used, NS.used]; +const y = new MyClass(); diff --git a/packages/knip/fixtures/rules/ns.ts b/packages/knip/fixtures/rules/ns.ts new file mode 100644 index 000000000..2d4deded3 --- /dev/null +++ b/packages/knip/fixtures/rules/ns.ts @@ -0,0 +1,5 @@ +export const used = 1; +export const unused = 1; + +export type UsedType = unknown; +export type UnusedType = unknown; diff --git a/packages/knip/fixtures/rules/package.json b/packages/knip/fixtures/rules/package.json new file mode 100644 index 000000000..11875d31c --- /dev/null +++ b/packages/knip/fixtures/rules/package.json @@ -0,0 +1,41 @@ +{ + "name": "@fixtures/rules", + "dependencies": { + "used": "*", + "unused": "*" + }, + "devDependencies": { + "@dev/used": "*", + "@dev/unused": "*" + }, + "peerDependencies": { + "optional-peer-dep": "*" + }, + "peerDependenciesMeta": { + "optional-peer-dep": { + "optional": true + } + }, + "scripts": { + "unlisted-binary": "unlisted" + }, + "knip": { + "include": ["classMembers", "nsExports", "nsTypes"], + "rules": { + "files": "warn", + "dependencies": "warn", + "devDependencies": "warn", + "optionalPeerDependencies": "warn", + "unlisted": "warn", + "binaries": "warn", + "unresolved": "warn", + "exports": "warn", + "types": "warn", + "nsExports": "warn", + "nsTypes": "warn", + "duplicates": "warn", + "enumMembers": "warn", + "classMembers": "warn" + } + } +} diff --git a/packages/knip/fixtures/rules/unused.ts b/packages/knip/fixtures/rules/unused.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/knip/src/ConfigurationValidator.ts b/packages/knip/src/ConfigurationValidator.ts index 5740335ea..95956a060 100644 --- a/packages/knip/src/ConfigurationValidator.ts +++ b/packages/knip/src/ConfigurationValidator.ts @@ -15,6 +15,7 @@ const issueTypeSchema = z.union([ z.literal('files'), z.literal('dependencies'), z.literal('devDependencies'), + z.literal('optionalPeerDependencies'), z.literal('unlisted'), z.literal('binaries'), z.literal('unresolved'), diff --git a/packages/knip/src/IssueCollector.ts b/packages/knip/src/IssueCollector.ts index b4041d646..1dcc861a8 100644 --- a/packages/knip/src/IssueCollector.ts +++ b/packages/knip/src/IssueCollector.ts @@ -60,9 +60,11 @@ export class IssueCollector { if (this.filters.dir && !filePath.startsWith(`${this.filters.dir}/`)) continue; if (this.referencedFiles.has(filePath)) continue; if (this.isMatch(filePath)) continue; + this.issues.files.add(filePath); // @ts-expect-error TODO Fix up in next major - this.issues._files.add({ type: 'files', filePath, symbol: relative(filePath) }); + this.issues._files.add({ type: 'files', filePath, symbol: relative(filePath), severity: this.rules.files }); + this.counters.files++; this.counters.processed++; } diff --git a/packages/knip/src/reporters/symbols.ts b/packages/knip/src/reporters/symbols.ts index ba1244e03..1e03c4bec 100644 --- a/packages/knip/src/reporters/symbols.ts +++ b/packages/knip/src/reporters/symbols.ts @@ -42,6 +42,7 @@ export default ({ report, issues, tagHints, configurationHints, noConfigHints, i for (const issue of issuesForType) { const relPath = toRelative(issue.filePath); if (issue.isFixed) console.log(picocolors.gray(`${relPath} (deleted)`)); + else if (issue.severity === 'warn') console.log(picocolors.gray(relPath)); else console.log(relPath); } totalIssues = totalIssues + issuesForType.length; diff --git a/packages/knip/test/rules.test.ts b/packages/knip/test/rules.test.ts new file mode 100644 index 000000000..bb6968e74 --- /dev/null +++ b/packages/knip/test/rules.test.ts @@ -0,0 +1,38 @@ +import { test } from 'bun:test'; +import assert from 'node:assert/strict'; +import { main } from '../src/index.js'; +import { getValuesByKeyDeep } from '../src/util/object.js'; +import { resolve } from '../src/util/path.js'; +import baseArguments from './helpers/baseArguments.js'; +import baseCounters from './helpers/baseCounters.js'; + +const cwd = resolve('fixtures/rules'); + +test('Respect warnings in rules', async () => { + const { issues, counters } = await main({ + ...baseArguments, + cwd, + }); + + const severities = getValuesByKeyDeep(issues, 'severity'); + + assert(severities.every(severity => severity === 'warn')); + + assert.deepEqual(counters, { + ...baseCounters, + files: 1, + dependencies: 1, + devDependencies: 1, + optionalPeerDependencies: 1, + unlisted: 1, + binaries: 1, + unresolved: 1, + exports: 2, + types: 2, + duplicates: 1, + enumMembers: 1, + classMembers: 1, + processed: 4, + total: 4, + }); +});