const fs = require('fs')
const path = require('path')
const es5 = require('../github/compat-table/data-es5')
const es6 = require('../github/compat-table/data-es6')
const stage1to3 = require('../github/compat-table/data-esnext')
const stage4 = require('../github/compat-table/data-es2016plus')
const environments = require('../github/compat-table/environments.json')
const compareVersions = require('../github/compat-table/build-utils/compare-versions')
const parseEnvsVersions = require('../github/compat-table/build-utils/parse-envs-versions')
const interpolateAllResults = require('../github/compat-table/build-utils/interpolate-all-results')
interpolateAllResults(es5.tests, environments)
interpolateAllResults(es6.tests, environments)
interpolateAllResults(stage1to3.tests, environments)
interpolateAllResults(stage4.tests, environments)
const features = {
'Object/array literal extensions: Getter accessors': { target: 'ObjectAccessors' },
'Object/array literal extensions: Setter accessors': { target: 'ObjectAccessors' },
'default function parameters': { target: 'DefaultArgument' },
'rest parameters': { target: 'RestArgument' },
'spread syntax for iterable objects': { target: 'ArraySpread' },
'object literal extensions': { target: 'ObjectExtensions' },
'for..of loops': { target: 'ForOf' },
'template literals': { target: 'TemplateLiteral' },
'destructuring, declarations': { target: 'Destructuring' },
'destructuring, assignment': { target: 'Destructuring' },
'destructuring, parameters': { target: 'Destructuring' },
'new.target': { target: 'NewTarget' },
'const': { target: 'ConstAndLet' },
'let': { target: 'ConstAndLet' },
'arrow functions': { target: 'Arrow' },
'class': { target: 'Class' },
'generators': { target: 'Generator' },
'Unicode code point escapes': { target: 'UnicodeEscapes' },
'RegExp "y" and "u" flags': { target: 'RegexpStickyAndUnicodeFlags' },
'exponentiation (**) operator': { target: 'ExponentOperator' },
'nested rest destructuring, declarations': { target: 'NestedRestBinding' },
'nested rest destructuring, parameters': { target: 'NestedRestBinding' },
'async functions': { target: 'AsyncAwait' },
'object rest/spread properties': { target: 'ObjectRestSpread' },
'RegExp Lookbehind Assertions': { target: 'RegexpLookbehindAssertions' },
'RegExp named capture groups': { target: 'RegexpNamedCaptureGroups' },
'RegExp Unicode Property Escapes': { target: 'RegexpUnicodePropertyEscapes' },
's (dotAll) flag for regular expressions': { target: 'RegexpDotAllFlag' },
'Asynchronous Iterators: async generators': { target: 'AsyncGenerator' },
'Asynchronous Iterators: for-await-of loops': { target: 'ForAwait' },
'optional catch binding': { target: 'OptionalCatchBinding' },
'BigInt: basic functionality': { target: 'Bigint' },
'optional chaining operator (?.)': { target: 'OptionalChain' },
'nullish coalescing operator (??)': { target: 'NullishCoalescing' },
'Logical Assignment': { target: 'LogicalAssignment' },
'Hashbang Grammar': { target: 'Hashbang' },
'instance class fields: public instance class fields': { target: 'ClassField' },
'instance class fields: computed instance class fields': { target: 'ClassField' },
'static class fields: public static class fields': { target: 'ClassStaticField' },
'static class fields: computed static class fields': { target: 'ClassStaticField' },
'instance class fields: private instance class fields basic support': { target: 'ClassPrivateField' },
'instance class fields: private instance class fields initializers': { target: 'ClassPrivateField' },
'instance class fields: optional private instance class fields access': { target: 'ClassPrivateField' },
'instance class fields: optional deep private instance class fields access': { target: 'ClassPrivateField' },
'static class fields: private static class fields': { target: 'ClassPrivateStaticField' },
'private class methods: private instance methods': { target: 'ClassPrivateMethod' },
'private class methods: private accessor properties': { target: 'ClassPrivateAccessor' },
'private class methods: private static methods': { target: 'ClassPrivateStaticMethod' },
'private class methods: private static accessor properties': { target: 'ClassPrivateStaticAccessor' },
'Ergonomic brand checks for private fields': { target: 'ClassPrivateBrandCheck' },
}
const versions = {}
const engines = [
'es',
'chrome',
'edge',
'firefox',
'ie',
'ios',
'node',
'opera',
'safari',
'deno',
'hermes',
'rhino',
]
function getValueOfTest(value) {
if (typeof value === 'object' && value !== null) {
return value.val === true
}
return value === true
}
function mergeVersions(target, res) {
const lowestVersionMap = {}
for (const key in res) {
if (getValueOfTest(res[key])) {
const match = /^([a-z_]+)[0-9_]+$/.exec(key)
if (match) {
const engine = match[1]
if (engines.indexOf(engine) >= 0) {
const version = parseEnvsVersions({ [key]: true })[engine][0].version
if (!lowestVersionMap[engine] || compareVersions({ version }, { version: lowestVersionMap[engine] }) < 0) {
lowestVersionMap[engine] = version
}
}
}
}
}
const highestVersionMap = versions[target] || (versions[target] = {})
for (const engine in lowestVersionMap) {
const version = lowestVersionMap[engine]
if (!highestVersionMap[engine] || compareVersions({ version }, { version: highestVersionMap[engine][0].start }) > 0) {
highestVersionMap[engine] = [{ start: version, end: null }]
}
}
}
mergeVersions('ObjectAccessors', { es5: true })
mergeVersions('ArraySpread', { es2015: true })
mergeVersions('Arrow', { es2015: true })
mergeVersions('Class', { es2015: true })
mergeVersions('ConstAndLet', { es2015: true })
mergeVersions('DefaultArgument', { es2015: true })
mergeVersions('Destructuring', { es2015: true })
mergeVersions('DynamicImport', { es2015: true })
mergeVersions('ForOf', { es2015: true })
mergeVersions('Generator', { es2015: true })
mergeVersions('NewTarget', { es2015: true })
mergeVersions('ObjectExtensions', { es2015: true })
mergeVersions('RegexpStickyAndUnicodeFlags', { es2015: true })
mergeVersions('RestArgument', { es2015: true })
mergeVersions('TemplateLiteral', { es2015: true })
mergeVersions('UnicodeEscapes', { es2015: true })
mergeVersions('ExponentOperator', { es2016: true })
mergeVersions('NestedRestBinding', { es2016: true })
mergeVersions('AsyncAwait', { es2017: true })
mergeVersions('AsyncGenerator', { es2018: true })
mergeVersions('ForAwait', { es2018: true })
mergeVersions('ObjectRestSpread', { es2018: true })
mergeVersions('RegexpDotAllFlag', { es2018: true })
mergeVersions('RegexpLookbehindAssertions', { es2018: true })
mergeVersions('RegexpNamedCaptureGroups', { es2018: true })
mergeVersions('RegexpUnicodePropertyEscapes', { es2018: true })
mergeVersions('OptionalCatchBinding', { es2019: true })
mergeVersions('Bigint', { es2020: true })
mergeVersions('ImportMeta', { es2020: true })
mergeVersions('NullishCoalescing', { es2020: true })
mergeVersions('OptionalChain', { es2020: true })
mergeVersions('TypeofExoticObjectIsObject', { es2020: true })
mergeVersions('LogicalAssignment', { es2021: true })
mergeVersions('ClassField', { es2022: true })
mergeVersions('ClassPrivateAccessor', { es2022: true })
mergeVersions('ClassPrivateBrandCheck', { es2022: true })
mergeVersions('ClassPrivateField', { es2022: true })
mergeVersions('ClassPrivateMethod', { es2022: true })
mergeVersions('ClassPrivateStaticAccessor', { es2022: true })
mergeVersions('ClassPrivateStaticField', { es2022: true })
mergeVersions('ClassPrivateStaticMethod', { es2022: true })
mergeVersions('ClassStaticBlocks', { es2022: true })
mergeVersions('ClassStaticField', { es2022: true })
mergeVersions('TopLevelAwait', { es2022: true })
mergeVersions('ArbitraryModuleNamespaceNames', { es2022: true })
mergeVersions('RegexpMatchIndices', { es2022: true })
mergeVersions('RegexpSetNotation', {})
mergeVersions('ImportAssertions', {})
mergeVersions('ExportStarAs', {
chrome72: true,
edge79: true,
es2020: true,
firefox80: true,
node12: true,
opera60: true,
})
mergeVersions('ImportMeta', {
chrome64: true,
edge79: true,
firefox62: true,
ios12: true,
node10_4: true,
opera51: true,
safari11_1: true,
})
mergeVersions('TopLevelAwait', {
chrome89: true,
edge89: true,
firefox89: true,
ios15: true,
node14_8: true,
opera75: true,
safari15: true,
})
mergeVersions('DynamicImport', {
chrome63: true,
edge79: true,
firefox67: true,
ios11: true,
opera50: true,
safari11_1: true,
})
mergeVersions('TypeofExoticObjectIsObject', {
chrome0: true,
edge0: true,
es0: true,
firefox0: true,
ios0: true,
node0: true,
opera0: true,
safari0: true,
})
mergeVersions('InlineScript', {})
versions.DynamicImport.node = [
{ start: [12, 20], end: [13] },
{ start: [13, 2] },
]
versions.NodeColonPrefixImport = {
node: [
{ start: [12, 20], end: [13] },
{ start: [14, 13, 1] },
]
}
versions.NodeColonPrefixRequire = {
node: [
{ start: [14, 18], end: [15] },
{ start: [16] },
]
}
mergeVersions('ArbitraryModuleNamespaceNames', {
chrome90: true,
node16: true,
firefox87: true,
})
mergeVersions('ImportAssertions', {
chrome91: true,
node16_14: true,
})
mergeVersions('ClassStaticBlocks', {
chrome91: true,
edge94: true,
firefox93: true,
node16_11: true,
opera80: true,
})
mergeVersions('RegexpMatchIndices', {
chrome90: true,
edge90: true,
firefox88: true,
ios15: true,
opera76: true,
safari15: true,
})
for (const test of [...es5.tests, ...es6.tests, ...stage4.tests, ...stage1to3.tests]) {
const feature = features[test.name]
if (feature) {
feature.found = true
if (test.subtests) {
const res = {}
for (const subtest of test.subtests)
for (const key in subtest.res)
res[key] = true
for (const subtest of test.subtests)
for (const key in res)
res[key] &&= getValueOfTest(subtest.res[key] ?? false)
mergeVersions(feature.target, res)
} else {
mergeVersions(feature.target, test.res)
}
} else if (test.subtests) {
for (const subtest of test.subtests) {
const feature = features[`${test.name}: ${subtest.name}`]
if (feature) {
feature.found = true
mergeVersions(feature.target, subtest.res)
}
}
}
}
for (const feature in features) {
if (!features[feature].found) {
throw new Error(`Did not find ${feature}`)
}
}
applyManualOverride('ClassPrivateBrandCheck', 'node', [{ start: [16, 9], end: null }], [{ start: [16, 4], end: null }])
applyManualOverride('Hashbang', 'node', [{ start: [12, 0], end: null }], [{ start: [12, 5], end: null }])
applyManualOverride('OptionalChain', 'node', [{ start: [16, 9], end: null }], [{ start: [16, 1], end: null }])
applyManualOverride('TemplateLiteral', 'node', [{ start: [4], end: null }], [{ start: [10], end: null }])
function applyManualOverride(target, engine, expected, changed) {
const observed = JSON.stringify(versions[target][engine])
expected = JSON.stringify(expected)
if (observed !== expected)
throw new Error(`Mismatch for versions.${target}.${engine}: Expected ${observed} to be ${expected}`)
versions[target][engine] = changed
}
function upper(text) {
if (text === 'es' || text === 'ios' || text === 'ie') return text.toUpperCase()
return text[0].toUpperCase() + text.slice(1)
}
function jsFeatureString(feature) {
return feature.replace(/([A-Z])/g, '-$1').slice(1).toLowerCase()
}
function simpleMap(entries) {
let maxLength = 0
for (const [key] of entries) {
maxLength = Math.max(maxLength, key.length + 1)
}
return entries.map(([key, value]) => `\t${(key + ':').padEnd(maxLength)} ${value},`).join('\n')
}
function jsTableMap(obj) {
const keys = Object.keys(obj).sort()
const maxLength = keys.reduce((a, b) => Math.max(a, b.length + 1), 0)
if (keys.length === 0) return '{}'
return `{\n${keys.map(x => {
const items = obj[x].map(y => {
return `{start: v{${y.start.concat(0, 0).slice(0, 3).join(', ')
}}${y.end ? `, end: v{${y.end.concat(0, 0).slice(0, 3).join(', ')}}` : ''}}`
})
return `\t\t${(upper(x) + ':').padEnd(maxLength)} {${items.join(', ')}},`
}).join('\n')}\n\t}`
}
function jsTableValidEnginesMap(engines) {
const keys = engines.slice().sort()
const maxLength = keys.reduce((a, b) => Math.max(a, b.length + 4), 0)
if (keys.length === 0) return '{}'
return keys.map(x => {
return `\t${`"${x}": `.padEnd(maxLength)}api.Engine${upper(x)},`
}).join('\n')
}
fs.writeFileSync(__dirname + '/../internal/compat/js_table.go',
`// This file was automatically generated by "${path.basename(__filename)}"
package compat
type Engine uint8
const (
${engines.slice().sort().map((x, i) => `\t${upper(x)}${i ? '' : ' Engine = iota'}`).join('\n')}
)
func (e Engine) String() string {
\tswitch e {
${engines.slice().sort().map(x => `\tcase ${upper(x)}:\n\t\treturn "${x}"`).join('\n')}
\t}
\treturn ""
}
type JSFeature uint64
const (
${Object.keys(versions).sort().map((x, i) => `\t${x}${i ? '' : ' JSFeature = 1 << iota'}`).join('\n')}
)
var StringToJSFeature = map[string]JSFeature{
${simpleMap(Object.keys(versions).sort().map(x => [`"${jsFeatureString(x)}"`, x]))}
}
func (features JSFeature) Has(feature JSFeature) bool {
\treturn (features & feature) != 0
}
func (features JSFeature) ApplyOverrides(overrides JSFeature, mask JSFeature) JSFeature {
\treturn (features & ^mask) | (overrides & mask)
}
var jsTable = map[JSFeature]map[Engine][]versionRange{
${Object.keys(versions).sort().map(x => `\t${x}: ${jsTableMap(versions[x])},`).join('\n')}
}
// Return all features that are not available in at least one environment
func UnsupportedJSFeatures(constraints map[Engine][]int) (unsupported JSFeature) {
\tfor feature, engines := range jsTable {
\t\tif feature == InlineScript {
\t\t\tcontinue // This is purely user-specified
\t\t}
\t\tfor engine, version := range constraints {
\t\t\tif versionRanges, ok := engines[engine]; !ok || !isVersionSupported(versionRanges, version) {
\t\t\t\tunsupported |= feature
\t\t\t}
\t\t}
\t}
\treturn
}
`)
fs.writeFileSync(__dirname + '/../pkg/api/api_js_table.go',
`// This file was automatically generated by "${path.basename(__filename)}"
package api
import "github.com/evanw/esbuild/internal/compat"
type EngineName uint8
const (
${engines.filter(x => x !== 'es').map((x, i) => `\tEngine${upper(x)}${i ? '' : ' EngineName = iota'}`).join('\n')}
)
func convertEngineName(engine EngineName) compat.Engine {
\tswitch engine {
${engines.filter(x => x !== 'es').map(x => `\tcase Engine${upper(x)}:\n\t\treturn compat.${upper(x)}`).join('\n')}
\tdefault:
\t\tpanic("Invalid engine name")
\t}
}
`)
fs.writeFileSync(__dirname + '/../pkg/cli/cli_js_table.go',
`// This file was automatically generated by "${path.basename(__filename)}"
package cli
import "github.com/evanw/esbuild/pkg/api"
var validEngines = map[string]api.EngineName{
${jsTableValidEnginesMap(engines.filter(x => x !== 'es'))}
}
`)