-
Notifications
You must be signed in to change notification settings - Fork 637
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TreeFS: Symlink-enabled, prefix-tree implementation of
FileSystem
(#…
…926) Summary: Pull Request resolved: #926 Currently, `HasteFS` is the only implementation of the `FileSystem` interface, used by Metro to check for file existence during resolution, and to retrieve metadata (file hashes, etc) during transformation. `HasteFS` is based on a simple, performant `Map` of real paths to metadata. This diff introduces `TreeFS`, which is based on an in-memory prefix tree representation, split on path separators, of all watched files. By using a prefix tree, we can lookup files whose paths segments are directory symlinks or files which are themselves links to other files. We use `TreeFS` in preference to `HasteFS` when `config.resolver.unstable_enableSymlinks` (default: `false`) is `true`. Reviewed By: motiz88 Differential Revision: D42552999 fbshipit-source-id: 70318ae89a27b560ab1236b147a51031682d3e1f
- Loading branch information
1 parent
a0ba994
commit db7fd66
Showing
6 changed files
with
683 additions
and
125 deletions.
There are no files selected for viewing
136 changes: 136 additions & 0 deletions
136
packages/metro-file-map/src/__tests__/FileSystem-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @flow strict-local | ||
* @format | ||
* @oncall react_native | ||
*/ | ||
|
||
import type {FileMetaData, Path} from '../flow-types'; | ||
let mockPathModule; | ||
jest.mock('path', () => mockPathModule); | ||
jest.mock('../lib/fast_path', () => mockPathModule); | ||
|
||
describe.each([['win32'], ['posix']])( | ||
'FileSystem implementations on %s', | ||
platform => { | ||
beforeAll(() => { | ||
mockPathModule = jest.requireActual<{}>('path')[platform]; | ||
}); | ||
|
||
// Convenience function to write paths with posix separators but convert them | ||
// to system separators | ||
const p: string => string = filePath => | ||
platform === 'win32' | ||
? filePath.replace(/\//g, '\\').replace(/^\\/, 'C:\\') | ||
: filePath; | ||
|
||
describe.each([['TreeFS'], ['HasteFS']])('%s', label => { | ||
let FileSystem; | ||
let fs; | ||
|
||
beforeAll(() => { | ||
jest.resetModules(); | ||
FileSystem = | ||
label === 'HasteFS' | ||
? require('../HasteFS').default | ||
: require('../lib/TreeFS').default; | ||
|
||
fs = new FileSystem({ | ||
rootDir: p('/project'), | ||
files: new Map([ | ||
[p('foo/another.js'), ['', 0, 0, 1, '', '', 0]], | ||
[p('../outside/external.js'), ['', 0, 0, 1, '', '', 0]], | ||
[p('bar.js'), ['', 234, 0, 1, '', '', 0]], | ||
]), | ||
}); | ||
}); | ||
|
||
test('getAllFiles returns all files by absolute path', () => { | ||
expect(fs.getAllFiles().sort()).toEqual([ | ||
p('/outside/external.js'), | ||
p('/project/bar.js'), | ||
p('/project/foo/another.js'), | ||
]); | ||
}); | ||
|
||
test.each([ | ||
p('/outside/external.js'), | ||
p('/project/bar.js'), | ||
p('/project/foo/another.js'), | ||
])('existence check passes: %s', filePath => { | ||
expect(fs.exists(filePath)).toBe(true); | ||
}); | ||
|
||
test('existence check fails for directories', () => { | ||
expect(fs.exists(p('/project/foo'))).toBe(false); | ||
}); | ||
|
||
test('implements linkStats()', () => { | ||
expect(fs.linkStats(p('./bar.js'))).toEqual({ | ||
fileType: 'f', | ||
modifiedTime: 234, | ||
}); | ||
}); | ||
|
||
describe('matchFiles', () => { | ||
test('matches files against a pattern', async () => { | ||
expect( | ||
fs.matchFiles( | ||
mockPathModule.sep === mockPathModule.win32.sep | ||
? /project\\foo/ | ||
: /project\/foo/, | ||
), | ||
).toEqual([p('/project/foo/another.js')]); | ||
}); | ||
}); | ||
|
||
describe('matchFilesWithContext', () => { | ||
test('matches files against context', () => { | ||
const fs = new FileSystem({ | ||
rootDir: p('/root'), | ||
files: new Map<Path, FileMetaData>([ | ||
[p('foo/another.js'), ['', 0, 0, 0, '', '', 0]], | ||
[p('bar.js'), ['', 0, 0, 0, '', '', 0]], | ||
]), | ||
}); | ||
|
||
expect(fs.getAllFiles()).toEqual([ | ||
p('/root/foo/another.js'), | ||
p('/root/bar.js'), | ||
]); | ||
|
||
// Test non-recursive skipping deep paths | ||
expect( | ||
fs.matchFilesWithContext(p('/root'), { | ||
filter: new RegExp( | ||
// Test starting with `./` since this is mandatory for parity with Webpack. | ||
/^\.\/.*/, | ||
), | ||
recursive: false, | ||
}), | ||
).toEqual([p('/root/bar.js')]); | ||
|
||
// Test inner directory | ||
expect( | ||
fs.matchFilesWithContext(p('/root/foo'), { | ||
filter: new RegExp(/.*/), | ||
recursive: true, | ||
}), | ||
).toEqual([p('/root/foo/another.js')]); | ||
|
||
// Test recursive | ||
expect( | ||
fs.matchFilesWithContext(p('/root'), { | ||
filter: new RegExp(/.*/), | ||
recursive: true, | ||
}), | ||
).toEqual([p('/root/foo/another.js'), p('/root/bar.js')]); | ||
}); | ||
}); | ||
}); | ||
}, | ||
); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.