Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

func TestAutoImportPackageJsonImportsHashSlashNodenext(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: /tsconfig.json
{
"compilerOptions": {
"module": "nodenext",
"rootDir": "./",
"outDir": "build"
}
}
// @Filename: /package.json
{
"imports": {
"#/*": {
"types": "./src/*",
"default": "./src/*"
}
}
}
// @Filename: /src/domain/entities/entity.ts
export const entity = 1;
// @Filename: /src/features/deep/consumer.ts
entit/**/`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.BaselineAutoImportsCompletions(t, []string{""})
}

func TestAutoImportPackageJsonImportsHashSlashNode16(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: /tsconfig.json
{
"compilerOptions": {
"module": "node16"
}
}
// @Filename: /package.json
{
"imports": {
"#/*": "./src/*"
}
}
// @Filename: /src/domain/entities/entity.ts
export const entity = 1;
// @Filename: /src/consumer.ts
entit/**/`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.BaselineAutoImportsCompletions(t, []string{""})
}
5 changes: 4 additions & 1 deletion internal/modulespecifiers/specifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1031,9 +1031,12 @@ func tryGetModuleNameFromPackageJsonImports(
top := imports.AsObject()
entries := top.Entries()
for k, value := range entries {
if !strings.HasPrefix(k, "#") || k == "#" || strings.HasPrefix(k, "#/") {
if k == "#" || k == "#/" || !strings.HasPrefix(k, "#") {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is surprising - does the blog post need to be updated?

Image

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, --moduleResolution node20 doesn't exist yet. Everything else is right.

Copy link
Copy Markdown
Member

@DanielRosenwasser DanielRosenwasser Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this logic says that "#" and "#/" are invalid entries in "imports" - so am I misunderstanding?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are, aren't they? You need something after that prefix.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, to be explicit - the blog post example uses a bare # entry, which is not being used as a prefix.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Omg, sorry, I totally missed that. I was only focused on the highlighted text. Yes, the blog post needs to be updated:

Developer/eg/imports                                                                              v24.14.0
❯ node index.js
node:internal/modules/esm/resolve:698
    throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base));
          ^

TypeError [ERR_INVALID_MODULE_SPECIFIER]: Invalid module "#" is not a valid internal imports specifier name imported from /Users/andrew/Developer/eg/imports/index.js
    at packageImportsResolve (node:internal/modules/esm/resolve:698:11)
    at moduleResolve (node:internal/modules/esm/resolve:848:16)
    at defaultResolve (node:internal/modules/esm/resolve:991:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:719:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:736:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:765:52)
    at #resolve (node:internal/modules/esm/loader:701:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:621:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:160:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:245:17) {
  code: 'ERR_INVALID_MODULE_SPECIFIER'
}

Node.js v24.14.0

Developer/eg/imports                                                                              v24.14.0
❯ node index.js
node:internal/modules/esm/resolve:698
    throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base));
          ^

TypeError [ERR_INVALID_MODULE_SPECIFIER]: Invalid module "#/" is not a valid internal imports specifier name imported from /Users/andrew/Developer/eg/imports/index.js
    at packageImportsResolve (node:internal/modules/esm/resolve:698:11)
    at moduleResolve (node:internal/modules/esm/resolve:848:16)
    at defaultResolve (node:internal/modules/esm/resolve:991:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:719:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:736:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:765:52)
    at #resolve (node:internal/modules/esm/loader:701:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:621:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:160:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:245:17) {
  code: 'ERR_INVALID_MODULE_SPECIFIER'
}

Node.js v24.14.0

continue // invalid imports entry
}
if strings.HasPrefix(k, "#/") && options.GetModuleResolutionKind() != core.ModuleResolutionKindNodeNext && options.GetModuleResolutionKind() != core.ModuleResolutionKindBundler {
continue // "#/" imports keys are only valid in nodenext/bundler
}
mode := MatchingModeExact
if strings.HasSuffix(k, "/") {
mode = MatchingModeDirectory
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// === Auto Imports ===
```ts
// @FileName: /src/consumer.ts
entit/**/
``````ts
import { entity } from "./domain/entities/entity";

entit
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// === Auto Imports ===
```ts
// @FileName: /src/features/deep/consumer.ts
entit/**/
``````ts
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems broken, and the next like is missing a file path - what's going on here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These baselines have always been missing a newline. I'll fix them all in a separate PR but the baseline still shows everything is working.

import { entity } from "#/domain/entities/entity.js";

entit
```

Loading