Skip to content

Commit

Permalink
Add script to get builtin globals (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker authored Jan 24, 2024
1 parent 47d4224 commit d9bef83
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
yarn.lock
.cache
48 changes: 24 additions & 24 deletions globals.json
Original file line number Diff line number Diff line change
Expand Up @@ -1099,30 +1099,30 @@
"XPathEvaluator": false,
"XPathExpression": false,
"XPathResult": false,
"XRAnchor": false,
"XRBoundedReferenceSpace": false,
"XRCPUDepthInformation": false,
"XRDepthInformation": false,
"XRFrame": false,
"XRInputSource": false,
"XRInputSourceArray": false,
"XRInputSourceEvent": false,
"XRInputSourcesChangeEvent": false,
"XRPose": false,
"XRReferenceSpace": false,
"XRReferenceSpaceEvent": false,
"XRRenderState": false,
"XRRigidTransform": false,
"XRSession": false,
"XRSessionEvent": false,
"XRSpace": false,
"XRSystem": false,
"XRView": false,
"XRViewerPose": false,
"XRViewport": false,
"XRWebGLBinding": false,
"XRWebGLDepthInformation": false,
"XRWebGLLayer": false,
"XRAnchor": false,
"XRBoundedReferenceSpace": false,
"XRCPUDepthInformation": false,
"XRDepthInformation": false,
"XRFrame": false,
"XRInputSource": false,
"XRInputSourceArray": false,
"XRInputSourceEvent": false,
"XRInputSourcesChangeEvent": false,
"XRPose": false,
"XRReferenceSpace": false,
"XRReferenceSpaceEvent": false,
"XRRenderState": false,
"XRRigidTransform": false,
"XRSession": false,
"XRSessionEvent": false,
"XRSpace": false,
"XRSystem": false,
"XRView": false,
"XRViewerPose": false,
"XRViewport": false,
"XRWebGLBinding": false,
"XRWebGLDepthInformation": false,
"XRWebGLLayer": false,
"XSLTProcessor": false
},
"worker": {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"node": ">=8"
},
"scripts": {
"test": "xo && ava"
"test": "xo && ava",
"update-builtin-globals": "node scripts/get-builtin-globals.mjs"
},
"files": [
"index.js",
Expand All @@ -37,6 +38,7 @@
},
"devDependencies": {
"ava": "^2.4.0",
"cheerio": "^1.0.0-rc.12",
"tsd": "^0.14.0",
"xo": "^0.36.1"
},
Expand Down
133 changes: 133 additions & 0 deletions scripts/get-builtin-globals.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import fs from 'node:fs/promises';
import * as cheerio from 'cheerio';

const SPECIFICATION_URLS = [
'https://raw.githubusercontent.com/tc39/ecma262/HEAD/spec.html',
'https://cdn.jsdelivr.net/gh/tc39/ecma262/spec.html',
];
const CACHE_FILE = new URL('../.cache/spec.html', import.meta.url);
const DATA_FILE = new URL('../globals.json', import.meta.url);

const getText = async (url) => {
const response = await fetch(url);
const text = await response.text();
return text;
};

const any = async (asyncFunctions) => {
const errors = [];
for (const function_ of asyncFunctions) {
try {
return await function_();
} catch (error) {
errors.push(error);
}
}

throw new AggregateError(errors, 'All failed.');
};

const getSpecification = async () => {
let stat;

try {
stat = await fs.stat(CACHE_FILE);
} catch {}

if (stat) {
if (Date.now() - stat.ctimeMs < /* 10 hours */ 10 * 60 * 60 * 1000) {
return fs.readFile(CACHE_FILE, 'utf8');
}

await fs.rm(CACHE_FILE);
}

const text = await any(SPECIFICATION_URLS.map((url) => () => getText(url)));

await fs.mkdir(new URL('./', CACHE_FILE), { recursive: true });
await fs.writeFile(CACHE_FILE, text);

return text;
};

function* getGlobalObjects(specification) {
const $ = cheerio.load(specification);
for (const element of $('emu-clause#sec-global-object > emu-clause h1')) {
const property = $(element).text().trim().split(/\s/)[0];
const descriptor = Object.getOwnPropertyDescriptor(globalThis, property);
if (descriptor) {
yield { property, descriptor };
}
}

// Annex B
yield* ['escape', 'unescape'].map((property) => ({
property,
descriptor: Object.getOwnPropertyDescriptor(globalThis, property),
}));
}

function* getObjectProperties(specification) {
const $ = cheerio.load(specification);

for (const element of $('emu-clause#sec-properties-of-the-object-prototype-object > emu-clause > h1')) {
const text = $(element).text().trim();
if (!text.startsWith('Object.prototype.')) {
continue;
}

const property = text.split(/\s/)[0].slice('Object.prototype.'.length);
// `Object.prototype.{__proto__, ..}`
if (property.startsWith('_')) {
continue;
}

const descriptor = Object.getOwnPropertyDescriptor(
Object.prototype,
property
);
if (descriptor) {
yield { property, descriptor };
}
}
}

let specification = await getSpecification();
const builtinGlobals = Object.fromEntries(
[
...getGlobalObjects(specification),
// `globalThis` is an object
...getObjectProperties(specification),
]
.sort(({ property: propertyA }, { property: propertyB }) =>
propertyA.localeCompare(propertyB)
)
.map(({ property }) => [
property,
// Most of these except `Infinity`, `NaN`, `undefined` are actually writable/configurable
false,
])
);

const globals = JSON.parse(await fs.readFile(DATA_FILE));
const originalGlobals = Object.keys(globals.builtin);
globals.builtin = builtinGlobals;

await fs.writeFile(DATA_FILE, JSON.stringify(globals, undefined, '\t') + '\n');

const added = Object.keys(builtinGlobals).filter(
(property) => !originalGlobals.includes(property)
);
const removed = originalGlobals.filter(
(property) => !Object.hasOwn(builtinGlobals)
);

console.log(`
✅ Builtin globals updated.
Added(${added.length}):
${added.map((property) => ` - ${property}`).join('\n') || 'None'}
Removed(${removed.length}):
${removed.map((property) => ` - ${property}`).join('\n') || 'None'}
`);

0 comments on commit d9bef83

Please sign in to comment.