Skip to content

Commit d9bef83

Browse files
authored
Add script to get builtin globals (#207)
1 parent 47d4224 commit d9bef83

File tree

4 files changed

+161
-25
lines changed

4 files changed

+161
-25
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules
22
yarn.lock
3+
.cache

globals.json

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,30 +1099,30 @@
10991099
"XPathEvaluator": false,
11001100
"XPathExpression": false,
11011101
"XPathResult": false,
1102-
"XRAnchor": false,
1103-
"XRBoundedReferenceSpace": false,
1104-
"XRCPUDepthInformation": false,
1105-
"XRDepthInformation": false,
1106-
"XRFrame": false,
1107-
"XRInputSource": false,
1108-
"XRInputSourceArray": false,
1109-
"XRInputSourceEvent": false,
1110-
"XRInputSourcesChangeEvent": false,
1111-
"XRPose": false,
1112-
"XRReferenceSpace": false,
1113-
"XRReferenceSpaceEvent": false,
1114-
"XRRenderState": false,
1115-
"XRRigidTransform": false,
1116-
"XRSession": false,
1117-
"XRSessionEvent": false,
1118-
"XRSpace": false,
1119-
"XRSystem": false,
1120-
"XRView": false,
1121-
"XRViewerPose": false,
1122-
"XRViewport": false,
1123-
"XRWebGLBinding": false,
1124-
"XRWebGLDepthInformation": false,
1125-
"XRWebGLLayer": false,
1102+
"XRAnchor": false,
1103+
"XRBoundedReferenceSpace": false,
1104+
"XRCPUDepthInformation": false,
1105+
"XRDepthInformation": false,
1106+
"XRFrame": false,
1107+
"XRInputSource": false,
1108+
"XRInputSourceArray": false,
1109+
"XRInputSourceEvent": false,
1110+
"XRInputSourcesChangeEvent": false,
1111+
"XRPose": false,
1112+
"XRReferenceSpace": false,
1113+
"XRReferenceSpaceEvent": false,
1114+
"XRRenderState": false,
1115+
"XRRigidTransform": false,
1116+
"XRSession": false,
1117+
"XRSessionEvent": false,
1118+
"XRSpace": false,
1119+
"XRSystem": false,
1120+
"XRView": false,
1121+
"XRViewerPose": false,
1122+
"XRViewport": false,
1123+
"XRWebGLBinding": false,
1124+
"XRWebGLDepthInformation": false,
1125+
"XRWebGLLayer": false,
11261126
"XSLTProcessor": false
11271127
},
11281128
"worker": {

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"node": ">=8"
1616
},
1717
"scripts": {
18-
"test": "xo && ava"
18+
"test": "xo && ava",
19+
"update-builtin-globals": "node scripts/get-builtin-globals.mjs"
1920
},
2021
"files": [
2122
"index.js",
@@ -37,6 +38,7 @@
3738
},
3839
"devDependencies": {
3940
"ava": "^2.4.0",
41+
"cheerio": "^1.0.0-rc.12",
4042
"tsd": "^0.14.0",
4143
"xo": "^0.36.1"
4244
},

