Skip to content

Commit 7bf624b

Browse files
authored
Merge pull request #85 from arethetypeswrong/checker
Improved default export analysis
2 parents d2703b2 + 959229e commit 7bf624b

24 files changed

+1287
-136
lines changed

.changeset/sweet-mangos-marry.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@arethetypeswrong/core": minor
3+
---
4+
5+
New problem kind: **Missing `export =`**
6+
7+
Previously, `FalseExportDefault` had many cases of false positives where the JavaScript assigned an object to `module.exports`, and that object had a `default` property pointing back to itself. This pattern is not a true `FalseExportDefault`, but it is still problematic if the types only declare an `export default`. These kinds of false positives of `FalseExportDefault` are now instead reported as `MissingExportEquals`.
8+
9+
Additionally, `FalseExportDefault` was only ever reported as being visible in `node16-esm`, but this was incorrect. The consequences are most likely to be visible in `node16-esm`, but the problem is fundamentally independent of the module resolution mode, and inaccuracies can be observed in other modes as well, especially when `esModuleInterop` is not enabled.

docs/problems/MissingExportEquals.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# ❓ Missing `export =`
2+
3+
The JavaScript appears to set both `module.exports` and `module.exports.default` for improved compatibility, but the types only reflect the latter (by using `export default`). This will cause TypeScript under the `node16` module mode to think an extra `.default` property access is required, which will work at runtime but is not necessary. These types `export =` an object with a `default` property instead of using `export default`.
4+
5+
## Explanation
6+
7+
This problem occurs when a CommonJS JavaScript file appears to use a compatibility pattern like:
8+
9+
```js
10+
class Whatever {
11+
/* ... */
12+
}
13+
Whatever.default = Whatever;
14+
module.exports = Whatever;
15+
```
16+
17+
but the corresponding type definitions only reflect the existence of the `module.exports.default` property:
18+
19+
```ts
20+
declare class Whatever {
21+
/* ... */
22+
}
23+
export default Whatever;
24+
```
25+
26+
The types should declare the existence of the `Whatever` class on both `module.exports` and `module.exports.default`. The method of doing this can vary depending on the kinds of things already being exported from the types. When the `export default` exports a class, and that class is the only export in the file, the `default` can be declared as a static property on the class, and the `export default` swapped for `export =`:
27+
28+
```ts
29+
declare class Whatever {
30+
static default: typeof Whatever;
31+
/* ... */
32+
}
33+
export = Whatever;
34+
```
35+
36+
When the file exports additional types, it will be necessary to declare a `namespace` that merges with the class and contains the exported types:
37+
38+
```ts
39+
declare class Whatever {
40+
static default: typeof Whatever;
41+
/* ... */
42+
}
43+
declare namespace Whatever {
44+
export interface WhateverProps {
45+
/* ... */
46+
}
47+
}
48+
export = Whatever;
49+
```
50+
51+
This merging namespace can also be used to declare the `default` property when the main export is a function:
52+
53+
```ts
54+
declare function Whatever(props: Whatever.WhateverProps): void;
55+
declare namespace Whatever {
56+
declare const _default: typeof Whatever;
57+
export { _default as default };
58+
59+
export interface WhateverProps {
60+
/* ... */
61+
}
62+
}
63+
export = Whatever;
64+
```
65+
66+
## Consequences
67+
68+
This problem is similar to the [“Incorrect default export”](./FalseExportDefault.md) problem, but in this case, the types are _incomplete_ rather than wholly incorrect. This incompleteness may lead TypeScript users importing from Node.js ESM code, or CommonJS code without `esModuleInterop` enabled, to add an extra `.default` property onto default imports to access the module’s `module.exports.default` property, even though accessing the `module.exports` would have been sufficient.
69+
70+
```ts
71+
import Whatever from "pkg";
72+
Whatever.default(); // Ok, but `Whatever()` would have worked!
73+
```
74+
75+
## Common causes
76+
77+
This problem is usually caused by library authors incorrectly hand-authoring declaration files to match existing JavaScript rather than generating JavaScript and type declarations from TypeScript with `tsc`, or by using a third-party TypeScript emitter that adds an extra compatibility layer to TypeScript written with `export default`. Libraries compiling to CommonJS should generally avoid writing `export default` as input syntax.

packages/cli/src/problemUtils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const problemFlags: Record<ProblemKind, string> = {
1111
FallbackCondition: "fallback-condition",
1212
CJSOnlyExportsDefault: "cjs-only-exports-default",
1313
FalseExportDefault: "false-export-default",
14+
MissingExportEquals: "missing-export-equals",
1415
UnexpectedModuleSyntax: "unexpected-module-syntax",
1516
InternalResolutionError: "internal-resolution-error",
1617
};

