#!/usr/bin/env node 'use strict'; const fs = require('fs').promises; const path = require('path'); const { promisify } = require('util'); const exec = promisify(require('child_process').exec); const $ = require('cheerio'); const fromEntries = require('object.fromentries'); const years = require('./years'); async function getOps(year) { if (year < 2015) { throw new RangeError('ES2015+ only'); } const edition = year - 2009; const specHTMLurl = year > 2015 ? new URL(`https://raw.githubusercontent.com/tc39/ecma262/${year === 2022 ? 'master' : `es${year}`}/spec.html`) : new URL('https://262.ecma-international.org/6.0/'); const cmd = `curl -q --silent ${specHTMLurl}`; console.log(year, cmd); const specHTML = String((await exec(cmd, { maxBuffer: Infinity })).stdout); const root = $(specHTML); let aOps = root.filter('[aoid]') .add(root.find('[aoid]')) .add(root.find('[id^="sec-numeric-types-"]:not([aoid])')) .not('[type="sdo"]'); if (aOps.length === 0) { aOps = root.find('p:contains(" abstract operation ")').closest('section').add(root.find('#sec-reference-specification-type > section')); } const missings = []; const entries = aOps.toArray().map((x) => { const op = $(x); let aoid = op.attr('aoid'); let id = op.attr('id'); if (!id) { id = op.closest('[id]').attr('id'); } // years other than 2016 have `id.startsWith('eqn-')` const isConstant = op.text().trim().split('\n').length === 1 && op.text().startsWith(`${aoid} = `); if (isConstant) { return null; } if (!aoid) { if (!aoid && ( id === 'sec-ecmascript-standard-built-in-objects' || id === 'sec-forbidden-extensions' || id === 'sec-jobs-and-job-queues' || id === 'sec-%typedarray%.prototype.sort' || id === 'sec-hostresolveimportedmodule' || id === 'sec-tostring-applied-to-the-number-type' )) { return null; } if (op.parent().attr('id') === 'sec-reference-specification-type') { ({ groups: { aoid } } = op.find('h1').text().match(/\s(?[a-zA-Z][a-z][a-zA-Z]+)\s/m)); } else if ((/^sec-numeric-types-(?:number|bigint)-/).test(op.attr('id'))) { ({ groups: { aoid } } = op.find('h1').text().match(/\s?(?[a-zA-Z][a-z][a-zA-Z]+(?:::[a-zA-Z][a-z][a-zA-Z]+)+)\s/m)); } else { const match = op.text().match(/When the (?[a-zA-Z][a-z][a-zA-Z]+) abstract operation is called/m) || op.text().match(/The (?[a-zA-Z][a-z][a-zA-Z]+) abstract operation/m) || op.text().match(/ abstract operation (?[a-zA-Z/0-9]+)/m); if (match) { ({ groups: { aoid } } = match); } } } if (aoid && !id) { missings.push(aoid); } else if (!aoid) { missings.push(id); } if (!aoid || !id) { return null; } return [ aoid, `https://262.ecma-international.org/${edition}.0/#${id}`, ]; }).filter((x) => x && x[0]); if (missings.length > 0) { throw `Missing URLs: ${missings}`; } if (year === 2015) { entries.push( ['abs', 'https://262.ecma-international.org/6.0/#sec-algorithm-conventions'], ['Abstract Equality Comparison', 'https://262.ecma-international.org/6.0/#sec-abstract-equality-comparison'], ['Abstract Relational Comparison', 'https://262.ecma-international.org/6.0/#sec-abstract-relational-comparison'], ['CreateArrayIterator', 'https://262.ecma-international.org/6.0/#sec-createarrayiterator'], ['DateFromTime', 'https://262.ecma-international.org/6.0/#sec-date-number'], ['Day', 'https://262.ecma-international.org/6.0/#sec-day-number-and-time-within-day'], ['DayFromYear', 'https://262.ecma-international.org/6.0/#sec-year-number'], ['DaysInYear', 'https://262.ecma-international.org/6.0/#sec-year-number'], ['DayWithinYear', 'https://262.ecma-international.org/6.0/#sec-month-number'], ['floor', 'https://262.ecma-international.org/6.0/#sec-algorithm-conventions'], ['GetBase', 'https://262.ecma-international.org/6.0/#sec-jobs-and-job-queues'], ['GetReferencedName', 'https://262.ecma-international.org/6.0/#sec-jobs-and-job-queues'], ['HasPrimitiveBase', 'https://262.ecma-international.org/6.0/#sec-jobs-and-job-queues'], ['HostResolveImportedModule', 'sec-hostresolveimportedmodule'], ['HourFromTime', 'https://262.ecma-international.org/6.0/#sec-hours-minutes-second-and-milliseconds'], ['InLeapYear', 'https://262.ecma-international.org/6.0/#sec-year-number'], ['IsPropertyReference', 'https://262.ecma-international.org/6.0/#sec-jobs-and-job-queues'], ['IsStrictReference', 'https://262.ecma-international.org/6.0/#sec-jobs-and-job-queues'], ['IsSuperReference', 'https://262.ecma-international.org/6.0/#sec-jobs-and-job-queues'], ['IsUnresolvableReference', 'https://262.ecma-international.org/6.0/#sec-jobs-and-job-queues'], ['max', 'https://262.ecma-international.org/6.0/#sec-algorithm-conventions'], ['min', 'https://262.ecma-international.org/6.0/#sec-algorithm-conventions'], ['MinFromTime', 'https://262.ecma-international.org/6.0/#sec-hours-minutes-second-and-milliseconds'], ['modulo', 'https://262.ecma-international.org/6.0/#sec-algorithm-conventions'], ['MonthFromTime', 'https://262.ecma-international.org/6.0/#sec-month-number'], ['msFromTime', 'https://262.ecma-international.org/6.0/#sec-hours-minutes-second-and-milliseconds'], ['msPerDay', 'https://262.ecma-international.org/6.0/#sec-day-number-and-time-within-day'], ['SecFromTime', 'https://262.ecma-international.org/6.0/#sec-hours-minutes-second-and-milliseconds'], ['sign', 'https://262.ecma-international.org/6.0/#sec-algorithm-conventions'], ['Strict Equality Comparison', 'https://262.ecma-international.org/6.0/#sec-strict-equality-comparison'], ['thisBooleanValue', 'https://262.ecma-international.org/6.0/#sec-properties-of-the-boolean-prototype-object'], ['thisNumberValue', 'https://262.ecma-international.org/6.0/#sec-properties-of-the-number-prototype-object'], ['thisStringValue', 'https://262.ecma-international.org/6.0/#sec-properties-of-the-string-prototype-object'], ['thisTimeValue', 'https://262.ecma-international.org/6.0/#sec-properties-of-the-date-prototype-object'], ['TimeFromYear', 'https://262.ecma-international.org/6.0/#sec-year-number'], ['TimeWithinDay', 'https://262.ecma-international.org/6.0/#sec-day-number-and-time-within-day'], ['ToDateString', 'https://262.ecma-international.org/6.0/#sec-todatestring'], ['Type', 'https://262.ecma-international.org/6.0/#sec-ecmascript-data-types-and-values'], ['WeekDay', 'https://262.ecma-international.org/6.0/#sec-week-day'], ['YearFromTime', 'https://262.ecma-international.org/6.0/#sec-year-number'], ); } else if (year === 2016) { entries.push( ['thisNumberValue', 'https://262.ecma-international.org/7.0/#sec-properties-of-the-number-prototype-object'], ['thisStringValue', 'https://262.ecma-international.org/7.0/#sec-properties-of-the-string-prototype-object'], ); } if (year >= 2015 && year <= 2017) { entries.push(['DaylightSavingTA', `https://262.ecma-international.org/${edition}.0/#sec-daylight-saving-time-adjustment`]); } if (year >= 2021) { entries.push( ['clamp', `https://262.ecma-international.org/${edition}.0/#clamping`], ['substring', `https://262.ecma-international.org/${edition}.0/#substring`], ); } entries.sort(([a], [b]) => a.localeCompare(b)); const obj = fromEntries(entries); const outputPath = path.join('operations', `${year}.js`); let output = `'use strict';\n\nmodule.exports = ${JSON.stringify(obj, null, '\t')};\n`; if ((year === 5 || year >= 2015) && year < 2018) { output = output.replace(/[=] \{\n/m, "= {\n\tIsPropertyDescriptor: 'https://262.ecma-international.org/6.0/#sec-property-descriptor-specification-type', // not actually an abstract op\n\n"); } await fs.writeFile(outputPath, output); console.log(`npx eslint --quiet --fix ${outputPath}`); return exec(`npx eslint --quiet --fix ${outputPath}`); } Promise.all(years.map((year) => getOps(year))).catch((e) => { console.error(e); process.exitCode = 1; });