fix-knip-unused-exports

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Fix Knip Unused Exports

修复Knip未使用导出问题

Fix knip "Unused exports" violations. There are several categories of violation, each with a different fix strategy.
修复Knip的「未使用导出」违规问题。违规分为多个类别,每个类别对应不同的修复策略。

When to Use

适用场景

  • npm run knip
    reports "Unused exports"
  • npm run knip
    报告「未使用导出」

When NOT to Use

不适用场景

  • The export is consumed by non-test production code in another file -- something else is wrong
  • 该导出被其他文件中的非测试生产代码使用——此时存在其他问题

Workflow

操作流程

1. Identify Violations

1. 识别违规

bash
npm run knip
Output looks like:
Unused exports (3)
::error file=packages/foo/src/bar.ts,line=42,title=Unused exports::myFunction
bash
npm run knip
输出示例:
Unused exports (3)
::error file=packages/foo/src/bar.ts,line=42,title=Unused exports::myFunction

2. Classify Each Violation

2. 分类每个违规

For each flagged export, grep the entire repository (not just the package):
bash
rg "myFunction"
Determine which category it falls into:
CategoryCallersFix
Test-only exportUsed in same file + test files onlyExtract to new file
Dead barrel re-exportRe-exported from
index.ts
, but production code imports via relative paths or other subpaths instead
Remove the re-export from the barrel
Internally-only-used exportUsed only within the same file, not by tests or other filesRemove the
export
keyword
Dead codeNo callers anywhereDelete the export
Production consumer existsUsed by non-test code in another fileNot a knip issue -- investigate further
Important: When grepping, exclude test files to identify production consumers:
bash
rg "myFunction" --glob '!**/*.test.*'
针对每个标记的导出,在整个仓库(不仅限于当前包)中搜索:
bash
rg "myFunction"
确定其所属类别:
类别调用方修复方案
仅测试用导出仅在同一文件和测试文件中使用提取至新文件
无效桶式重导出在index.ts中重导出,但生产代码通过相对路径或其他子路径导入从桶文件中移除该重导出
仅内部使用的导出仅在同一文件内使用,未被测试或其他文件引用移除
export
关键字
无效代码无任何调用方删除该导出
存在生产环境调用方被其他文件中的非测试代码使用不属于Knip问题——需进一步排查
重要提示:搜索时排除测试文件,以识别生产环境调用方:
bash
rg "myFunction" --glob '!**/*.test.*'

Fix: Test-Only Exports (Extract to New File)

修复方案:仅测试用导出(提取至新文件)

When a function is exported solely for test access but is also used internally in the same file.
当某个函数仅为测试访问而导出,但同时在同一文件内部被使用时。

Plan the Extraction

规划提取步骤

Before writing code, answer these questions:
a) What moves to the new file?
  • The flagged export function/class/const
  • All private helper functions it depends on
  • All private constants/types it depends on
b) Are any helpers shared with functions staying behind?
  • If yes, the helper must be exported from the new file, and the original file imports it
  • This means the new file will have 2+ exports (which is fine for any filename-match-export lint rule)
c) Will the new file have exactly one exported function?
  • If your project enforces a
    filename-match-export
    lint rule, the file MUST be named after that export:
    myFunction.ts
  • If the file has 2+ function exports, the name is flexible
d) Does a test file with a matching name exist?
  • If
    bar.ts
    stays and
    bar.test.ts
    exists, the test must still import something from
    ./bar
    (if your project enforces a
    test-imports-source
    rule)
  • If
    bar.ts
    is deleted (everything moved out), that rule typically only applies when the matching source file exists
e) Any circular dependency risk?
  • Draw the import graph: new file -> original file -> new file is circular
  • Fix: move the shared dependency to the new file or a third file
f) Does it export a constant?
  • If your project enforces a
    constants-file-organization
    lint rule, exported constants must live in a file named
    constants.ts
  • If the extracted function depends on a constant that other functions in the original file also use, do NOT export the constant from the new file. Instead, call the function (e.g., replace
    BUDGET[effort]
    with
    getBudget(effort)
    ) to avoid needing a separate
    constants.ts
编写代码前,先回答以下问题:
a) 哪些内容需要移至新文件?
  • 被标记的导出函数/类/常量
  • 它依赖的所有私有辅助函数
  • 它依赖的所有私有常量/类型