packages/cli/test/snapshots/@[email protected]

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ $ attw @[email protected] -f table-flipped
66
77
@vitejs/plugin-react v3.1.0
88
9+
❓ The JavaScript appears to set both module.exports and module.exports.default for improved compatibility, but the types only reflect the latter (by using export default). This will cause TypeScript under the node16 module mode to think an extra .default property access is required, which will work at runtime but is not necessary. These types export = an object with a default property instead of using export default. https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/MissingExportEquals.md
10+
911
🎭 Import resolved to a CommonJS type declaration file, but an ESM JavaScript file. https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseCJS.md
1012
1113
12-
┌────────────────────────┬───────────────────────────┬────────────────────────┬─────────┐
13-
│ │ node10 │ node16 (from CJS) │ node16 (from ESM) │ bundler │
14-
├────────────────────────┼───────────────────────────┼────────────────────────┼─────────┤
15-
│ "@vitejs/plugin-react" │ 🟢 │ 🟢 (CJS) │ 🎭 Masquerading as CJS │ 🟢 │
16-
└────────────────────────┴───────────────────────────┴────────────────────────┴─────────┘
14+
┌────────────────────────┬───────────────────────┬───────────────────────┬────────────────────────┬─────────┐
15+
│ │ node10 │ node16 (from CJS) │ node16 (from ESM) │ bundler │
16+
├────────────────────────┼───────────────────────┼───────────────────────┼────────────────────────┼─────────┤
17+
│ "@vitejs/plugin-react" │ ❓ Missing `export =` │ ❓ Missing `export =` │ 🎭 Masquerading as CJS │ 🟢 │
18+
└────────────────────────┴───────────────────────┴───────────────────────┴────────────────────────┴─────────┘
1719
1820
1921
```

packages/cli/test/snapshots/[email protected]

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ Build tools:
1111
- rollup@^2.44.0
1212
- @rollup/plugin-typescript@^10.0.1
1313
14-
No problems found 🌟
14+
❓ The JavaScript appears to set both module.exports and module.exports.default for improved compatibility, but the types only reflect the latter (by using export default). This will cause TypeScript under the node16 module mode to think an extra .default property access is required, which will work at runtime but is not necessary. These types export = an object with a default property instead of using export default. https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/MissingExportEquals.md
1515
1616
17-
┌───────┬────────┬───────────────────┬────────────────────────────┐
18-
│ │ node10 │ node16 (from CJS) │ node16 (from ESM) │ bundler │
19-
├───────┼────────┼───────────────────┼────────────────────────────┤
20-
│ "ajv" │ 🟢 │ 🟢 (CJS) │ 🟢 (CJS) │ 🟢
21-
└───────┴────────┴───────────────────┴────────────────────────────┘
17+
┌───────┬───────────────────────┬───────────────────────┬───────────────────────┬───────────────────────┐
18+
│ │ node10 │ node16 (from CJS) │ node16 (from ESM) │ bundler
19+
├───────┼───────────────────────┼───────────────────────┼───────────────────────┼───────────────────────┤
20+
│ "ajv" │ ❓ Missing `export =` │ ❓ Missing `export =` │ ❓ Missing `export =` │ ❓ Missing `export =`
21+
└───────┴───────────────────────┴───────────────────────┴───────────────────────┴───────────────────────┘
2222
2323
2424
```
2525

26-
Exit code: 0
26+
Exit code: 1

packages/cli/test/snapshots/[email protected]

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Build tools:
1010
- typescript@^4.8.4
1111
- rollup@^2.67.0
1212
13-
Wildcard subpaths cannot yet be analyzed by this tool. https://github.com/arethetypeswrong/arethetypeswrong.github.io/issues/40
13+
🃏 Wildcard subpaths cannot yet be analyzed by this tool. https://github.com/arethetypeswrong/arethetypeswrong.github.io/issues/40
1414
1515
💀 Import failed to resolve to type declarations or JavaScript files. https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/NoResolution.md
1616
@@ -24,7 +24,7 @@ Build tools:
2424
├─────────────────────────────────────────┼──────────────────────┼──────────────────────────────┼────────────────────┼────────────────────┤
2525
│ "axios" │ 🟢 │ 🟢 (CJS) │ 🟢 (ESM) │ 🟢 │
2626
├─────────────────────────────────────────┼──────────────────────┼──────────────────────────────┼────────────────────┼────────────────────┤
27-
│ "axios/unsafe/*" │ Unable to check │ Unable to check │ Unable to check │ Unable to check │
27+
│ "axios/unsafe/*" │ 🃏 Unable to check │ 🃏 Unable to check │ 🃏 Unable to check │ 🃏 Unable to check │
2828
├─────────────────────────────────────────┼──────────────────────┼──────────────────────────────┼────────────────────┼────────────────────┤
2929
│ "axios/unsafe/core/settle.js" │ 💀 Resolution failed │ ❌ No types │ ❌ No types │ ❌ No types │
3030
│ │ │ ⚠️ ESM (dynamic import only) │ │ │

packages/cli/test/snapshots/[email protected]

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ hexoid v1.0.0
99
❗️ The resolved types use export default where the JavaScript file appears to use module.exports =. This will cause TypeScript under the node16 module mode to think an extra .default property access is required, but that will likely fail at runtime. These types should use export = instead of export default. https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseExportDefault.md
1010
1111
12-
┌──────────┬────────┬───────────────────┬──────────────────────────────┬─────────┐
13-
│ │ node10 │ node16 (from CJS) │ node16 (from ESM) │ bundler │
14-
├──────────┼────────┼───────────────────┼──────────────────────────────┼─────────┤
15-
│ "hexoid" │ 🟢 │ 🟢 (CJS) │ ❗️ Incorrect default export │ 🟢
16-
└──────────┴────────┴───────────────────┴──────────────────────────────┴─────────┘
12+
┌──────────┬──────────────────────────────┬──────────────────────────────┬──────────────────────────────┬──────────────────────────────┐
13+
│ │ node10 │ node16 (from CJS) │ node16 (from ESM) │ bundler
14+
├──────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
15+
│ "hexoid" │ ❗️ Incorrect default export │ ❗️ Incorrect default export │ ❗️ Incorrect default export │ ❗️ Incorrect default export
16+
└──────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
1717
1818
1919
```

0 commit comments

Comments
 (0)