| 1 | import rule from "@effect/oxc/oxlint/rules/no-unused-internal" |
| 2 | import * as fs from "node:fs" |
| 3 | import * as os from "node:os" |
| 4 | import * as path from "node:path" |
| 5 | import { describe, expect, it } from "vitest" |
| 6 | import { createTestContext } from "./utils.ts" |
| 7 | |
| 8 | function writeFixture(files: Record<string, string>): string { |
| 9 | const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "no-unused-internal-rule-")) |
| 10 | for (const [file, source] of Object.entries(files)) { |
| 11 | const fileName = path.join(cwd, file) |
| 12 | fs.mkdirSync(path.dirname(fileName), { recursive: true }) |
| 13 | fs.writeFileSync(fileName, source) |
| 14 | } |
| 15 | return cwd |
| 16 | } |
| 17 | |
| 18 | function run(cwd: string, file: string) { |
| 19 | const filename = path.join(cwd, file) |
| 20 | const source = fs.readFileSync(filename, "utf8") |
| 21 | const { context, errors } = createTestContext({ sourceCode: source, cwd, filename }) |
| 22 | const visitors = rule.create(context as never) |
| 23 | visitors.Program?.({ type: "Program", range: [0, source.length] } as never) |
| 24 | return errors.map((error) => error.message) |
| 25 | } |
| 26 | |
| 27 | describe("no-unused-internal", () => { |
| 28 | it("reports unused top-level @internal exports", () => { |
| 29 | const cwd = writeFixture({ |
| 30 | "packages/foo/src/Internal.ts": `/** @internal */ |
| 31 | export const unused = 1 |
| 32 | ` |
| 33 | }) |
| 34 | |
| 35 | expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([ |
| 36 | `@internal export "unused" is not used by production sources` |
| 37 | ]) |
| 38 | }) |
| 39 | |
| 40 | it("counts same-file references as usage", () => { |
| 41 | const cwd = writeFixture({ |
| 42 | "packages/foo/src/Internal.ts": `/** @internal */ |
| 43 | export const used = 1 |
| 44 | |
| 45 | export const value = used |
| 46 | ` |
| 47 | }) |
| 48 | |
| 49 | expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([]) |
| 50 | }) |
| 51 | |
| 52 | it("counts type-only references as usage", () => { |
| 53 | const cwd = writeFixture({ |
| 54 | "packages/foo/src/Internal.ts": `/** @internal */ |
| 55 | export interface Internal { |
| 56 | readonly value: string |
| 57 | } |
| 58 | `, |
| 59 | "packages/foo/src/Consumer.ts": `import type { Internal } from "./Internal.ts" |
| 60 | |
| 61 | type Local = Internal |
| 62 | ` |
| 63 | }) |
| 64 | |
| 65 | expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([]) |
| 66 | }) |
| 67 | |
| 68 | it("reports @internal exports used in public exported type signatures", () => { |
| 69 | const cwd = writeFixture({ |
| 70 | "packages/foo/src/Internal.ts": `/** @internal */ |
| 71 | export interface Internal { |
| 72 | readonly value: string |
| 73 | } |
| 74 | `, |
| 75 | "packages/foo/src/Public.ts": `import type { Internal } from "./Internal.ts" |
| 76 | |
| 77 | export interface Public { |
| 78 | readonly value: Internal |
| 79 | } |
| 80 | ` |
| 81 | }) |
| 82 | |
| 83 | expect(run(cwd, "packages/foo/src/Public.ts")).toEqual([ |
| 84 | `Do not reference @internal export "Internal" in a public exported type signature` |
| 85 | ]) |
| 86 | expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([]) |
| 87 | }) |
| 88 | |
| 89 | it("reports public re-exports of @internal exports", () => { |
| 90 | const cwd = writeFixture({ |
| 91 | "packages/foo/src/Internal.ts": `/** @internal */ |
| 92 | export const internal = 1 |
| 93 | `, |
| 94 | "packages/foo/src/Public.ts": `export { internal } from "./Internal.ts" |
| 95 | ` |
| 96 | }) |
| 97 | |
| 98 | expect(run(cwd, "packages/foo/src/Public.ts")).toEqual([ |
| 99 | `Do not re-export @internal export "internal" from a public module` |
| 100 | ]) |
| 101 | expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([]) |
| 102 | }) |
| 103 | |
| 104 | it("ignores @internal class member type signatures", () => { |
| 105 | const cwd = writeFixture({ |
| 106 | "packages/foo/src/Internal.ts": `/** @internal */ |
| 107 | export interface Internal { |
| 108 | readonly value: string |
| 109 | } |
| 110 | `, |
| 111 | "packages/foo/src/Public.ts": `import type { Internal } from "./Internal.ts" |
| 112 | |
| 113 | export class Public { |
| 114 | /** @internal */ |
| 115 | getInternal(): Internal { |
| 116 | throw new Error("not implemented") |
| 117 | } |
| 118 | } |
| 119 | ` |
| 120 | }) |
| 121 | |
| 122 | expect(run(cwd, "packages/foo/src/Public.ts")).toEqual([]) |
| 123 | expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([]) |
| 124 | }) |
| 125 | |
| 126 | it("ignores public type signatures in internal directories", () => { |
| 127 | const cwd = writeFixture({ |
| 128 | "packages/foo/src/Internal.ts": `/** @internal */ |
| 129 | export interface Internal { |
| 130 | readonly value: string |
| 131 | } |
| 132 | `, |
| 133 | "packages/foo/src/internal/Consumer.ts": `import type { Internal } from "../Internal.ts" |
| 134 | |
| 135 | export interface PublicInsideInternalDirectory { |
| 136 | readonly value: Internal |
| 137 | } |
| 138 | ` |
| 139 | }) |
| 140 | |
| 141 | expect(run(cwd, "packages/foo/src/internal/Consumer.ts")).toEqual([]) |
| 142 | expect(run(cwd, "packages/foo/src/Internal.ts")).toEqual([]) |
| 143 | }) |
| 144 | }) |