Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/docs/src/content/docs/reference/jsdoc-tsdoc-tags.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,32 @@ Works identical to [`@public`][9]. Knip ignores other tags like `@alpha` and

[TSDoc: @beta][10]

## `@knip-ignore-until`

Temporarily ignore an unused export until a specified date. After the date
passes, Knip will report the export as unused again.

Example:

```ts
/**
* @knip-ignore-until 2025-04-01
*/
export const temporarilyIgnored = () => {};
```

Use ISO 8601 date format (`YYYY-MM-DD`). This is useful for:

- Exports that will be used in upcoming features
- Temporary workarounds with a planned removal date
- Staged rollouts where code will be enabled later

| Date value | Behavior |
| ---------- | -------- |
| Future date | Ignored (not reported) |
| Past date | Reported as unused |
| Invalid date | Reported as unused |

[1]: ../reference/cli.md#--tags
[2]: ./configuration.md#tags
[3]: ./cli.md#--include-entry-exports
Expand Down
1 change: 1 addition & 0 deletions packages/knip/fixtures/ignore-until/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './module.js';
16 changes: 16 additions & 0 deletions packages/knip/fixtures/ignore-until/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* @knip-ignore-until 2099-12-31
*/
export const ignoredUntilFuture = 1;

/**
* @knip-ignore-until 2020-01-01
*/
export const ignoredUntilPast = 1;

/**
* @knip-ignore-until invalid-date
*/
export const ignoredUntilInvalid = 1;

export const regularUnused = 1;
3 changes: 3 additions & 0 deletions packages/knip/fixtures/ignore-until/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "@fixtures/ignore-until"
}
3 changes: 2 additions & 1 deletion packages/knip/src/typescript/ast-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ export const getJSDocTags = (node: ts.Node) => {
tagNodes = [...tagNodes, ...ts.getJSDocTags(node.parent)];
}
for (const tagNode of tagNodes) {
const match = tagNode.getText()?.match(/@\S+/);
const text = tagNode.getText();
const match = text?.match(/@knip-ignore-until\s+\S+/) ?? text?.match(/@\S+/);
if (match) tags.add(match[0]);
}
return tags;
Expand Down
13 changes: 12 additions & 1 deletion packages/knip/src/util/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,21 @@ export const shouldIgnore = (jsDocTags: Set<string>, tags: Tags) => {
return false;
};

const isIgnoredUntilValid = (jsDocTags: Set<string>) => {
for (const tag of jsDocTags) {
const match = tag.match(/@knip-ignore-until\s+(\S+)/);
if (match && new Date(match[1]) > new Date()) {
return true;
}
}
return false;
};

export const getShouldIgnoreHandler = (isProduction: boolean) => (jsDocTags: Set<string>) =>
jsDocTags.has(PUBLIC_TAG) ||
jsDocTags.has(BETA_TAG) ||
jsDocTags.has(ALIAS_TAG) ||
(isProduction && jsDocTags.has(INTERNAL_TAG));
(isProduction && jsDocTags.has(INTERNAL_TAG)) ||
isIgnoredUntilValid(jsDocTags);

export const getShouldIgnoreTagHandler = (tags: Tags) => (jsDocTags: Set<string>) => shouldIgnore(jsDocTags, tags);
28 changes: 28 additions & 0 deletions packages/knip/test/ignore-until.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { main } from '../src/index.js';
import baseCounters from './helpers/baseCounters.js';
import { createOptions } from './helpers/create-options.js';
import { resolve } from './helpers/resolve.js';

const cwd = resolve('fixtures/ignore-until');

test('Ignore exports until date', async () => {
const options = await createOptions({ cwd });
const { issues, counters } = await main(options);

assert(!issues.exports['module.ts']?.['ignoredUntilFuture']);

assert(issues.exports['module.ts']?.['ignoredUntilPast']);

assert(issues.exports['module.ts']?.['ignoredUntilInvalid']);

assert(issues.exports['module.ts']?.['regularUnused']);

assert.deepEqual(counters, {
...baseCounters,
exports: 3,
processed: 2,
total: 2,
});
});
Loading