import fs = require('fs')
import path = require('path')
import es5 = require('../repos/kangax/compat-table/data-es5.js')
import es6 = require('../repos/kangax/compat-table/data-es6.js')
import stage1to3 = require('../repos/kangax/compat-table/data-esnext.js')
import stage4 = require('../repos/kangax/compat-table/data-es2016plus.js')
import environments = require('../repos/kangax/compat-table/environments.json')
import parseEnvsVersions = require('../repos/kangax/compat-table/build-utils/parse-envs-versions.js')
import interpolateAllResults = require('../repos/kangax/compat-table/build-utils/interpolate-all-results.js')
import { Engine, JSFeature, SupportMap, jsFeatures } from './index'
interpolateAllResults(es5.tests, environments)
interpolateAllResults(es6.tests, environments)
interpolateAllResults(stage1to3.tests, environments)
interpolateAllResults(stage4.tests, environments)
const features: Record<string, JSFeature> = {
'Object/array literal extensions: Getter accessors': 'ObjectAccessors',
'Object/array literal extensions: Setter accessors': 'ObjectAccessors',
'arrow functions': 'Arrow',
'class': 'Class',
'const': 'ConstAndLet',
'default function parameters': 'DefaultArgument',
'destructuring, assignment': 'Destructuring',
'destructuring, declarations': 'Destructuring',
'destructuring, parameters': 'Destructuring',
'for..of loops': 'ForOf',
'function "name" property: isn\'t writable, is configurable': 'FunctionNameConfigurable',
'generators': 'Generator',
'let': 'ConstAndLet',
'new.target': 'NewTarget',
'object literal extensions': 'ObjectExtensions',
'RegExp "y" and "u" flags': 'RegexpStickyAndUnicodeFlags',
'rest parameters': 'RestArgument',
'spread syntax for iterable objects': 'ArraySpread',
'template literals': 'TemplateLiteral',
'Unicode code point escapes': 'UnicodeEscapes',
'async functions': 'AsyncAwait',
'Asynchronous Iterators: async generators': 'AsyncGenerator',
'Asynchronous Iterators: for-await-of loops': 'ForAwait',
'BigInt: basic functionality': 'Bigint',
'exponentiation (**) operator': 'ExponentOperator',
'Hashbang Grammar': 'Hashbang',
'Logical Assignment': 'LogicalAssignment',
'nested rest destructuring, declarations': 'NestedRestBinding',
'nested rest destructuring, parameters': 'NestedRestBinding',
'nullish coalescing operator (??)': 'NullishCoalescing',
'object rest/spread properties': 'ObjectRestSpread',
'optional catch binding': 'OptionalCatchBinding',
'optional chaining operator (?.)': 'OptionalChain',
'RegExp Lookbehind Assertions': 'RegexpLookbehindAssertions',
'RegExp named capture groups': 'RegexpNamedCaptureGroups',
'RegExp Unicode Property Escapes': 'RegexpUnicodePropertyEscapes',
's (dotAll) flag for regular expressions': 'RegexpDotAllFlag',
'instance class fields: computed instance class fields': 'ClassField',
'instance class fields: public instance class fields': 'ClassField',
'static class fields: computed static class fields': 'ClassStaticField',
'static class fields: public static class fields': 'ClassStaticField',
'instance class fields: optional deep private instance class fields access': 'ClassPrivateField',
'instance class fields: optional private instance class fields access': 'ClassPrivateField',
'instance class fields: private instance class fields basic support': 'ClassPrivateField',
'instance class fields: private instance class fields initializers': 'ClassPrivateField',
'static class fields: private static class fields': 'ClassPrivateStaticField',
'private class methods: private accessor properties': 'ClassPrivateAccessor',
'private class methods: private instance methods': 'ClassPrivateMethod',
'private class methods: private static accessor properties': 'ClassPrivateStaticAccessor',
'private class methods: private static methods': 'ClassPrivateStaticMethod',
'Ergonomic brand checks for private fields': 'ClassPrivateBrandCheck',
}
const environmentToEngine: Record<string, Engine> = {
'es': 'ES',
'chrome': 'Chrome',
'edge': 'Edge',
'firefox': 'Firefox',
'ie': 'IE',
'ios': 'IOS',
'node': 'Node',
'opera': 'Opera',
'safari': 'Safari',
'deno': 'Deno',
'hermes': 'Hermes',
'rhino': 'Rhino',
}
const subtestsToSkip: Record<string, boolean> = {
'destructuring, parameters: duplicate identifier': true,
}
const getValueOfTest = (value: boolean | { val: boolean }): boolean => {
if (typeof value === 'object' && value !== null) {
return value.val === true
}
return value === true
}
interface Test {
name: string
res: Record<string, boolean | { val: boolean }>
subtests?: { name: string, res: Record<string, boolean | { val: boolean }> }[]
}
const updateMap = (map: SupportMap<JSFeature>, feature: JSFeature, engine: Engine, version: string, testName: string, passed: boolean): void => {
const engines = map[feature] || (map[feature] = {})
const versions = engines[engine] || (engines[engine] = {})
const support = versions[version] || (versions[version] = {})
if (passed) {
support.passed = (support.passed || 0) + 1
} else {
support.failed ||= new Set
support.failed.add(testName)
}
}
const mergeIndividualTestResults = (map: SupportMap<JSFeature>, feature: JSFeature, testName: string, res: Record<string, boolean | { val: boolean }>, omit: Engine[]): void => {
const environments = parseEnvsVersions(res)
for (const environment in environments) {
const engine = environmentToEngine[environment]
if (engine && omit.indexOf(engine) < 0) {
for (const parsed of environments[environment]) {
const version = parsed.version.join('.')
if (/^\d+(?:\.\d+(?:\.\d+)?)?$/.test(version)) {
updateMap(map, feature, engine, version, testName, getValueOfTest(res[parsed.id]))
}
}
}
}
}
const mergeAllTestResults = (map: SupportMap<JSFeature>, tests: Test[], { omit = [] }: { omit?: Engine[] } = {}): void => {
for (const test of tests) {
const feature = features[test.name]
if (feature) {
if (test.subtests) {
for (const subtest of test.subtests) {
const fullName = `${test.name}: ${subtest.name}`
if (subtestsToSkip[fullName]) continue
mergeIndividualTestResults(map, feature, fullName, subtest.res, omit)
}
} else {
mergeIndividualTestResults(map, feature, test.name, test.res, omit)
}
} else if (test.subtests) {
for (const subtest of test.subtests) {
const fullName = `${test.name}: ${subtest.name}`
if (subtestsToSkip[fullName]) continue
const feature = features[fullName]
if (feature) mergeIndividualTestResults(map, feature, fullName, subtest.res, omit)
}
}
}
}
const reformatNodeCompatTable = (): Test[] => {
const nodeCompatTableDir = path.join(__dirname, 'repos/williamkapke/node-compat-table/results/v8')
const testMap: Record<string, Test> = {}
const subtestMap: Record<string, Test> = {}
const tests: Test[] = []
for (const entry of fs.readdirSync(nodeCompatTableDir)) {
const match = /^([1-9]\d*\.\d+\.\d+)\.json$/.exec(entry)
if (match) {
const version = 'node' + match[1].replace(/\./g, '_')
const jsonPath = path.join(nodeCompatTableDir, entry)
const json = JSON.parse(fs.readFileSync(jsonPath, 'utf8'))
for (const key in json) {
if (key.startsWith('ES')) {
const object = json[key]
for (const key in object) {
const testResult = object[key]
const split = key.replace('<code>', '').replace('</code>', '').split('›')
if (split.length === 2) {
let test = testMap[split[1]]
if (!test) {
test = testMap[split[1]] = { name: split[1], res: {} }
tests.push(test)
}
test.res[version] = testResult
}
else if (split.length === 3) {
const subtestKey = `${split[1]}: ${split[2]}`
let subtest = subtestMap[subtestKey]
if (!subtest) {
let test = testMap[split[1]]
if (!test) {
test = testMap[split[1]] = { name: split[1], res: {} }
tests.push(test)
}
subtest = subtestMap[subtestKey] = { name: split[2], res: {} }
test.subtests ||= []
test.subtests.push(subtest)
}
subtest.res[version] = testResult
}
}
}
}
}
}
return tests
}
export const js: SupportMap<JSFeature> = {} as SupportMap<JSFeature>
mergeAllTestResults(js, [...es5.tests, ...es6.tests, ...stage4.tests, ...stage1to3.tests], { omit: ['Node'] })
mergeAllTestResults(js, reformatNodeCompatTable())