b) 是否有辅助函数与保留在原文件的函数共享?
  • 如果是,该辅助函数必须从新文件导出,原文件需导入它
  • 这意味着新文件会有2个及以上导出(符合任何「文件名匹配导出」的 lint 规则)
c) 新文件是否仅包含一个导出函数?
  • 如果项目强制执行「文件名匹配导出」的 lint 规则,文件名必须与该导出名称一致:
    myFunction.ts
  • 如果文件包含2个及以上函数导出,文件名可灵活设置
d) 是否存在名称匹配的测试文件?
  • 如果保留
    bar.ts
    且存在
    bar.test.ts
    ,测试文件仍需从
    ./bar
    导入某些内容(如果项目强制执行「测试导入源」规则)
  • 如果
    bar.ts
    被删除(所有内容移出),该规则通常仅在匹配的源文件存在时适用
e) 是否存在循环依赖风险?
  • 绘制导入关系图:新文件 -> 原文件 -> 新文件即为循环依赖
  • 修复方案:将共享依赖移至新文件或第三个文件
f) 是否导出常量?
  • 如果项目强制执行「常量文件组织」的 lint 规则,导出的常量必须放在名为
    constants.ts
    的文件中
  • 如果提取的函数依赖某个常量,且原文件中的其他函数也使用该常量,请勿从新文件导出该常量。而是通过函数调用(例如将
    BUDGET[effort]
    替换为
    getBudget(effort)
    )来避免需要单独的
    constants.ts
    文件

Execute the Extraction

执行提取操作

Create the new file in the same directory:
typescript
// myFunction.ts (new file)
import { SomeType } from '../types';

function privateHelper(): void { /* ... */ }

export function myFunction(): SomeType {
  return privateHelper();
}
Update the original file to import from the new file:
typescript
// bar.ts (original file, updated)
import { myFunction } from './myFunction';

function otherFunction() {
  const result = myFunction(); // Now imports from new file
}
Update test files to import from the new file:
typescript
// bar.test.ts (updated)
import { myFunction } from './myFunction';
// If bar.ts still exists, you may need to also import something from './bar'
// to satisfy any test-imports-source rule
在同一目录下创建新文件:
typescript
// myFunction.ts (新文件)
import { SomeType } from '../types';

function privateHelper(): void { /* ... */ }

export function myFunction(): SomeType {
  return privateHelper();
}
更新原文件,从新文件导入:
typescript
// bar.ts (原文件,已更新)
import { myFunction } from './myFunction';

function otherFunction() {
  const result = myFunction(); // 现在从新文件导入
}
更新测试文件,从新文件导入:
typescript
// bar.test.ts (已更新)
import { myFunction } from './myFunction';
// 如果bar.ts仍然存在,可能还需要从'./bar'导入某些内容
// 以满足「测试导入源」规则

Watch for Chained Violations

注意连锁违规

After extracting, run
npm run knip
again. If function A was extracted to a new file alongside function B that A calls, but B is also only consumed by tests externally, knip will flag B too. You need to extract B to its own file so that A's file creates a genuine production import of B.
Example: suppose
throwMappedError
was first extracted alongside
mapResponseFailure
into
error-mappers.ts
. If
throwMappedError
is only called internally within that file (by
mapResponseFailure
), it will still be flagged. Fix: extract it to
throwMappedError.ts
, making the import from
error-mappers.ts
a genuine production consumer.
提取完成后,再次运行
npm run knip
。如果函数A被提取到新文件,同时函数B也被移至该文件且被A调用,但B仅被外部测试代码使用,Knip会标记B。此时需要将B提取到单独的文件,使A所在文件对B的导入成为真实的生产环境导入。
示例:假设
throwMappedError
最初与
mapResponseFailure
一起被提取到
error-mappers.ts
。如果
throwMappedError
仅在该文件内部被
mapResponseFailure
调用,它仍会被标记。修复方案:将其提取到
throwMappedError.ts
,使
error-mappers.ts
对它的导入成为真实的生产环境调用。

Fix: Dead Barrel Re-Exports (Remove from index.ts)

修复方案:无效桶式重导出(从index.ts中移除)

When a barrel
index.ts
re-exports something, but no production code imports it through the barrel. This happens when:
  • Production code within the same package uses relative imports (e.g.,
    import { x } from './source'
    ) instead of the barrel
  • Production code in other packages imports directly from a subpath (e.g.,
    @scope/pkg/feature/handlers
    ) instead of the barrel
  • The re-export was added speculatively but never consumed
