From 18ef7becb931f09bfd83e098fad6956cba4a1989 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Wed, 6 Jul 2022 00:36:57 -0400 Subject: [PATCH] fix #2365, close #2372: bug in compat-table code --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ internal/compat/js_table.go | 16 ---------------- scripts/compat-table.js | 33 +++++++++++++++++++++++++++++---- 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08666e5507d..00a6dcc97ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,36 @@ This fix was contributed by [@magic-akari](https://github.com/magic-akari). +* Fix a bug in esbuild's feature compatibility table generator ([#2365](https://github.com/evanw/esbuild/issues/2365)) + + Passing specific JavaScript engines to esbuild's `--target` flag restricts esbuild to only using JavaScript features that are supported on those engines in the output files that esbuild generates. The data for this feature is automatically derived from this compatibility table with a script: https://kangax.github.io/compat-table/. + + However, the script had a bug that could incorrectly consider a JavaScript syntax feature to be supported in a given engine even when it doesn't actually work in that engine. Specifically this bug happened when a certain aspect of JavaScript syntax has always worked incorrectly in that engine and the bug in that engine has never been fixed. This situation hasn't really come up before because previously esbuild pretty much only targeted JavaScript engines that always fix their bugs, but the two new JavaScript engines that were added in the previous release ([Hermes](https://hermesengine.dev/) and [Rhino](https://github.com/mozilla/rhino)) have many aspects of the JavaScript specification that have never been implemented, and may never be implemented. For example, the `let` and `const` keywords are not implemented correctly in those engines. + + With this release, esbuild's compatibility table generator script has been fixed and as a result, esbuild will now correctly consider a JavaScript syntax feature to be unsupported in a given engine if there is some aspect of that syntax that is broken in all known versions of that engine. This means that the following JavaScript syntax features are no longer considered to be supported by these engines (represented using esbuild's internal names for these syntax features): + + Hermes: + - `arrow` + - `const-and-let` + - `default-argument` + - `generator` + - `optional-catch-binding` + - `optional-chain` + - `rest-argument` + - `template-literal` + + Rhino: + - `arrow` + - `const-and-let` + - `destructuring` + - `for-of` + - `generator` + - `object-extensions` + - `template-literal` + + IE: + - `const-and-let` + ## 0.14.48 * Enable using esbuild in Deno via WebAssembly ([#2323](https://github.com/evanw/esbuild/issues/2323)) diff --git a/internal/compat/js_table.go b/internal/compat/js_table.go index e613f6f4ad8..3139bccec3a 100644 --- a/internal/compat/js_table.go +++ b/internal/compat/js_table.go @@ -241,11 +241,9 @@ var jsTable = map[JSFeature]map[Engine][]versionRange{ Edge: {{start: v{13, 0, 0}}}, ES: {{start: v{2015, 0, 0}}}, Firefox: {{start: v{45, 0, 0}}}, - Hermes: {{start: v{0, 7, 0}}}, IOS: {{start: v{10, 0, 0}}}, Node: {{start: v{6, 0, 0}}}, Opera: {{start: v{36, 0, 0}}}, - Rhino: {{start: v{1, 7, 13}}}, Safari: {{start: v{10, 0, 0}}}, }, AsyncAwait: { @@ -392,12 +390,9 @@ var jsTable = map[JSFeature]map[Engine][]versionRange{ Edge: {{start: v{14, 0, 0}}}, ES: {{start: v{2015, 0, 0}}}, Firefox: {{start: v{51, 0, 0}}}, - Hermes: {{start: v{0, 7, 0}}}, - IE: {{start: v{11, 0, 0}}}, IOS: {{start: v{11, 0, 0}}}, Node: {{start: v{6, 0, 0}}}, Opera: {{start: v{36, 0, 0}}}, - Rhino: {{start: v{1, 7, 13}}}, Safari: {{start: v{11, 0, 0}}}, }, DefaultArgument: { @@ -405,7 +400,6 @@ var jsTable = map[JSFeature]map[Engine][]versionRange{ Edge: {{start: v{14, 0, 0}}}, ES: {{start: v{2015, 0, 0}}}, Firefox: {{start: v{53, 0, 0}}}, - Hermes: {{start: v{0, 7, 0}}}, IOS: {{start: v{10, 0, 0}}}, Node: {{start: v{6, 0, 0}}}, Opera: {{start: v{36, 0, 0}}}, @@ -420,7 +414,6 @@ var jsTable = map[JSFeature]map[Engine][]versionRange{ IOS: {{start: v{10, 0, 0}}}, Node: {{start: v{6, 5, 0}}}, Opera: {{start: v{38, 0, 0}}}, - Rhino: {{start: v{1, 7, 14}}}, Safari: {{start: v{10, 0, 0}}}, }, DynamicImport: { @@ -472,7 +465,6 @@ var jsTable = map[JSFeature]map[Engine][]versionRange{ IOS: {{start: v{10, 0, 0}}}, Node: {{start: v{6, 5, 0}}}, Opera: {{start: v{38, 0, 0}}}, - Rhino: {{start: v{1, 7, 13}}}, Safari: {{start: v{10, 0, 0}}}, }, Generator: { @@ -480,11 +472,9 @@ var jsTable = map[JSFeature]map[Engine][]versionRange{ Edge: {{start: v{13, 0, 0}}}, ES: {{start: v{2015, 0, 0}}}, Firefox: {{start: v{53, 0, 0}}}, - Hermes: {{start: v{0, 7, 0}}}, IOS: {{start: v{10, 0, 0}}}, Node: {{start: v{6, 0, 0}}}, Opera: {{start: v{37, 0, 0}}}, - Rhino: {{start: v{1, 7, 13}}}, Safari: {{start: v{10, 0, 0}}}, }, Hashbang: { @@ -582,7 +572,6 @@ var jsTable = map[JSFeature]map[Engine][]versionRange{ IOS: {{start: v{10, 0, 0}}}, Node: {{start: v{4, 0, 0}}}, Opera: {{start: v{31, 0, 0}}}, - Rhino: {{start: v{1, 7, 14}}}, Safari: {{start: v{10, 0, 0}}}, }, ObjectRestSpread: { @@ -601,7 +590,6 @@ var jsTable = map[JSFeature]map[Engine][]versionRange{ Edge: {{start: v{79, 0, 0}}}, ES: {{start: v{2019, 0, 0}}}, Firefox: {{start: v{58, 0, 0}}}, - Hermes: {{start: v{0, 7, 0}}}, IOS: {{start: v{11, 3, 0}}}, Node: {{start: v{10, 0, 0}}}, Opera: {{start: v{53, 0, 0}}}, @@ -612,7 +600,6 @@ var jsTable = map[JSFeature]map[Engine][]versionRange{ Edge: {{start: v{91, 0, 0}}}, ES: {{start: v{2020, 0, 0}}}, Firefox: {{start: v{74, 0, 0}}}, - Hermes: {{start: v{0, 7, 0}}}, IOS: {{start: v{13, 4, 0}}}, Node: {{start: v{16, 9, 0}}}, Opera: {{start: v{77, 0, 0}}}, @@ -683,7 +670,6 @@ var jsTable = map[JSFeature]map[Engine][]versionRange{ Edge: {{start: v{12, 0, 0}}}, ES: {{start: v{2015, 0, 0}}}, Firefox: {{start: v{43, 0, 0}}}, - Hermes: {{start: v{0, 7, 0}}}, IOS: {{start: v{10, 0, 0}}}, Node: {{start: v{6, 0, 0}}}, Opera: {{start: v{34, 0, 0}}}, @@ -694,11 +680,9 @@ var jsTable = map[JSFeature]map[Engine][]versionRange{ Edge: {{start: v{13, 0, 0}}}, ES: {{start: v{2015, 0, 0}}}, Firefox: {{start: v{34, 0, 0}}}, - Hermes: {{start: v{0, 7, 0}}}, IOS: {{start: v{9, 0, 0}}}, Node: {{start: v{4, 0, 0}}}, Opera: {{start: v{28, 0, 0}}}, - Rhino: {{start: v{1, 7, 14}}}, Safari: {{start: v{9, 0, 0}}}, }, TopLevelAwait: { diff --git a/scripts/compat-table.js b/scripts/compat-table.js index 18f3ba23df3..9b5d9920304 100644 --- a/scripts/compat-table.js +++ b/scripts/compat-table.js @@ -101,6 +101,23 @@ const engines = [ 'rhino', ] +function getValueOfTest(value) { + // Handle values like this: + // + // { + // val: true, + // note_id: "ff-shorthand-methods", + // ... + // } + // + if (typeof value === 'object' && value !== null) { + return value.val === true + } + + // String values such as "flagged" are considered to be false + return value === true +} + function mergeVersions(target, res) { // The original data set will contain something like "chrome44: true" for a // given feature. And the interpolation script will expand this to something @@ -109,7 +126,7 @@ function mergeVersions(target, res) { const lowestVersionMap = {} for (const key in res) { - if (res[key] === true) { + if (getValueOfTest(res[key])) { const match = /^([a-z_]+)[0-9_]+$/.exec(key) if (match) { const engine = match[1] @@ -316,9 +333,17 @@ for (const test of [...es5.tests, ...es6.tests, ...stage4.tests, ...stage1to3.te if (feature) { feature.found = true if (test.subtests) { - for (const subtest of test.subtests) { - mergeVersions(feature.target, subtest.res) - } + const res = {} + + // Intersect all subtests (so a key is only true if it's true in all subtests) + 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) }