microsoft / microsoft/TypeScript
Autocomplete incorrectly suggests transitive files from monorepo package
Nobody has claimed this yet.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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