当桶文件
index.ts
重导出某个内容,但没有生产环境代码通过桶文件导入它时会出现这种情况。常见原因:
  • 同一包内的生产环境代码使用相对路径导入(例如
    import { x } from './source'
    )而非桶文件
  • 其他包的生产环境代码直接从子路径导入(例如
    @scope/pkg/feature/handlers
    )而非桶文件
  • 重导出是添加的,但从未被使用

How to Identify

如何识别

Grep excluding test files. If the only hits are:
  • The barrel
    index.ts
    itself
  • Source files using relative imports within the same package
  • Test files
Then the barrel re-export is unused. Simply remove it from
index.ts
.
排除测试文件进行搜索。如果仅有的匹配结果是:
  • 桶文件
    index.ts
    本身
  • 同一包内使用相对路径导入的源文件
  • 测试文件
则该桶式重导出是无效的,只需从
index.ts
中移除它即可。

Cross-Package Test Imports

跨包测试导入

If a test in another package imports the symbol through the barrel (e.g.,
import { x } from '@scope/pkg/feature'
), you need to provide an alternative import path after removing the barrel re-export:
  1. Add a subpath export in the source package's
    package.json
    :
    json
    {
      "exports": {
        "./feature": "./src/feature/index.ts",
        "./feature/doSomething": "./src/feature/doSomething.ts"
      }
    }
  2. Update the test to import from the new subpath:
    typescript
    import { doSomething } from '@scope/pkg/feature/doSomething';
This pattern follows typical subpath-export conventions used in monorepos.
如果其他包中的测试通过桶文件导入该符号(例如
import { x } from '@scope/pkg/feature'
),移除桶式重导出后需要提供替代导入路径:
  1. 在源包的
    package.json
    中添加子路径导出
    json
    {
      "exports": {
        "./feature": "./src/feature/index.ts",
        "./feature/doSomething": "./src/feature/doSomething.ts"
      }
    }
  2. 更新测试文件,从新的子路径导入:
    typescript
    import { doSomething } from '@scope/pkg/feature/doSomething';
此模式遵循 monorepo 中常用的子路径导出约定。

Fix: Internally-Only-Used Exports (Un-export)

修复方案:仅内部使用的导出(取消导出)

When an export is only used within the same file and not imported by anything else (not even tests), just remove the
export
keyword:
typescript
// Before
export const MySchema = z.object({ ... });

// After
const MySchema = z.object({ ... });
This is common for Zod schemas that are only used as building blocks for other schemas in the same file.
当某个导出仅在同一文件内使用,未被任何其他内容(甚至测试)导入时,只需移除
export
关键字:
typescript
// 修复前
export const MySchema = z.object({ ... });

// 修复后
const MySchema = z.object({ ... });
这种情况常见于Zod模式,它们仅作为同一文件中其他模式的构建块使用。

Verify

验证

Run ALL of these checks on the affected packages:
bash
undefined
对受影响的包运行以下所有检查:
bash
undefined

Knip passes (the whole point)

Knip检查通过(核心目标)

npm run knip
npm run knip

Types still compile

类型仍可编译

npm run typecheck
npm run typecheck

Tests still pass

测试仍可通过

npm run test
npm run test

Lint passes (catches filename-match-export, test-imports-source, constants-file-organization, etc.)

Lint检查通过(捕获文件名匹配导出、测试导入源、常量文件组织等问题)

npm run lint

If cross-package imports exist, also verify the consuming package.
npm run lint

如果存在跨包导入,还需验证消费包。

Interacting Lint Rules

相互作用的Lint规则

Many TypeScript monorepos layer additional custom lint rules on top of knip. Adapt the fixes below to whichever of these your project uses.
许多TypeScript monorepo会在Knip之上添加额外的自定义Lint规则。根据项目使用的规则调整以下修复方案。

filename-match-export
(or similar)

filename-match-export
(或类似规则)

If a file has exactly ONE exported function (not a React component), the filename must match the function name.
  • export function loadConfig
    in
    loadConfig.ts
    -- passes
  • export function loadConfig
    in
    helpers.ts
    -- fails
  • Two exports in
    helpers.ts
    -- rule does not apply (multiple exports)
如果文件仅包含一个导出函数(非React组件),文件名必须与函数名匹配。
  • export function loadConfig
    loadConfig.ts
    中——符合规则
  • export function loadConfig
    helpers.ts
    中——违反规则
  • helpers.ts
    中有两个导出——规则不适用(多个导出)

test-imports-source
(or similar)

test-imports-source
(或类似规则)

If
foo.test.ts
and
foo.ts
both exist, the test must import from
./foo
.
  • Imports like
    import { x } from './foo'
    satisfy the rule
  • Typically also accepts importing from
    '.'
    or
    './index'
    if
    index.ts
    re-exports from
    foo.ts
  • If
    foo.ts
    is deleted, the rule does not apply
如果
foo.test.ts
foo.ts
都存在,测试文件必须从
./foo
导入。
  • 类似
    import { x } from './foo'
    的导入符合规则
  • 通常也接受从
    '.'
    './index'
    导入(如果
    index.ts
    foo.ts
    重导出)
  • 如果
    foo.ts
    被删除,规则不适用

constants-file-organization
(or similar)

constants-file-organization
(或类似规则)

Exported constants must be defined in a file named
constants.ts
.
  • If you extract a function that depends on a shared constant, do NOT export the constant from the function's file
  • Instead, replace direct constant access with function calls (e.g.,
    BUDGET[effort]
    becomes
    getBudget(effort)
    )
  • Or move the constant to a
    constants.ts
    file
导出的常量必须定义在名为
constants.ts
的文件中。
  • 如果提取的函数依赖共享常量,请勿从函数所在文件导出该常量
  • 而是将直接常量访问替换为函数调用(例如
    BUDGET[effort]
    改为
    getBudget(effort)
  • 或者将常量移至
    constants.ts
    文件

How Knip Traces Exports

Knip如何追踪导出

  • Knip ignores test files (
    **/*.test.*
    ,
    **/*.spec.*
    )
  • ignoreIssues
    in
    knip.json
    suppresses warnings ON the listed file, but does NOT make the source export "used"
  • Barrel re-exports (
    export { x } from './source'
    ) from an
    index.ts
    with
    ignoreIssues
    do NOT count as usage of the source export
  • Only genuine imports from non-test, non-ignored project files count as usage
  • includeEntryExports: true
    (if set) means exports from entry point files are checked too, so entry-point-style files (migrations, scripts) may need explicit
    ignoreIssues
  • Knip忽略测试文件(
    **/*.test.*
    **/*.spec.*
  • knip.json
    中的
    ignoreIssues
    会抑制列出文件的警告,但不会使源导出被标记为「已使用」
  • 来自
    index.ts
    且带有
    ignoreIssues
    的桶式重导出(
    export { x } from './source'
    )不会被视为源导出的使用
  • 只有来自非测试、非忽略项目文件的真实导入才会被视为使用
  • 如果设置了
    includeEntryExports: true
    ,入口文件的导出也会被检查,因此入口点类型的文件(迁移脚本、CLI)可能需要显式添加
    ignoreIssues

Package Subpath Exports

包子路径导出

When removing barrel re-exports that cross-package tests relied on, add subpath exports to
package.json
:
json
{
  "exports": {
    "./feature": "./src/feature/index.ts",
    "./feature/doSomething": "./src/feature/doSomething.ts"
  }
}
当移除跨包测试依赖的桶式重导出时,需在
package.json
中添加子路径导出:
json
{
  "exports": {
    "./feature": "./src/feature/index.ts",
    "./feature/doSomething": "./src/feature/doSomething.ts"
  }
}

What Not to Do

禁止操作

  • Do not add files to
    ignoreIssues
    in
    knip.json
    unless they are genuine entry point scripts (migrations, CLIs)
  • Do not merge all functions into one file to reduce exports -- same-file usage of an export does not count as usage from knip's perspective
  • Do not remove the
    export
    keyword if tests need it -- the tests would break
  • Do not create circular imports between the new and original files
  • Do not export constants from non-
    constants.ts
    files if your project enforces a
    constants-file-organization
    lint rule
  • 除非是真实的入口点脚本(迁移脚本、CLI),否则不要将文件添加到
    knip.json
    ignoreIssues
  • 不要将所有函数合并到一个文件以减少导出——Knip不将同一文件内对导出的使用视为有效使用
  • 如果测试需要该导出,不要移除
    export
    关键字——否则测试会失败
  • 不要在新文件和原文件之间创建循环依赖
  • 如果项目强制执行「常量文件组织」Lint规则,不要从非
    constants.ts
    文件导出常量