1import rule from "@effect/oxc/oxlint/rules/no-import-from-barrel-package"
2import { describe, expect, it } from "vitest"
3import { runRule } from "./utils.ts"
4
5describe("no-import-from-barrel-package", () => {
6 const testOptions = {
7 filename: "/test/file.ts",
8 cwd: "/test",
9 ruleOptions: [{ checkPatterns: ["^effect$"] }]
10 }
11
12 const createImportDeclaration = (
13 source: string,
14 specifiers: Array<unknown>,
15 importKind?: "type" | "value"
16 ) => {
17 const base = {
18 type: "ImportDeclaration" as const,
19 source: { value: source },
20 specifiers,
21 range: [0, 50] as [number, number]
22 }
23 return importKind !== undefined ? { ...base, importKind } : base
24 }
25
26 const createNamedSpecifier = (
27 name: string,
28 local?: string,
29 importKind?: "type" | "value"
30 ) => {
31 const base = {
32 type: "ImportSpecifier" as const,
33 imported: { type: "Identifier" as const, name },
34 local: { name: local ?? name }
35 }
36 return importKind !== undefined ? { ...base, importKind } : base
37 }
38
39 it("should not report for imports from non-barrel packages", () => {
40 const node = createImportDeclaration("effect/Effect", [
41 createNamedSpecifier("Effect")
42 ])
43 const errors = runRule(rule, "ImportDeclaration", node, testOptions)
44 expect(errors).toHaveLength(0)
45 })
46
47 it("should report for named imports from effect barrel", () => {
48 const node = createImportDeclaration("effect", [
49 createNamedSpecifier("Effect")
50 ])
51 const errors = runRule(rule, "ImportDeclaration", node, testOptions)
52 expect(errors).toHaveLength(1)
53 expect(errors[0].message).toBe(
54 `Use import * as Effect from "effect/Effect" instead`
55 )
56 })
57
58 it("should report for multiple named imports from effect barrel", () => {
59 const node = createImportDeclaration("effect", [
60 createNamedSpecifier("Effect"),
61 createNamedSpecifier("Option"),
62 createNamedSpecifier("Either")
63 ])
64 const errors = runRule(rule, "ImportDeclaration", node, testOptions)
65 expect(errors).toHaveLength(3)
66 })
67
68 it("should not report for type-only import declarations", () => {
69 const node = createImportDeclaration(
70 "effect",
71 [createNamedSpecifier("Effect")],
72 "type"
73 )
74 const errors = runRule(rule, "ImportDeclaration", node, testOptions)
75 expect(errors).toHaveLength(0)
76 })
77
78 it("should not report for type imports within named specifiers", () => {
79 const node = createImportDeclaration("effect", [
80 createNamedSpecifier("Effect", "Effect", "type")
81 ])
82 const errors = runRule(rule, "ImportDeclaration", node, testOptions)
83 expect(errors).toHaveLength(0)
84 })
85
86 it("should handle aliased imports", () => {
87 const node = createImportDeclaration("effect", [
88 createNamedSpecifier("Effect", "Eff")
89 ])
90 const errors = runRule(rule, "ImportDeclaration", node, testOptions)
91 expect(errors).toHaveLength(1)
92 expect(errors[0].message).toBe(
93 `Use import * as Eff from "effect/Effect" instead`
94 )
95 })
96
97 it("should report for namespace imports from barrel", () => {
98 const node = createImportDeclaration("effect", [
99 {
100 type: "ImportNamespaceSpecifier",
101 local: { name: "Effect" }
102 }
103 ])
104 const errors = runRule(rule, "ImportDeclaration", node, testOptions)
105 expect(errors).toHaveLength(1)
106 expect(errors[0].message).toBe(
107 `Do not use namespace import from barrel file "effect", import from specific modules instead`
108 )
109 })
110
111 it("should not report for namespace imports from module paths", () => {
112 const node = createImportDeclaration("effect/Effect", [
113 {
114 type: "ImportNamespaceSpecifier",
115 local: { name: "Effect" }
116 }
117 ])
118 const errors = runRule(rule, "ImportDeclaration", node, testOptions)
119 expect(errors).toHaveLength(0)
120 })
121
122 it("should not report for default imports", () => {
123 const node = createImportDeclaration("effect", [
124 {
125 type: "ImportDefaultSpecifier",
126 local: { name: "Effect" }
127 }
128 ])
129 const errors = runRule(rule, "ImportDeclaration", node, testOptions)
130 expect(errors).toHaveLength(0)
131 })
132
133 describe("configuration", () => {
134 it("should match regex patterns", () => {
135 const node = createImportDeclaration("@myorg/utils", [
136 createNamedSpecifier("helper")
137 ])
138 // No patterns configured - doesn't match
139 const defaultErrors = runRule(rule, "ImportDeclaration", node, testOptions)
140 expect(defaultErrors).toHaveLength(0)
141
142 // Pattern matches @myorg/*
143 const customErrors = runRule(rule, "ImportDeclaration", node, {
144 ...testOptions,
145 ruleOptions: [{ checkPatterns: ["^@myorg/"] }]
146 })
147 expect(customErrors).toHaveLength(1)
148 })
149
150 it("should match exact package with pattern", () => {
151 const node = createImportDeclaration("lodash", [
152 createNamedSpecifier("map")
153 ])
154 // No patterns - doesn't match
155 const defaultErrors = runRule(rule, "ImportDeclaration", node, testOptions)
156 expect(defaultErrors).toHaveLength(0)
157
158 // Exact match pattern
159 const customErrors = runRule(rule, "ImportDeclaration", node, {
160 ...testOptions,
161 ruleOptions: [{ checkPatterns: ["^lodash$"] }]
162 })
163 expect(customErrors).toHaveLength(1)
164 })
165
166 it("should disable relative index checking when configured", () => {
167 const node = createImportDeclaration("./index.ts", [
168 createNamedSpecifier("foo")
169 ])
170 // Default checks relative imports
171 const defaultErrors = runRule(rule, "ImportDeclaration", node, testOptions)
172 expect(defaultErrors).toHaveLength(1)
173
174 // Disabled relative checking
175 const customErrors = runRule(rule, "ImportDeclaration", node, {
176 ...testOptions,
177 ruleOptions: [{ checkRelativeIndexImports: false }]
178 })
179 expect(customErrors).toHaveLength(0)
180 })
181 })
182})