microsoft / microsoft/TypeScript

Autocomplete incorrectly suggests transitive files from monorepo package

Open
#63,033 7 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Bug Corsa Domain: LS: Auto-import
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

### 🔎 Search Terms

"autocomplete transitive", "autocomplete export null", "auto-import workspace package", "monorepo auto-import"

### 🕗 Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about "Auto-import Heuristics and Preferences". Notably, I think this line from the FAQ is appropriate:

> It operates under a few key assumptions:
> ...
> 2. It's a bad idea to import things that won't work

### ⏯ Playground Link

This involves several files, so I've included a fourslash test below.

### 💻 Code

Below is a failing fourslash test that I put at `tests/cases/fourslash/server/autoImportProviderTransitiveLeak.ts`:

```ts
///

// @Filename: /home/src/workspaces/project/tsconfig.base.json
//// {
//// "compilerOptions": {
//// "module": "nodenext",
//// "composite": true,
//// "outDir": "${configDir}/dist"
//// }
//// }

// packages/foo

// @Filename: /home/src/workspaces/project/packages/foo/package.json
//// { "name": "@packages/foo", "type": "module", "version": "1.0.0", "exports": { ".": { "types": "./src/index.ts", "default": "./dist/index.js" }, "./internal/*": null } }

// @Filename: /home/src/workspaces/project/packages/foo/tsconfig.json
//// { "extends": "../../tsconfig.base.json" }

// @Filename: /home/src/workspaces/project/packages/foo/src/internal/index.ts
//// export function fooInternal() { console.log("foo"); }

// @Filename: /home/src/workspaces/project/packages/foo/src/index.ts
//// import { fooInternal } from "./internal/index.js"
////
//// export function foo() { fooInternal(); }

// packages/bar

// @Filename: /home/src/workspaces/project/packages/bar/package.json
//// { "name": "@packages/bar", "type": "module", "version": "1.0.0", "exports": { ".": { "types": "./src/index.ts", "default": "./dist/index.js" }, "./internal/*": null }, "dependencies": { "@packages/foo": "*" } }

// @Filename: /home/src/workspaces/project/packages/bar/tsconfig.json
//// { "extends": "../../tsconfig.base.json" }

// @Filename: /home/src/workspaces/project/packages/bar/src/index.ts
//// fo/**/

// npm workspaces

// @Filename: /home/src/workspaces/project/package.json
//// { "workspaces": ["packages/*"], "type": "module" }

// @link: /home/src/workspaces/project/packages/foo -> /home/src/workspaces/project/node_modules/@packages/foo
// @link: /home/src/workspaces/project/packages/bar -> /home/src/workspaces/project/node_modules/@packages/bar

goTo.marker("");

verify.completions({
marker: "",
includes: [
{
name: "foo",
source: "@packages/foo",
sourceDisplay: "@packages/foo",
hasAction: true,
sortText: completion.SortText.AutoImportSuggestions,
},
],
excludes: ["fooInternal"],
preferences: {
includeCompletionsForModuleExports: true,
allowIncompleteCompletions: true,
},
});

```

### 🙁 Actual behavior

When replicating this in a repository, autocomplete suggests `fooInternal` via a relative path import. Accepting this import results in the following code that has a red squiggly on the import line and fails to compile:

```
import { fooInternal } from "../../foo/src/internal/index.js";

fooInternal
```

The error is:
```
File '/packages/foo/src/internal/index.ts' is not under 'rootDir' '/packages/bar/src'. 'rootDir' is expected to contain all source files.
```

### 🙂 Expected behavior

I believe that `fooInternal` should not be suggested because it's "a bad idea to import things that won't work" per the FAQ. This import won't work for two reasons:

1. It is not in the current Typescript project, nor is it referenced.
2. Even if it suggested the package path and not a relative path, it is blocked by the NodeJS exports.

### Additional information about the issue

After doing some initial investigation with Claude to identify the source of the bug, I noticed that `getNodeModulesPackageNameFromFileName` might have theoretically worked here, but it does not because these are symlinks due to being workspace packages, which are resolved to a non-`node_modules` directory. I tried the test with `"preserveSymlinks": true`, however, and the bug still occurred. This appears to be because `AutoImportProviderProject` unconditionally resolves the symlink, which may or may not be appropriate, I'm not sure.

I'm happy to provide more context about _why_ we want `fooInternal` to not be suggested, but I hope it's clear from the reproduction case.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with tests/cases/fourslash/server/autoImportProviderTransitiveLeak.ts and run the failing fourslash test. Trace getNodeModulesPackageNameFromFileName and AutoImportProviderProject while investigating how workspace symlinks and package exports are handled. Done means foo remains suggested from @packages/foo while fooInternal is excluded.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
developer-experience, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
50/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.