1import rule from "@effect/oxc/oxlint/rules/no-unused-internal"
2import * as fs from "node:fs"
3import * as os from "node:os"
4import * as path from "node:path"
5import { describe, expect, it } from "vitest"
6import { createTestContext } from "./utils.ts"
7
8function 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
18function 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
27describe("no-unused-internal", () => {
28 it("reports unused top-level @internal exports", () => {
29 const cwd = writeFixture({
30 "packages/foo/src/Internal.ts": `/** @internal */
31export 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 */
43export const used = 1
44
45export 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 */
55export interface Internal {
56 readonly value: string
57}
58`,
59 "packages/foo/src/Consumer.ts": `import type { Internal } from "./Internal.ts"
60
61type 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 */
71export interface Internal {
72 readonly value: string
73}
74`,
75 "packages/foo/src/Public.ts": `import type { Internal } from "./Internal.ts"
76
77export 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 */
92export 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 */
107export interface Internal {
108 readonly value: string
109}
110`,
111 "packages/foo/src/Public.ts": `import type { Internal } from "./Internal.ts"
112
113export 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 */
129export interface Internal {
130 readonly value: string
131}
132`,
133 "packages/foo/src/internal/Consumer.ts": `import type { Internal } from "../Internal.ts"
134
135export 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})