Skip to content

Commit 2e699c0

Browse files
committed
feat(lib): createModuleResolutionHost
Signed-off-by: Lexus Drumgold <[email protected]>
1 parent 87bc641 commit 2e699c0

File tree

11 files changed

+471
-9
lines changed

11 files changed

+471
-9
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ import {
8181

8282
This package exports the following identifiers:
8383

84+
- [`createModuleResolutionHost`](./src/lib/create-module-resolution-host.mts)
8485
- [`isResolvedTsconfig`](./src/lib/is-resolved-tsconfig.mts)
8586
- [`isTsconfigHost`](./src/lib/is-tsconfig-host.mts)
8687
- [`loadTsconfig`](./src/lib/load-tsconfig.mts)

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,11 @@
6666
"tsconfig-utils": "./src/internal/fs.browser.mts",
6767
"default": "./dist/internal/fs.browser.mjs"
6868
},
69-
"node": "fs/promises",
70-
"default": "fs/promises"
69+
"node": {
70+
"tsconfig-utils": "./src/internal/fs.node.mts",
71+
"default": "./dist/internal/fs.node.mjs"
72+
},
73+
"default": "./dist/internal/fs.node.mjs"
7174
},
7275
"#internal/*": {
7376
"tsconfig-utils": "./src/internal/*.mts",

src/__snapshots__/index.e2e.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
exports[`e2e:tsconfig-utils > should expose public api 1`] = `
44
[
5+
"createModuleResolutionHost",
56
"isResolvedTsconfig",
67
"isTsconfigHost",
78
"loadTsconfig",

src/interfaces/__tests__/file-system.spec-d.mts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,84 @@ describe('unit-d:interfaces/FileSystem', () => {
1010
it('should extend mlly.FileSystem', () => {
1111
expectTypeOf<TestSubject>().toMatchTypeOf<mlly.FileSystem>()
1212
})
13+
14+
describe('readFile', () => {
15+
type Subject = TestSubject['readFile']
16+
17+
it('should match [this: void]', () => {
18+
expectTypeOf<Subject>().thisParameter.toEqualTypeOf<void>()
19+
})
20+
21+
describe('parameters', () => {
22+
it('should be callable with [mlly.ModuleId]', () => {
23+
expectTypeOf<Subject>().parameters.toEqualTypeOf<[mlly.ModuleId]>()
24+
})
25+
})
26+
27+
describe('returns', () => {
28+
it('should return Buffer | string', () => {
29+
expectTypeOf<Subject>().returns.toEqualTypeOf<Buffer | string>()
30+
})
31+
})
32+
})
33+
34+
describe('readdir', () => {
35+
type Subject = TestSubject['readdir']
36+
37+
it('should match [this: void]', () => {
38+
expectTypeOf<Subject>().thisParameter.toEqualTypeOf<void>()
39+
})
40+
41+
describe('parameters', () => {
42+
it('should be callable with [mlly.ModuleId]', () => {
43+
expectTypeOf<Subject>().parameters.toEqualTypeOf<[mlly.ModuleId]>()
44+
})
45+
})
46+
47+
describe('returns', () => {
48+
it('should return string[]', () => {
49+
expectTypeOf<Subject>().returns.toEqualTypeOf<string[]>()
50+
})
51+
})
52+
})
53+
54+
describe('realpath', () => {
55+
type Subject = TestSubject['realpath']
56+
57+
it('should match [this: void]', () => {
58+
expectTypeOf<Subject>().thisParameter.toEqualTypeOf<void>()
59+
})
60+
61+
describe('parameters', () => {
62+
it('should be callable with [mlly.ModuleId]', () => {
63+
expectTypeOf<Subject>().parameters.toEqualTypeOf<[mlly.ModuleId]>()
64+
})
65+
})
66+
67+
describe('returns', () => {
68+
it('should return string', () => {
69+
expectTypeOf<Subject>().returns.toEqualTypeOf<string>()
70+
})
71+
})
72+
})
73+
74+
describe('stat', () => {
75+
type Subject = TestSubject['stat']
76+
77+
it('should match [this: void]', () => {
78+
expectTypeOf<Subject>().thisParameter.toEqualTypeOf<void>()
79+
})
80+
81+
describe('parameters', () => {
82+
it('should be callable with [mlly.ModuleId]', () => {
83+
expectTypeOf<Subject>().parameters.toEqualTypeOf<[mlly.ModuleId]>()
84+
})
85+
})
86+
87+
describe('returns', () => {
88+
it('should return mlly.Stats', () => {
89+
expectTypeOf<Subject>().returns.toEqualTypeOf<mlly.Stats>()
90+
})
91+
})
92+
})
1393
})

src/interfaces/file-system.mts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,69 @@ import type * as mlly from '@flex-development/mlly'
1212
*
1313
* @extends {mlly.FileSystem}
1414
*/
15-
interface FileSystem extends mlly.FileSystem {}
15+
interface FileSystem extends mlly.FileSystem {
16+
/**
17+
* Get the contents of a file.
18+
*
19+
* @see {@linkcode Buffer}
20+
* @see {@linkcode mlly.ModuleId}
21+
*
22+
* @override
23+
*
24+
* @this {void}
25+
*
26+
* @param {mlly.ModuleId} id
27+
* The path or `file:` URL to handle
28+
* @return {Buffer | string}
29+
* File contents
30+
*/
31+
readFile(this: void, id: mlly.ModuleId): Buffer | string
1632

33+
/**
34+
* Read the contents of a directory.
35+
*
36+
* @see {@linkcode mlly.ModuleId}
37+
*
38+
* @this {void}
39+
*
40+
* @param {mlly.ModuleId} id
41+
* The path or `file:` URL to handle
42+
* @return {string[]}
43+
* List of filenames
44+
*/
45+
readdir(this: void, id: mlly.ModuleId): string[]
46+
47+
/**
48+
* Get the canonical pathname of `id`.
49+
*
50+
* @see {@linkcode mlly.ModuleId}
51+
*
52+
* @override
53+
*
54+
* @this {void}
55+
*
56+
* @param {mlly.ModuleId} id
57+
* The path or `file:` URL to handle
58+
* @return {string}
59+
* Canonical pathname of `id`
60+
*/
61+
realpath(this: void, id: mlly.ModuleId): string
62+
63+
/**
64+
* Get information about a directory or file.
65+
*
66+
* @see {@linkcode mlly.ModuleId}
67+
* @see {@linkcode mlly.Stats}
68+
*
69+
* @override
70+
*
71+
* @this {void}
72+
*
73+
* @param {mlly.ModuleId} id
74+
* The path or `file:` URL to handle
75+
* @return {mlly.Stats}
76+
* Info about `id`
77+
*/
78+
stat(this: void, id: mlly.ModuleId): mlly.Stats
79+
}
1780
export type { FileSystem as default }

src/interfaces/host-module-resolution.mts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,14 @@ interface ModuleResolutionHost {
7272
* @this {void}
7373
*
7474
* @param {ModuleId} id
75-
* Module id of file
75+
* The file path or URL to read
7676
* @return {Buffer | string}
7777
* File contents or `undefined` if file does not exist at `id`
7878
*/
7979
readFile(this: void, id: ModuleId): string | undefined
8080

8181
/**
82-
* Get the resolved pathname for `id`.
82+
* Get the canonical pathname of `id`.
8383
*
8484
* @see {@linkcode ModuleId}
8585
*
@@ -88,7 +88,7 @@ interface ModuleResolutionHost {
8888
* @param {ModuleId} id
8989
* The path or `file:` URL to handle
9090
* @return {string}
91-
* Resolved pathname
91+
* Canonical pathname of `id`
9292
*/
9393
realpath(this: void, id: ModuleId): string
9494

src/internal/fs.browser.mts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { FileSystem } from '@flex-development/tsconfig-utils'
1212
*/
1313
const fs: FileSystem = {
1414
/**
15-
* Get the contents of a file.
15+
* Synchronously get the contents of a file.
1616
*
1717
* @return {never}
1818
* Never; not implemented
@@ -23,7 +23,18 @@ const fs: FileSystem = {
2323
},
2424

2525
/**
26-
* Get the resolved pathname of a file.
26+
* Synchronously read the contents of a directory.
27+
*
28+
* @return {never}
29+
* Never; not implemented
30+
* @throws {Error}
31+
*/
32+
readdir(): never {
33+
throw new Error('[readdir] not implemented')
34+
},
35+
36+
/**
37+
* Get the canonical pathname of a directory or file.
2738
*
2839
* @return {never}
2940
* Never; not implemented
@@ -34,7 +45,7 @@ const fs: FileSystem = {
3445
},
3546

3647
/**
37-
* Get information about a file.
48+
* Synchronously get information about a directory or file.
3849
*
3950
* @return {never}
4051
* Never; not implemented

src/internal/fs.node.mts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @file Internal - fs/node
3+
* @module mkbuild/internal/fs/node
4+
*/
5+
6+
import type { FileSystem } from '@flex-development/tsconfig-utils'
7+
import { readdirSync, readFileSync, realpathSync, statSync } from 'node:fs'
8+
9+
/**
10+
* File system API.
11+
*
12+
* @const {FileSystem} fs
13+
*/
14+
const fs: FileSystem = {
15+
readFile: readFileSync,
16+
readdir: readdirSync,
17+
realpath: realpathSync,
18+
stat: statSync
19+
}
20+
21+
export default fs
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* @file Unit Tests - createModuleResolutionHost
3+
* @module tsconfig-utils/lib/tests/unit/createModuleResolutionHost
4+
*/
5+
6+
import testSubject from '#lib/create-module-resolution-host'
7+
import * as mlly from '@flex-development/mlly'
8+
import pathe from '@flex-development/pathe'
9+
import type { ModuleResolutionHost } from '@flex-development/tsconfig-utils'
10+
import ts from 'typescript'
11+
12+
describe('unit:lib/createModuleResolutionHost', () => {
13+
let subject: ModuleResolutionHost
14+
15+
beforeAll(() => {
16+
subject = testSubject()
17+
})
18+
19+
describe('host', () => {
20+
describe('directoryExists', () => {
21+
it.each<Parameters<ModuleResolutionHost['directoryExists']>>([
22+
[import.meta.url],
23+
[new URL('node:fs')]
24+
])('should return `false` if `id` is not a directory (%j)', id => {
25+
expect(subject.directoryExists(id)).to.be.false
26+
})
27+
28+
it.each<Parameters<ModuleResolutionHost['directoryExists']>>([
29+
[String(pathe.cwd())],
30+
[new URL(mlly.cwd())]
31+
])('should return `true` if `id` is a directory (%j)', id => {
32+
expect(subject.directoryExists(id)).to.be.true
33+
})
34+
})
35+
36+
describe('fileExists', () => {
37+
it.each<Parameters<ModuleResolutionHost['fileExists']>>([
38+
['package-lock.json'],
39+
[String(pathe.cwd())],
40+
[new URL(mlly.cwd())]
41+
])('should return `false` if `id` is not a file (%j)', id => {
42+
expect(subject.fileExists(id)).to.be.false
43+
})
44+
45+
it.each<Parameters<ModuleResolutionHost['fileExists']>>([
46+
['package.json'],
47+
[String(new URL('vitest.config.mts', mlly.cwd()))],
48+
[import.meta.url]
49+
])('should return `true` if `id` is a file (%j)', id => {
50+
expect(subject.fileExists(id)).to.be.true
51+
})
52+
})
53+
54+
describe('getCurrentDirectory', () => {
55+
it('should return path to current working directory', () => {
56+
expect(subject.getCurrentDirectory()).to.eq(pathe.cwd() + pathe.sep)
57+
})
58+
})
59+
60+
describe('getDirectories', () => {
61+
it('should return list of subdirectory names', () => {
62+
// Arrange
63+
const id: string = pathe.dirname(pathe.dirname(import.meta.url))
64+
const path: string = pathe.fileURLToPath(id)
65+
66+
// Act + Expect
67+
expect(subject.getDirectories(id)).to.eql(ts.sys.getDirectories(path))
68+
})
69+
})
70+
71+
describe('readFile', () => {
72+
it('should return `undefined` if file does not exist at `id`', () => {
73+
// Arrange
74+
const id: string = 'src/index.cts'
75+
76+
// Act + Expect
77+
expect(subject.readFile(id)).to.eq(ts.sys.readFile(id)).and.be.undefined
78+
})
79+
80+
it.each<Parameters<ModuleResolutionHost['readFile']>>([
81+
['src/index.mts'],
82+
[import.meta.url]
83+
])('should return contents of file at `id` (%j)', id => {
84+
// Arrange
85+
const path: string = pathe.toPath(id)
86+
87+
// Act
88+
const result = subject.readFile(id)
89+
90+
// Expect
91+
expect(result).to.eq(ts.sys.readFile(path)).and.not.be.undefined
92+
})
93+
})
94+
95+
describe('realpath', () => {
96+
it.each<Parameters<ModuleResolutionHost['realpath']>>([
97+
['src/lib/..'],
98+
[pathe.dirname(import.meta.url) + pathe.sep + pathe.dot.repeat(2)]
99+
])('should return canonical pathname of `id` (%j)', id => {
100+
// Arrange
101+
const path: string = pathe.toPath(id)
102+
103+
// Act
104+
const result = subject.realpath(id)
105+
106+
// Expect
107+
expect(result).to.eq(ts.sys.realpath!(path)).and.not.be.undefined
108+
})
109+
})
110+
})
111+
})

0 commit comments

Comments
 (0)