scripts/get-builtin-globals.mjs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import fs from 'node:fs/promises';
2+
import * as cheerio from 'cheerio';
3+
4+
const SPECIFICATION_URLS = [
5+
'https://raw.githubusercontent.com/tc39/ecma262/HEAD/spec.html',
6+
'https://cdn.jsdelivr.net/gh/tc39/ecma262/spec.html',
7+
];
8+
const CACHE_FILE = new URL('../.cache/spec.html', import.meta.url);
9+
const DATA_FILE = new URL('../globals.json', import.meta.url);
10+
11+
const getText = async (url) => {
12+
const response = await fetch(url);
13+
const text = await response.text();
14+
return text;
15+
};
16+
17+
const any = async (asyncFunctions) => {
18+
const errors = [];
19+
for (const function_ of asyncFunctions) {
20+
try {
21+
return await function_();
22+
} catch (error) {
23+
errors.push(error);
24+
}
25+
}
26+
27+
throw new AggregateError(errors, 'All failed.');
28+
};
29+
30+
const getSpecification = async () => {
31+
let stat;
32+
33+
try {
34+
stat = await fs.stat(CACHE_FILE);
35+
} catch {}
36+
37+
if (stat) {
38+
if (Date.now() - stat.ctimeMs < /* 10 hours */ 10 * 60 * 60 * 1000) {
39+
return fs.readFile(CACHE_FILE, 'utf8');
40+
}
41+
42+
await fs.rm(CACHE_FILE);
43+
}
44+
45+
const text = await any(SPECIFICATION_URLS.map((url) => () => getText(url)));
46+
47+
await fs.mkdir(new URL('./', CACHE_FILE), { recursive: true });
48+
await fs.writeFile(CACHE_FILE, text);
49+
50+
return text;
51+
};
52+
53+
function* getGlobalObjects(specification) {
54+
const $ = cheerio.load(specification);
55+
for (const element of $('emu-clause#sec-global-object > emu-clause h1')) {
56+
const property = $(element).text().trim().split(/\s/)[0];
57+
const descriptor = Object.getOwnPropertyDescriptor(globalThis, property);
58+
if (descriptor) {
59+
yield { property, descriptor };
60+
}
61+
}
62+
63+
// Annex B
64+
yield* ['escape', 'unescape'].map((property) => ({
65+
property,
66+
descriptor: Object.getOwnPropertyDescriptor(globalThis, property),
67+
}));
68+
}
69+
70+
function* getObjectProperties(specification) {
71+
const $ = cheerio.load(specification);
72+
73+
for (const element of $('emu-clause#sec-properties-of-the-object-prototype-object > emu-clause > h1')) {
74+
const text = $(element).text().trim();
75+
if (!text.startsWith('Object.prototype.')) {
76+
continue;
77+
}
78+
79+
const property = text.split(/\s/)[0].slice('Object.prototype.'.length);
80+
// `Object.prototype.{__proto__, ..}`
81+
if (property.startsWith('_')) {
82+
continue;
83+
}
84+
85+
const descriptor = Object.getOwnPropertyDescriptor(
86+
Object.prototype,
87+
property
88+
);
89+
if (descriptor) {
90+
yield { property, descriptor };
91+
}
92+
}
93+
}
94+
95+
let specification = await getSpecification();
96+
const builtinGlobals = Object.fromEntries(
97+
[
98+
...getGlobalObjects(specification),
99+
// `globalThis` is an object
100+
...getObjectProperties(specification),
101+
]
102+
.sort(({ property: propertyA }, { property: propertyB }) =>
103+
propertyA.localeCompare(propertyB)
104+
)
105+
.map(({ property }) => [
106+
property,
107+
// Most of these except `Infinity`, `NaN`, `undefined` are actually writable/configurable
108+
false,
109+
])
110+
);
111+
112+
const globals = JSON.parse(await fs.readFile(DATA_FILE));
113+
const originalGlobals = Object.keys(globals.builtin);
114+
globals.builtin = builtinGlobals;
115+
116+
await fs.writeFile(DATA_FILE, JSON.stringify(globals, undefined, '\t') + '\n');
117+
118+
const added = Object.keys(builtinGlobals).filter(
119+
(property) => !originalGlobals.includes(property)
120+
);
121+
const removed = originalGlobals.filter(
122+
(property) => !Object.hasOwn(builtinGlobals)
123+
);
124+
125+
console.log(`
126+
✅ Builtin globals updated.
127+
128+
Added(${added.length}):
129+
${added.map((property) => ` - ${property}`).join('\n') || 'None'}
130+
131+
Removed(${removed.length}):
132+
${removed.map((property) => ` - ${property}`).join('\n') || 'None'}
133+
`);

0 commit comments

Comments
 (0)