Repository: TypeScript
Issue: Treat certain kinds of UMD initialization as a module in checkJS mode
Labels: Suggestion,Awaiting More Feedback

# Bug Report

Here's plain JavaScript that works in Node, all Browsers for the last 3+ years, and all Bundlers (Vite, WebPack, etc) without the absolute minimum possible boilerplate, but `tsc` can't make heads or tails of it.

`foo.js`:
```js
var Foo = ("object" === typeof module && exports) || {};
(function (window) {
  "use strict";

  let Crypto = window.crypto || require("node:crypto");

  Foo.secret = new Uint8Array(16);
  Crypto.getRandomValues(Foo.secret);

  /**
   * @param {String} name
   */
  Foo.greet = function (name) {
    console.log(`Hello, ${name}!`);
  };

  //...
  console.log(Crypto.getRandomValues);
})(globalThis.window || {});
```

`uses-foo.js`:
```js
"use strict";

let Foo = require("./foo.js");

// This SHOULD have an error (but it doesn't)
// (meaning that Foo became global, but shouldn't have)
/** @type {Foo} */
let fooFail = null;
console.log(fooFail);

// This should NOT have an error (but it does)
//   - Foo should be module-exported,
//   - Foo.greet should automatically typed by its explicit definition
/** @type {import('./foo').Foo} */
let greet = Foo.greet;
greet("AJ");
```

`jsconfig.json`:
```json
{
  "compilerOptions": {
    "target": "es2022",
    "moduleDetection": "force",
    "module": "commonjs",
    "moduleResolution": "node",
    "typeRoots": ["./typings", "./node_modules/@types"],
    "allowJs": true,
    "checkJs": true,
    "noEmit": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitAny": true,
    "alwaysStrict": true,
    "skipLibCheck": true
  },
  "include": ["*.js", "bin/**/*.js", "lib/**/*.js", "src/**/*.js"],
  "exclude": ["node_modules"]
}
```

### 🔎 Search Terms

- umd

### 🕗 Version & Regression Information

Always

- This is the behavior in every version I tried, and I reviewed the FAQ for entries about umd

### ⏯ Playground Link

I couldn't figure out how to put multiple files in the playground or how to set the jsconfig.json, but here's the link anyway:

https://www.typescriptlang.org/play?declaration=false&target=9&jsx=0&module=1&filetype=js#code/G4QwTgBAYg9jEF4IAoBEMBGArApgYwBdVEEkCBPABxxgDMIBbGAEwFcAbHCAMm4hwAelGGAIBnAJQQAPtIgBvAL4BuAFDJarAHaEAljC0oA7rq3MYRqfNUQIqVmK5iCYXYVRqbETgQgBhMCoCeCQTMwsAOjxAymCZOTAcAEdWXUS0LRYcAC5ooJhUCU9bWBgIx2icXyQtHCMIAFVTAgAOAEEwMBByZABGADYirwD8iIBzKoAlEHCGADUQdlYcMWRS8vxEgiGvAHoAKn2vCH2IAAFKcBAGBQBlF1MxxQgta5xj-d2vdbHEqsQIJodAR9IZkK8GDgrMcIHgDGIYJwIuwYGNkAADAASOHYKIANBAACTyCE4RQAQnRQ1sKlUe12EUZXjhWgRSJRaJGsTKEwI01mCyWKyGigkyDGKIwiwAKgALXRiCJhcz1WQKUXKIA

### 💻 Code

- https://github.com/coolaj86/example-jswt-umd/tree/main/umd-ideal-but-broken
- https://github.com/coolaj86/example-jswt-umd/tree/main/umd-ugly-but-works

Here's the super hack workaround:

```js
/**
 * @typedef Foo2
 * @prop {Greet2} greet
 * @prop {Uint8Array} secret
 */

/**
 * @callback Greet2
 * @param {String} name
 */

/** @type {Foo2} */
//@ts-ignore
var Foo2 = ("object" === typeof module && exports) || {};
(function (window, Foo2) {
  "use strict";

  let Crypto = window.crypto || require("node:crypto");

  Foo2.secret = new Uint8Array(16);
  Crypto.getRandomValues(Foo2.secret);

  /**
   * @param {String} name
   */
  Foo2.greet = function (name) {
    console.log(`Hello, ${name}!`);
  };

  return Foo2;
})(globalThis.window || {}, Foo2);
if ("object" === typeof module) {
  module.exports = Foo2;
}
```

Issues with the hacky workaround are:

- looks really ugly and confusing, but triggers all the right heuristics in however `tsc` is doing its thing

### 🙁 Actual behavior

Can't detect any of Foo's properties, exports Foo as a global rather that a script, doesn't export the exports at all.

### 🙂 Expected behavior

All type info should be module style (not global script style) and should know that Foo is module.exports and that anything assigned to Foo is exported.