Skip to content

Commit 6fc5d2f

Browse files
authored
Merge pull request #17 from konard/issue-16-c3d3b6fd8be8
fix(app): use process.cwd() as default rootDir in Babel plugin
2 parents 5a71764 + dda6d5a commit 6fc5d2f

2 files changed

Lines changed: 135 additions & 1 deletion

File tree

packages/app/src/shell/babel-plugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const getContextFromState = (state: BabelState): JsxTaggerContext | null => {
5959
}
6060

6161
// Compute relative path from root using Effect's Path service
62-
const rootDir = state.opts?.rootDir ?? state.cwd ?? ""
62+
const rootDir = state.opts?.rootDir ?? state.cwd ?? process.cwd()
6363
const relativeFilename = computeRelativePath(rootDir, filename)
6464
const attributeName = state.opts?.attributeName ?? componentPathAttributeName
6565

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { type TransformOptions, transformSync } from "@babel/core"
2+
import { describe, expect, it } from "@effect/vitest"
3+
import { Effect } from "effect"
4+
import path from "node:path"
5+
6+
import { componentTaggerBabelPlugin, type ComponentTaggerBabelPluginOptions } from "../../src/shell/babel-plugin.js"
7+
8+
// CHANGE: add tests for rootDir fallback to process.cwd().
9+
// WHY: ensure correct relative path computation when rootDir and cwd are missing.
10+
// QUOTE(ТЗ): "При отсутствии rootDir и cwd относительный путь корректный (от process.cwd()). Добавить тест/fixture для этого случая."
11+
// REF: issue-16
12+
// SOURCE: n/a
13+
// FORMAT THEOREM: ∀ state: state.opts.rootDir = undefined ∧ state.cwd = undefined → rootDir = process.cwd()
14+
// PURITY: SHELL
15+
// EFFECT: Effect<void, never, never>
16+
// INVARIANT: plugin returns valid Babel PluginObj and computes correct relative paths
17+
// COMPLEXITY: O(1)/O(1)
18+
19+
/**
20+
* Helper function to transform JSX code with the component tagger plugin.
21+
*
22+
* @param code - JSX source code to transform
23+
* @param filename - Absolute path to the file being transformed
24+
* @param options - Optional plugin configuration
25+
* @param cwd - Optional Babel working directory
26+
* @returns Transformed code result
27+
*
28+
* @pure false - performs Babel transformation
29+
* @complexity O(n) where n = code length
30+
*/
31+
const transformJsx = (
32+
code: string,
33+
filename: string,
34+
options?: ComponentTaggerBabelPluginOptions,
35+
cwd?: string
36+
): ReturnType<typeof transformSync> => {
37+
const transformOptions: TransformOptions = {
38+
cwd,
39+
filename,
40+
parserOpts: {
41+
plugins: ["jsx", "typescript"]
42+
},
43+
plugins: options === undefined ? [componentTaggerBabelPlugin] : [[componentTaggerBabelPlugin, options]]
44+
}
45+
46+
return transformSync(code, transformOptions)
47+
}
48+
49+
/**
50+
* Helper function to verify transformed code contains expected path.
51+
*
52+
* @param result - Babel transform result
53+
* @param expectedPath - Expected relative path in the path attribute
54+
*
55+
* @pure true - only performs assertions
56+
* @complexity O(1)
57+
*/
58+
const expectPathAttribute = (result: ReturnType<typeof transformSync>, expectedPath: string): void => {
59+
expect(result).not.toBeNull()
60+
expect(result?.code).toBeDefined()
61+
expect(result?.code).toContain(`path="${expectedPath}:`)
62+
}
63+
64+
describe("babel-plugin", () => {
65+
it.effect("creates a valid Babel plugin object", () =>
66+
Effect.sync(() => {
67+
const plugin = componentTaggerBabelPlugin()
68+
69+
expect(plugin).toHaveProperty("name")
70+
expect(plugin).toHaveProperty("visitor")
71+
expect(plugin.name).toBe("component-path-babel-tagger")
72+
expect(typeof plugin.visitor).toBe("object")
73+
}))
74+
75+
it.effect("exports default plugin factory", () =>
76+
Effect.gen(function*() {
77+
const module = yield* Effect.tryPromise(() => import("../../src/shell/babel-plugin.js"))
78+
const defaultExport = module.default
79+
80+
expect(typeof defaultExport).toBe("function")
81+
82+
const plugin = defaultExport()
83+
expect(plugin).toHaveProperty("name")
84+
expect(plugin.name).toBe("component-path-babel-tagger")
85+
}))
86+
87+
it.effect("uses process.cwd() when rootDir and cwd are missing", () =>
88+
Effect.sync(() => {
89+
const code = "const App = () => { return <div>Hello</div> }"
90+
const testFilename = path.resolve(process.cwd(), "src/TestComponent.tsx")
91+
92+
const result = transformJsx(code, testFilename)
93+
94+
expectPathAttribute(result, "src/TestComponent.tsx")
95+
}))
96+
97+
it.effect("uses state.cwd when rootDir is missing", () =>
98+
Effect.sync(() => {
99+
const code = "const App = () => { return <div>Hello</div> }"
100+
const customCwd = "/custom/working/directory"
101+
const testFilename = path.resolve(customCwd, "src/TestComponent.tsx")
102+
103+
const result = transformJsx(code, testFilename, undefined, customCwd)
104+
105+
expectPathAttribute(result, "src/TestComponent.tsx")
106+
}))
107+
108+
it.effect("prefers explicit rootDir option", () =>
109+
Effect.sync(() => {
110+
const code = "const App = () => { return <div>Hello</div> }"
111+
const customRoot = "/custom/root"
112+
const testFilename = path.resolve(customRoot, "components/TestComponent.tsx")
113+
114+
const result = transformJsx(code, testFilename, { rootDir: customRoot })
115+
116+
expectPathAttribute(result, "components/TestComponent.tsx")
117+
}))
118+
119+
it.effect("skips non-JSX files", () =>
120+
Effect.sync(() => {
121+
const code = "const value = 42"
122+
const testFilename = path.resolve(process.cwd(), "src/utils.ts")
123+
124+
const result = transformSync(code, {
125+
filename: testFilename,
126+
parserOpts: { plugins: ["typescript"] },
127+
plugins: [componentTaggerBabelPlugin]
128+
})
129+
130+
expect(result).not.toBeNull()
131+
expect(result?.code).toBeDefined()
132+
expect(result?.code).not.toContain("path=\"")
133+
}))
134+
})

0 commit comments

Comments
 (0)