GeezerVison is showing github.com — watch: check parent and child path properly · nodejs/node@6556212
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/watch_mode/files_watcher.js +11-3Lines changed: 11 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 7 | 7 | SafeMap, | 8 | 8 | SafeSet, | 9 | 9 | SafeWeakMap, | | 10 | + StringPrototypeEndsWith, | 10 | 11 | StringPrototypeStartsWith, | 11 | 12 | } = primordials; | 12 | 13 |
| @@ -18,12 +19,19 @@ const EventEmitter = require('events'); | 18 | 19 | const { addAbortListener } = require('internal/events/abort_listener'); | 19 | 20 | const { watch } = require('fs'); | 20 | 21 | const { fileURLToPath } = require('internal/url'); | 21 | | -const { resolve, dirname } = require('path'); | | 22 | +const { resolve, dirname, sep } = require('path'); | 22 | 23 | const { setTimeout, clearTimeout } = require('timers'); | 23 | 24 |
| 24 | 25 | const supportsRecursiveWatching = process.platform === 'win32' || | 25 | 26 | process.platform === 'darwin'; | 26 | 27 |
| | 28 | +const isParentPath = (parentCandidate, childCandidate) => { | | 29 | + const parent = resolve(parentCandidate); | | 30 | + const child = resolve(childCandidate); | | 31 | + const normalizedParent = StringPrototypeEndsWith(parent, sep) ? parent : parent + sep; | | 32 | + return StringPrototypeStartsWith(child, normalizedParent); | | 33 | +}; | | 34 | + | 27 | 35 | class FilesWatcher extends EventEmitter { | 28 | 36 | #watchers = new SafeMap(); | 29 | 37 | #filteredFiles = new SafeSet(); | @@ -58,7 +66,7 @@ class FilesWatcher extends EventEmitter { | 58 | 66 | } | 59 | 67 |
| 60 | 68 | for (const { 0: watchedPath, 1: watcher } of this.#watchers.entries()) { | 61 | | - if (watcher.recursive && StringPrototypeStartsWith(path, watchedPath)) { | | 69 | + if (watcher.recursive && isParentPath(watchedPath, path)) { | 62 | 70 | return true; | 63 | 71 | } | 64 | 72 | } | @@ -68,7 +76,7 @@ class FilesWatcher extends EventEmitter { | 68 | 76 |
| 69 | 77 | #removeWatchedChildren(path) { | 70 | 78 | for (const { 0: watchedPath, 1: watcher } of this.#watchers.entries()) { | 71 | | - if (path !== watchedPath && StringPrototypeStartsWith(watchedPath, path)) { | | 79 | + if (path !== watchedPath && isParentPath(path, watchedPath)) { | 72 | 80 | this.#unwatch(watcher); | 73 | 81 | this.#watchers.delete(watchedPath); | 74 | 82 | } | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-watch-mode-files_watcher.mjs +29-1Lines changed: 29 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -6,7 +6,8 @@ import path from 'node:path'; | 6 | 6 | import assert from 'node:assert'; | 7 | 7 | import process from 'node:process'; | 8 | 8 | import { describe, it, beforeEach, afterEach } from 'node:test'; | 9 | | -import { writeFileSync, mkdirSync } from 'node:fs'; | | 9 | +import { writeFileSync, mkdirSync, appendFileSync } from 'node:fs'; | | 10 | +import { createInterface } from 'node:readline'; | 10 | 11 | import { setTimeout } from 'node:timers/promises'; | 11 | 12 | import { once } from 'node:events'; | 12 | 13 | import { spawn } from 'node:child_process'; | @@ -51,6 +52,33 @@ describe('watch mode file watcher', () => { | 51 | 52 | assert.strictEqual(changesCount, 1); | 52 | 53 | }); | 53 | 54 |
| | 55 | + it('should watch changed files with same prefix path string', async () => { | | 56 | + mkdirSync(tmpdir.resolve('subdir')); | | 57 | + mkdirSync(tmpdir.resolve('sub')); | | 58 | + const file1 = tmpdir.resolve('subdir', 'file1.mjs'); | | 59 | + const file2 = tmpdir.resolve('sub', 'file2.mjs'); | | 60 | + writeFileSync(file2, 'export const hello = () => { return "hello world"; };'); | | 61 | + writeFileSync(file1, 'import { hello } from "../sub/file2.mjs"; console.log(hello());'); | | 62 | + | | 63 | + const child = spawn(process.execPath, | | 64 | + ['--watch', file1], | | 65 | + { stdio: ['ignore', 'pipe', 'ignore'] }); | | 66 | + let completeCount = 0; | | 67 | + for await (const line of createInterface(child.stdout)) { | | 68 | + if (!line.startsWith('Completed running')) { | | 69 | + continue; | | 70 | + } | | 71 | + completeCount++; | | 72 | + if (completeCount === 1) { | | 73 | + appendFileSync(file1, '\n // append 1'); | | 74 | + } | | 75 | + // The file is reloaded due to file watching | | 76 | + if (completeCount === 2) { | | 77 | + child.kill(); | | 78 | + } | | 79 | + } | | 80 | + }); | | 81 | + | 54 | 82 | it('should debounce changes', async () => { | 55 | 83 | const file = tmpdir.resolve('file2'); | 56 | 84 | writeFileSync(file, 'written'); | |
|