Skip to content

Commit

Permalink
Propagate symlink events even if they match ignore patterns
Browse files Browse the repository at this point in the history
Differential Revision: D43214089

fbshipit-source-id: 56e750325c46626c3d4a1c935456a842f5f58799
  • Loading branch information
robhogan authored and facebook-github-bot committed Feb 13, 2023
1 parent c5179e3 commit a45c6d8
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 18 deletions.
114 changes: 111 additions & 3 deletions packages/metro-file-map/src/__tests__/index-test.js
Expand Up @@ -309,7 +309,7 @@ describe('HasteMap', () => {
expect(fileSystem.matchFiles('.git')).toEqual([]);
});

it('warn on ignore pattern except for regex', async () => {
it('throw on ignore pattern except for regex', async () => {
const config = {ignorePattern: 'Kiwi', ...defaultConfig};
mockFs['/project/fruits/Kiwi.js'] = `
// Kiwi!
Expand Down Expand Up @@ -1410,8 +1410,12 @@ describe('HasteMap', () => {
if (options.mockFs) {
mockFs = options.mockFs;
}
const watchConfig = {...defaultConfig, watch: true};
const hm = new HasteMap(watchConfig);
const config = {
...defaultConfig,
watch: true,
...options.config,
};
const hm = new HasteMap(config);
await hm.build();
try {
await fn(hm);
Expand Down Expand Up @@ -1457,6 +1461,12 @@ describe('HasteMap', () => {
size: null,
};

const MOCK_CHANGE_LINK = {
type: 'l',
modifiedTime: 46,
size: 5,
};

const MOCK_CHANGE_FOLDER = {
type: 'd',
modifiedTime: 45,
Expand Down Expand Up @@ -1561,6 +1571,104 @@ describe('HasteMap', () => {
},
);

hm_it(
'does not emit changes for regular files with unwatched extensions',
async hm => {
const {fileSystem} = await hm.build();
mockFs[path.join('/', 'project', 'fruits', 'Banana.unwatched')] = '';

const e = mockEmitters[path.join('/', 'project', 'fruits')];
e.emit(
'all',
'add',
path.join('Banana.js'),
path.join('/', 'project', 'fruits', ''),
MOCK_CHANGE_FILE,
);
e.emit(
'all',
'add',
path.join('Banana.unwatched'),
path.join('/', 'project', 'fruits', ''),
MOCK_CHANGE_FILE,
);
const {eventsQueue} = await waitForItToChange(hm);
const filePath = path.join('/', 'project', 'fruits', 'Banana.js');
expect(eventsQueue).toHaveLength(1);
expect(eventsQueue).toEqual([
{filePath, metadata: MOCK_CHANGE_FILE, type: 'add'},
]);
expect(fileSystem.getModuleName(filePath)).toBeDefined();
},
);

hm_it('does not emit delete events for unknown files', async hm => {
const {fileSystem} = await hm.build();
mockFs[path.join('/', 'project', 'fruits', 'Banana.unwatched')] = '';

const e = mockEmitters[path.join('/', 'project', 'fruits')];
e.emit(
'all',
'delete',
path.join('Banana.js'),
path.join('/', 'project', 'fruits', ''),
null,
);
e.emit(
'all',
'delete',
path.join('Unknown.ext'),
path.join('/', 'project', 'fruits', ''),
null,
);
const {eventsQueue} = await waitForItToChange(hm);
const filePath = path.join('/', 'project', 'fruits', 'Banana.js');
expect(eventsQueue).toHaveLength(1);
expect(eventsQueue).toEqual([
{filePath, metadata: MOCK_DELETE_FILE, type: 'delete'},
]);
expect(fileSystem.getModuleName(filePath)).toBeDefined();
expect(console.warn).not.toHaveBeenCalled();
expect(console.error).not.toHaveBeenCalled();
});

hm_it(
'does emit changes for symlinks with unlisted extensions',
async hm => {
const {fileSystem} = await hm.build();
const e = mockEmitters[path.join('/', 'project', 'fruits')];
mockFs[path.join('/', 'project', 'fruits', 'LinkToStrawberry.ext')] = {
link: 'Strawberry.js',
};
e.emit(
'all',
'add',
path.join('LinkToStrawberry.ext'),
path.join('/', 'project', 'fruits', ''),
MOCK_CHANGE_LINK,
);
const {eventsQueue} = await waitForItToChange(hm);
const filePath = path.join(
'/',
'project',
'fruits',
'LinkToStrawberry.ext',
);
expect(eventsQueue).toHaveLength(1);
expect(eventsQueue).toEqual([
{filePath, metadata: MOCK_CHANGE_LINK, type: 'add'},
]);
const linkStats = fileSystem.linkStats(filePath);
expect(linkStats).toEqual({
fileType: 'l',
modifiedTime: 46,
});
// getModuleName traverses the symlink, verifying the link is read.
expect(fileSystem.getModuleName(filePath)).toEqual('Strawberry');
},
{config: {enableSymlinks: true}},
);

hm_it(
'correctly tracks changes to both platform-specific versions of a single module name',
async hm => {
Expand Down
37 changes: 22 additions & 15 deletions packages/metro-file-map/src/index.js
Expand Up @@ -891,7 +891,9 @@ export default class HasteMap extends EventEmitter {
this._options.throwOnModuleCollision = false;
this._options.retainAllFiles = true;

const extensions = this._options.extensions;
const hasWatchedExtension = (filePath: string) =>
this._options.extensions.some(ext => filePath.endsWith(ext));

const rootDir = this._options.rootDir;

let changeQueue: Promise<null | void> = Promise.resolve();
Expand Down Expand Up @@ -930,15 +932,24 @@ export default class HasteMap extends EventEmitter {
root: Path,
metadata: ?ChangeEventMetadata,
) => {
const absoluteFilePath = path.join(root, normalizePathSep(filePath));
if (
(metadata && metadata.type === 'd') ||
this._ignore(absoluteFilePath) ||
!extensions.some(extension => absoluteFilePath.endsWith(extension))
metadata &&
// Ignore all directory events
(metadata.type === 'd' ||
// Ignore regular files with unwatched extensions
(metadata.type === 'f' && !hasWatchedExtension(filePath)))
) {
return;
}

const absoluteFilePath = path.join(root, normalizePathSep(filePath));

// Ignore files (including symlinks) whose path matches ignorePattern
// (we don't ignore node_modules in watch mode)
if (this._options.ignorePattern.test(absoluteFilePath)) {
return;
}

const relativeFilePath = fastPath.relative(rootDir, absoluteFilePath);
const linkStats = fileSystem.linkStats(relativeFilePath);

Expand Down Expand Up @@ -1034,10 +1045,11 @@ export default class HasteMap extends EventEmitter {
// point.
}
} else if (type === 'delete') {
invariant(
linkStats?.fileType != null,
'delete event received for file of unknown type',
);
if (linkStats == null) {
// Don't emit deletion events for files we weren't retaining.
// This is expected for deletion of an ignored file.
return null;
}
enqueueEvent({
modifiedTime: null,
size: null,
Expand Down Expand Up @@ -1161,12 +1173,7 @@ export default class HasteMap extends EventEmitter {
* Helpers
*/
_ignore(filePath: Path): boolean {
const ignorePattern = this._options.ignorePattern;
const ignoreMatched =
ignorePattern instanceof RegExp
? ignorePattern.test(filePath)
: ignorePattern && ignorePattern(filePath);

const ignoreMatched = this._options.ignorePattern.test(filePath);
return (
ignoreMatched ||
(!this._options.retainAllFiles && filePath.includes(NODE_MODULES))
Expand Down

0 comments on commit a45c6d8

Please sign in to comment.