Skip to content

is_dir misses missing_dependency when path exists as a non-directory (cold-path bug, diverges from enhanced-resolve) #239

Description

@stormslowly

Summary

CachedPathImpl::is_dir (in src/cache.rs) only calls ctx.add_missing_dependency when fs.metadata() returns None (path absent). When the path exists but is not a directory (e.g., a regular file named node_modules), is_dir returns false without recording anything to missing_dependencies. This diverges from enhanced-resolve, which records the path in missingDependencies either way so that webpack/rspack watchers re-run resolution when the file/dir type flips.

Repro

Fixture:

/a/b/c/some.js                          (file)
/a/b/node_modules                       (REGULAR FILE — not a dir)
/a/node_modules/module/index.js         (file inside real node_modules)

Resolve module from /a/b/c.

enhanced-resolve (5.21.1) missingDependencies includes:

  • /a/b/c/node_modules (absent)
  • /a/b/node_modules (exists as file — present)

rspack-resolver cold path missing_dependencies includes:

  • /a/b/c/node_modules (absent)
  • (no entry for /a/b/node_modules)missing

Why this matters

  • Webpack/rspack file watchers won't be notified when the user later replaces /a/b/node_modules (a file) with a real directory, so resolution stays stale and incremental rebuilds don't pick up the newly-installed package.
  • It also leaves a cold/warm divergence in PR fix(cache): replay missing_dependency tracking in cached_node_modules warm path #236: that PR's warm path adds the entry (because the OnceLock folds "absent" and "non-dir" into the same None), while the cold path still doesn't.

Suggested fix

In src/cache.rs, is_dir:

pub async fn is_dir<Fs: Send + Sync + FileSystem>(&self, fs: &Fs, ctx: &mut Ctx) -> bool {
  match self.meta(fs).await {
    Some(meta) if meta.is_dir => true,
    _ => {
      ctx.add_missing_dependency(self);
      false
    }
  }
}

Needs auditing for over-tracking impact on other call sites (e.g., find_package_json's parent-walk loop), but the node_modules walk is the load-bearing case.

Test

Extend src/tests/dependencies.rs::warm_cache_missing_dependencies fixture with a regular file named node_modules at some ancestor, then assert both cold and warm missing_dependencies contain it (and remain equal).

Related: #236

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions