Skip to content
This repository was archived by the owner on Oct 14, 2025. It is now read-only.

Commit 38816da

Browse files
committed
shimmer
1 parent f48ceb6 commit 38816da

12 files changed

Lines changed: 1278 additions & 510 deletions

File tree

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232

3333
- name: Validate types.d
3434
run: |
35-
npm run parse0
35+
npm run parse
3636
diff=$(git diff --ignore-space-change types.d.ts)
3737
if [[ -n "$diff" ]]; then
3838
echo $diff

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
shim.d.ts
2+
types.d.ts

README.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,7 @@ barDisposer();
5959
### Shimming
6060

6161
```typescript
62-
import shim from "easy-add-event-listener/shim";
63-
64-
shim();
62+
import "easy-add-event-listener/shim"; // shim implementation and TS declarations
6563

6664
const disposer = window.addEventListener(
6765
"load",
@@ -71,7 +69,7 @@ const disposer = window.addEventListener(
7169
disposer();
7270
```
7371

74-
#### Shimming Types
72+
#### Shimming custom types
7573

7674
- Run the cli to generate the shimmed types, refer to [this](#generate-types).
7775
- Load the types into your project by importing them or by using `patch-package`
@@ -85,6 +83,5 @@ The real effort here was to type the function:
8583
### Generate Types
8684

8785
```bash
88-
npm run parse <input> <output>
89-
npm run parse0
86+
npm run parse
9087
```

cli.mjs

Lines changed: 80 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,67 @@ import { readFileSync, writeFileSync } from "fs";
44
import * as path from "path";
55
import ts from "typescript";
66
import { fileURLToPath } from "url";
7+
import { program } from "commander";
78

89
const __dirname = path.dirname(fileURLToPath(import.meta.url));
910

10-
const [inputPath, outputPath] = process.argv.slice(2);
11-
if (!inputPath || !outputPath) {
12-
console.error(
13-
"Please provide the input and output file paths as command-line arguments."
14-
);
15-
process.exit(1);
16-
}
17-
11+
const dtsFiles = [
12+
"node_modules/typescript/lib/lib.dom.d.ts",
13+
"node_modules/@types/node/web-globals/events.d.ts",
14+
"node_modules/undici-types/eventsource.d.ts",
15+
"node_modules/undici-types/websocket.d.ts",
16+
];
17+
18+
program
19+
.name("easyAddEventListener parser")
20+
.description("Parses TS files in order to obtain addEventListener signatures")
21+
.argument(
22+
process.cwd() === __dirname ? "[files...]" : "<files...>",
23+
"Files to parse"
24+
)
25+
.option("-o,--output", "Output dir", __dirname)
26+
.parse();
27+
const { output: outputPath } = program.opts();
28+
const files = program.args.length ? program.args : dtsFiles;
1829
const sourceFile = ts.createSourceFile(
1930
"ast.ts",
20-
readFileSync(inputPath).toString(),
31+
files
32+
.map((file) => `// file: ${file}\n\n${readFileSync(file).toString()}`)
33+
.join("\n\n\n\n"),
2134
ts.ScriptTarget.Latest
2235
);
2336

2437
/**
25-
* @type {{ node: ts.InterfaceDeclaration, type: string, eventMap: string, ancestors: string[] }[]}
38+
* @type {{ node: ts.InterfaceDeclaration, shim: ts.InterfaceDeclaration, type: string, eventMap: string, ancestors: string[] }[]}
2639
*/
2740
const nodes = [];
2841
ts.forEachChild(sourceFile, visit);
2942

30-
// Sort so that the condition is correct for subclasses
31-
nodes.sort((a, b) =>
32-
b.ancestors.includes(a.type) ? 1 : a.ancestors.includes(b.type) ? -1 : 0
33-
);
34-
3543
const condition = nodes
3644
.filter(({ eventMap }) => !!eventMap)
45+
// Sort so that the condition is correct for subclasses
46+
.sort((a, b) =>
47+
b.ancestors.includes(a.type)
48+
? 1
49+
: a.ancestors.includes(b.type)
50+
? -1
51+
: b.ancestors.length - a.ancestors.length
52+
)
3753
.map(({ type, eventMap }) => `T extends ${type} ? ${eventMap} : `)
3854
.concat("never")
3955
.join("");
4056
const value = `export type EventMap<T extends EventTarget> = ${condition}`;
41-
writeFileSync(path.resolve(__dirname, "types.d.ts"), value);
42-
console.log(`Successfully written types.d.ts`);
57+
writeFileSync(path.resolve(outputPath, "types.d.ts"), value);
58+
console.log("Successfully created types.d.ts");
4359

4460
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
4561
writeFileSync(
46-
outputPath,
47-
printer.printNode(ts.EmitHint.Unspecified, sourceFile)
62+
path.resolve(outputPath, "shim.d.ts"),
63+
nodes
64+
.map(({ shim }) => printer.printNode(ts.EmitHint.Unspecified, shim))
65+
.join("\n\n")
4866
);
49-
console.log(`Successfully created ${outputPath}`);
67+
console.log("Successfully created shim.d.ts");
5068

5169
/**
5270
* Finds a node in the source file by its name and kind.
@@ -100,45 +118,61 @@ function visit(node) {
100118
return;
101119
}
102120

103-
node.members
104-
.filter(
105-
(member) =>
106-
ts.isMethodSignature(member) &&
107-
member.name?.getText(sourceFile) === "addEventListener"
108-
)
109-
.forEach(
110-
/**
111-
*
112-
* @param {ts.MethodSignature} methodNode
113-
* @returns
114-
*/
115-
(methodNode) => {
116-
methodNode.type = ts.factory.createTypeReferenceNode("VoidFunction");
117-
}
118-
);
121+
/**
122+
* @type {ts.MethodSignature[]}
123+
*/
124+
const addEventListenerDeclarations = node.members.filter(
125+
(member) =>
126+
ts.isMethodSignature(member) &&
127+
member.name?.getText(sourceFile) === "addEventListener"
128+
);
129+
130+
if (!addEventListenerDeclarations.length) {
131+
return;
132+
}
119133

120134
const heritageClauses = [];
121135
visitHeritageClauses(node, heritageClauses);
122136

123-
if (!heritageClauses.includes("EventTarget")) {
124-
return;
125-
}
137+
// if (!heritageClauses.includes("EventTarget")) {
138+
// return;
139+
// }
126140

127-
const eventMap = node.members
128-
.filter(
129-
(member) =>
130-
member.name?.getText(sourceFile) === "addEventListener" &&
131-
member.typeParameters?.length
132-
)
141+
const eventMap = addEventListenerDeclarations
142+
.filter((member) => member.typeParameters?.length)
133143
.flatMap((member) => member.typeParameters)
134144
.map((typeParam) => typeParam.constraint.getText(sourceFile))
135145
.find((k) => k.startsWith("keyof"))
136146
?.replace("keyof", "")
137147
.trim();
138148

149+
const generics =
150+
node.typeParameters?.filter((node) => !node.default).length ?? 0;
151+
139152
nodes.push({
140153
node,
141-
type: node.name.getText(sourceFile),
154+
shim: ts.factory.createInterfaceDeclaration(
155+
undefined,
156+
node.name,
157+
node.typeParameters,
158+
undefined,
159+
addEventListenerDeclarations.map((methodDeclarationNode) => {
160+
return ts.factory.createMethodDeclaration(
161+
methodDeclarationNode.modifiers,
162+
undefined,
163+
methodDeclarationNode.name,
164+
methodDeclarationNode.questionToken,
165+
methodDeclarationNode.typeParameters,
166+
methodDeclarationNode.parameters,
167+
ts.factory.createTypeReferenceNode("VoidFunction")
168+
);
169+
})
170+
),
171+
type: generics
172+
? `${node.name.getText(sourceFile)}<${new Array(generics)
173+
.fill("any")
174+
.join(", ")}>`
175+
: node.name.getText(sourceFile),
142176
eventMap,
143177
ancestors: heritageClauses,
144178
});
File renamed without changes.

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "module",
66
"main": "index.js",
77
"bin": {
8-
"addEventListener": "node cli.mjs"
8+
"parse": "node cli.mjs"
99
},
1010
"exports": {
1111
"/shim": {
@@ -14,9 +14,8 @@
1414
}
1515
},
1616
"scripts": {
17-
"test": "node index.spec.js && tsc index.test.ts --noEmit",
18-
"parse": "node cli.mjs",
19-
"parse0": "node cli.mjs node_modules/typescript/lib/lib.dom.d.ts shimmed.ts"
17+
"test": "node index.test.js && node shim.test.js && tsc index.test.ts shim.test.ts --noEmit",
18+
"parse": "node cli.mjs"
2019
},
2120
"keywords": [
2221
"EventTarget",
@@ -32,6 +31,7 @@
3231
"license": "ISC",
3332
"devDependencies": {
3433
"@types/node": "^24.7.2",
34+
"commander": "^14.0.1",
3535
"typescript": "^5.9.3"
3636
}
3737
}

0 commit comments

Comments
 (0)