Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add a failing test case with types: null condition #54096

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Andarist
Copy link
Contributor

@Andarist Andarist commented May 2, 2023

This is a test case related to this bug: #50762 .

It feels that this situation is specific-enough to be fixable regardless of what is decided for #50762 , cc @andrewbranch

@typescript-bot typescript-bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label May 2, 2023
@typescript-bot
Copy link
Collaborator

This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise.

@@ -0,0 +1,28 @@
error TS6504: File '/node_modules/foo/index.js' is a JavaScript file. Did you mean to enable the 'allowJs' option?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

i'm not sure how to avoid this in the test setup without allowJs: true (which I would prefer to avoid here).

Comment on lines +25 to +26
==== /a.ts (0 errors) ====
export { a } from "foo";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I feel like this should error because of the quite explicit types: null declaration in the package

@andrewbranch
Copy link
Member

I agree that this is #50762 which makes it a bug, and I agree that it’s narrow enough to be fixable on its own, and at first glance at the conversation at discordjs/discord.js#9485 it even feels useful. But thinking about it a bit more, it feels a little suspicious to try to forbid usage of a package subpath by derailing TypeScript specifically. In my view, TypeScript’s job is to know how the runtime/bundler is going to resolve, and then to find types for the implementation file the runtime/bundler finds. Sometimes, though, you need to sacrifice a bit of the former in service of the latter—that is, if your types aren’t colocated with implementation files, you grab TypeScript with the "types" condition earlier in the process and say “look, use this file over here, and just trust me that it’s a representation of what the runtime/bundler will resolve to.” But the usage you suggest in the discord.js conversation would make the "types" condition a lie. I think it would be better for them to use other nested conditions, or move the subpath to "imports", or whatever, in order to make that subpath effectively private to consumers, which seems to be the effect they’re going for.

@Andarist
Copy link
Contributor Author

Andarist commented May 2, 2023

Yeah, I overall agree with you - it's fishy to disallow something by disallowing types specifically. I don't know enough about their use case to suggest them a better way though. It just came up there since they thought that by not including types condition for this entrypoint they would forbid that at type-level.

However, because of the "used fallback condition" bug it didn't actually end up being forbidden there and even a more explicitly expressed intent (types: null) doesn't do that because it's still suffers from the same "used fallback condition" bug.

This particular PR is more about the fact that this should work and not as much about if it should be used 😉

@Andarist
Copy link
Contributor Author

Andarist commented May 2, 2023

It's also very likely that this particular manifestation of this bug makes weird combinations to behave incorrectly, like import: null, require: './index.js'. Or perhaps even stuff like node: null, import: './index.mjs'.

@andrewbranch
Copy link
Member

Do you want to see what the narrow fix for this would look like? If we don’t have to jump through hoops to fix null without fixing the rest of #50762, I’m not against it.

@Andarist
Copy link
Contributor Author

Andarist commented May 2, 2023

Do you want to see what the narrow fix for this would look like?

Do I want to? Not really. Will I investigate this? Yeeeah, that's very likely.

@andrewbranch
Copy link
Member

I’m going to steal this PR and fix it

@Andarist
Copy link
Contributor Author

Be my guest, take It 😉

@andrewbranch
Copy link
Member

Suppose you have

// @Filename: /a/b/c/node_modules/foo/package.json
{
  "exports": {
    ".": {
      "types": null
    }
  }
}

and

// @Filename: /a/b/c/node_modules/@types/foo/package.json
{
  "exports": {
    ".": {
      "types": "./index.d.ts"
    }
  }
}

Should the former block the latter? Should the former block a lookup in a higher-up node_modules directory?

@Andarist
Copy link
Contributor Author

Yes. The first matched location like this should stop the algorithm from looking any further.

I’d probably extend this to just any condition (not just to types). If null value gets selected that is a pretty explicit sign that the matched entrypoint is forbidden/disallowed at runtime (and thus at types level too)

@andrewbranch
Copy link
Member

I think this has some fairly unintuitive semantics. "types": null looks like it might mean “explicitly not typed,” but as you said, the fact that we arrived at null through "types" specifically is immaterial. If TS resolution arrives at a null, it should indicate that runtime resolution will also arrive at a null and stop further lookups.

If you want to tell TypeScript that something is explicitly not typed... you can point "types" at an untyped JavaScript file? 😅 But that won’t actually stop us from looking elsewhere for types.

I think this interpretation is perfectly consistent... it just struck me as weird. Implementation-wise, we actually have no existing mechanism to abort resolution with a failure. Sigh.

@jaridmargolin
Copy link

@andrewbranch - Are you still attempting to fix this? If I understand this thread correctly, I have what I would consider a similarly narrow issue. I am attempting to "hide" a subset of modules when using a custom "browser" condition. Would it make sense to create a new issue?

lib package.json:

  "exports": {
    ...
    "./entities/*/node": {
      "browser": null,
      "node": "./dist/entities/*/index.node.js"
    },
  }

consuming browser app tsconfig.json:

  "compilerOptions": {
    "customConditions": ["browser"]
  }

@andrewbranch
Copy link
Member

Planning to fix this as a breaking change in 6.0 or 6.5, depending on whether it needs a deprecation message; I’m not sure we have the details settled.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
Status: Waiting on reviewers
Development

Successfully merging this pull request may close these issues.

4 participants