Skip to content

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

@stormslowly

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions