Skip to content

Commit

Permalink
fix #3388: do something with pre-release versions
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Sep 15, 2023
1 parent 673ad10 commit ea9c644
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 84 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

* Allow pre-release versions to be passed to `target` ([#3388](https://github.com/evanw/esbuild/issues/3388))

People want to be able to pass version numbers for unreleased versions of node (which have extra stuff after the version numbers) to esbuild's `target` setting and have esbuild do something reasonable with them. These version strings are of course not present in esbuild's internal feature compatibility table because an unreleased version has not been released yet (by definition). With this release, esbuild will now attempt to accept these version strings passed to `target` and do something reasonable with them.

## 0.19.3

* Fix `list-style-type` with the `local-css` loader ([#3325](https://github.com/evanw/esbuild/issues/3325))
Expand Down
4 changes: 2 additions & 2 deletions compat-table/src/css_table.ts
Expand Up @@ -89,7 +89,7 @@ ${Object.keys(map).sort().map(feature => `\t${feature}: ${cssTableMap(map[featur
}
// Return all features that are not available in at least one environment
func UnsupportedCSSFeatures(constraints map[Engine][]int) (unsupported CSSFeature) {
func UnsupportedCSSFeatures(constraints map[Engine]Semver) (unsupported CSSFeature) {
\tfor feature, engines := range cssTable {
\t\tif feature == InlineStyle {
\t\t\tcontinue // This is purely user-specified
Expand Down Expand Up @@ -131,7 +131,7 @@ var cssPrefixTable = map[css_ast.D][]prefixData{
${Object.keys(prefixes).sort().map(property => `\tcss_ast.${property}: ${cssPrefixMap(prefixes[property as CSSProperty]!)},`).join('\n')}
}
func CSSPrefixData(constraints map[Engine][]int) (entries map[css_ast.D]CSSPrefix) {
func CSSPrefixData(constraints map[Engine]Semver) (entries map[css_ast.D]CSSPrefix) {
\tfor property, items := range cssPrefixTable {
\t\tprefixes := NoPrefix
\t\tfor engine, version := range constraints {
Expand Down
2 changes: 1 addition & 1 deletion compat-table/src/js_table.ts
Expand Up @@ -96,7 +96,7 @@ ${Object.keys(map).sort().map(feature => `\t${feature}: ${jsTableMap(map[feature
}
// Return all features that are not available in at least one environment
func UnsupportedJSFeatures(constraints map[Engine][]int) (unsupported JSFeature) {
func UnsupportedJSFeatures(constraints map[Engine]Semver) (unsupported JSFeature) {
\tfor feature, engines := range jsTable {
\t\tif feature == InlineScript {
\t\t\tcontinue // This is purely user-specified
Expand Down
4 changes: 2 additions & 2 deletions internal/bundler_tests/bundler_test.go
Expand Up @@ -27,8 +27,8 @@ import (
)

func es(version int) compat.JSFeature {
return compat.UnsupportedJSFeatures(map[compat.Engine][]int{
compat.ES: {version},
return compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{version}},
})
}

Expand Down
44 changes: 35 additions & 9 deletions internal/compat/compat.go
@@ -1,33 +1,59 @@
package compat

import "github.com/evanw/esbuild/internal/ast"
import (
"strconv"
"strings"

"github.com/evanw/esbuild/internal/ast"
)

type v struct {
major uint16
minor uint8
patch uint8
}

type Semver struct {
// "1.2.3-alpha" => { Parts: {1, 2, 3}, PreRelease: "-alpha" }
Parts []int
PreRelease string
}

func (v Semver) String() string {
b := strings.Builder{}
for _, part := range v.Parts {
if b.Len() > 0 {
b.WriteRune('.')
}
b.WriteString(strconv.Itoa(part))
}
b.WriteString(v.PreRelease)
return b.String()
}

// Returns <0 if "a < b"
// Returns 0 if "a == b"
// Returns >0 if "a > b"
func compareVersions(a v, b []int) int {
func compareVersions(a v, b Semver) int {
diff := int(a.major)
if len(b) > 0 {
diff -= b[0]
if len(b.Parts) > 0 {
diff -= b.Parts[0]
}
if diff == 0 {
diff = int(a.minor)
if len(b) > 1 {
diff -= b[1]
if len(b.Parts) > 1 {
diff -= b.Parts[1]
}
}
if diff == 0 {
diff = int(a.patch)
if len(b) > 2 {
diff -= b[2]
if len(b.Parts) > 2 {
diff -= b.Parts[2]
}
}
if diff == 0 && len(b.PreRelease) != 0 {
return 1 // "1.0.0" > "1.0.0-alpha"
}
return diff
}

Expand All @@ -37,7 +63,7 @@ type versionRange struct {
end v // Use 0.0.0 for "no end"
}

func isVersionSupported(ranges []versionRange, version []int) bool {
func isVersionSupported(ranges []versionRange, version Semver) bool {
for _, r := range ranges {
if compareVersions(r.start, version) <= 0 && (r.end == (v{}) || compareVersions(r.end, version) > 0) {
return true
Expand Down
63 changes: 63 additions & 0 deletions internal/compat/compat_test.go
@@ -0,0 +1,63 @@
package compat

import (
"fmt"
"testing"

"github.com/evanw/esbuild/internal/test"
)

func TestCompareVersions(t *testing.T) {
t.Helper()

check := func(a v, b Semver, expected rune) {
t.Helper()

at := fmt.Sprintf("%d.%d.%d", a.major, a.minor, a.patch)
bt := b.String()

t.Run(fmt.Sprintf("%q ? %q", at, bt), func(t *testing.T) {
observed := '='
if result := compareVersions(a, b); result < 0 {
observed = '<'
} else if result > 0 {
observed = '>'
}
if observed != expected {
test.AssertEqual(t, fmt.Sprintf("%c", observed), fmt.Sprintf("%c", expected))
}
})
}

check(v{0, 0, 0}, Semver{}, '=')

check(v{1, 0, 0}, Semver{}, '>')
check(v{0, 1, 0}, Semver{}, '>')
check(v{0, 0, 1}, Semver{}, '>')

check(v{0, 0, 0}, Semver{Parts: []int{1}}, '<')
check(v{0, 0, 0}, Semver{Parts: []int{0, 1}}, '<')
check(v{0, 0, 0}, Semver{Parts: []int{0, 0, 1}}, '<')

check(v{0, 4, 0}, Semver{Parts: []int{0, 5, 0}}, '<')
check(v{0, 5, 0}, Semver{Parts: []int{0, 5, 0}}, '=')
check(v{0, 6, 0}, Semver{Parts: []int{0, 5, 0}}, '>')

check(v{0, 5, 0}, Semver{Parts: []int{0, 5, 1}}, '<')
check(v{0, 5, 0}, Semver{Parts: []int{0, 5, 0}}, '=')
check(v{0, 5, 1}, Semver{Parts: []int{0, 5, 0}}, '>')

check(v{0, 5, 0}, Semver{Parts: []int{0, 5}}, '=')
check(v{0, 5, 1}, Semver{Parts: []int{0, 5}}, '>')

check(v{1, 0, 0}, Semver{Parts: []int{1}}, '=')
check(v{1, 1, 0}, Semver{Parts: []int{1}}, '>')
check(v{1, 0, 1}, Semver{Parts: []int{1}}, '>')

check(v{1, 2, 0}, Semver{Parts: []int{1, 2}, PreRelease: "-pre"}, '>')
check(v{1, 2, 1}, Semver{Parts: []int{1, 2}, PreRelease: "-pre"}, '>')
check(v{1, 1, 0}, Semver{Parts: []int{1, 2}, PreRelease: "-pre"}, '<')

check(v{1, 2, 3}, Semver{Parts: []int{1, 2, 3}, PreRelease: "-pre"}, '>')
check(v{1, 2, 2}, Semver{Parts: []int{1, 2, 3}, PreRelease: "-pre"}, '<')
}
4 changes: 2 additions & 2 deletions internal/compat/css_table.go
Expand Up @@ -85,7 +85,7 @@ var cssTable = map[CSSFeature]map[Engine][]versionRange{
}

// Return all features that are not available in at least one environment
func UnsupportedCSSFeatures(constraints map[Engine][]int) (unsupported CSSFeature) {
func UnsupportedCSSFeatures(constraints map[Engine]Semver) (unsupported CSSFeature) {
for feature, engines := range cssTable {
if feature == InlineStyle {
continue // This is purely user-specified
Expand Down Expand Up @@ -275,7 +275,7 @@ var cssPrefixTable = map[css_ast.D][]prefixData{
},
}

func CSSPrefixData(constraints map[Engine][]int) (entries map[css_ast.D]CSSPrefix) {
func CSSPrefixData(constraints map[Engine]Semver) (entries map[css_ast.D]CSSPrefix) {
for property, items := range cssPrefixTable {
prefixes := NoPrefix
for engine, version := range constraints {
Expand Down
2 changes: 1 addition & 1 deletion internal/compat/js_table.go
Expand Up @@ -767,7 +767,7 @@ var jsTable = map[JSFeature]map[Engine][]versionRange{
}

// Return all features that are not available in at least one environment
func UnsupportedJSFeatures(constraints map[Engine][]int) (unsupported JSFeature) {
func UnsupportedJSFeatures(constraints map[Engine]Semver) (unsupported JSFeature) {
for feature, engines := range jsTable {
if feature == InlineScript {
continue // This is purely user-specified
Expand Down
16 changes: 8 additions & 8 deletions internal/css_parser/css_parser_test.go
Expand Up @@ -60,14 +60,14 @@ func expectPrintedLowerUnsupported(t *testing.T, unsupportedCSSFeatures compat.C
func expectPrintedWithAllPrefixes(t *testing.T, contents string, expected string, expectedLog string) {
t.Helper()
expectPrintedCommon(t, contents+" [prefixed]", contents, expected, expectedLog, config.LoaderCSS, config.Options{
CSSPrefixData: compat.CSSPrefixData(map[compat.Engine][]int{
compat.Chrome: {0},
compat.Edge: {0},
compat.Firefox: {0},
compat.IE: {0},
compat.IOS: {0},
compat.Opera: {0},
compat.Safari: {0},
CSSPrefixData: compat.CSSPrefixData(map[compat.Engine]compat.Semver{
compat.Chrome: {Parts: []int{0}},
compat.Edge: {Parts: []int{0}},
compat.Firefox: {Parts: []int{0}},
compat.IE: {Parts: []int{0}},
compat.IOS: {Parts: []int{0}},
compat.Opera: {Parts: []int{0}},
compat.Safari: {Parts: []int{0}},
}),
})
}
Expand Down
20 changes: 10 additions & 10 deletions internal/js_parser/js_parser_test.go
Expand Up @@ -39,8 +39,8 @@ func expectParseError(t *testing.T, contents string, expected string) {
func expectParseErrorTarget(t *testing.T, esVersion int, contents string, expected string) {
t.Helper()
expectParseErrorCommon(t, contents, expected, config.Options{
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{
compat.ES: {esVersion},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{esVersion}},
}),
})
}
Expand Down Expand Up @@ -108,17 +108,17 @@ func expectPrintedNormalAndMangle(t *testing.T, contents string, normal string,
func expectPrintedTarget(t *testing.T, esVersion int, contents string, expected string) {
t.Helper()
expectPrintedCommon(t, contents, expected, config.Options{
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{
compat.ES: {esVersion},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{esVersion}},
}),
})
}

func expectPrintedMangleTarget(t *testing.T, esVersion int, contents string, expected string) {
t.Helper()
expectPrintedCommon(t, contents, expected, config.Options{
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{
compat.ES: {esVersion},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{esVersion}},
}),
MinifySyntax: true,
})
Expand All @@ -134,8 +134,8 @@ func expectPrintedASCII(t *testing.T, contents string, expected string) {
func expectPrintedTargetASCII(t *testing.T, esVersion int, contents string, expected string) {
t.Helper()
expectPrintedCommon(t, contents, expected, config.Options{
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{
compat.ES: {esVersion},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{esVersion}},
}),
ASCIIOnly: true,
})
Expand All @@ -144,8 +144,8 @@ func expectPrintedTargetASCII(t *testing.T, esVersion int, contents string, expe
func expectParseErrorTargetASCII(t *testing.T, esVersion int, contents string, expected string) {
t.Helper()
expectParseErrorCommon(t, contents, expected, config.Options{
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{
compat.ES: {esVersion},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{esVersion}},
}),
ASCIIOnly: true,
})
Expand Down
16 changes: 8 additions & 8 deletions internal/js_parser/ts_parser_test.go
Expand Up @@ -44,8 +44,8 @@ func expectParseErrorTargetTS(t *testing.T, esVersion int, contents string, expe
TS: config.TSOptions{
Parse: true,
},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{
compat.ES: {esVersion},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{esVersion}},
}),
})
}
Expand Down Expand Up @@ -80,8 +80,8 @@ func expectPrintedAssignSemanticsTargetTS(t *testing.T, esVersion int, contents
UseDefineForClassFields: config.False,
},
},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{
compat.ES: {esVersion},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{esVersion}},
}),
})
}
Expand Down Expand Up @@ -127,8 +127,8 @@ func expectPrintedTargetTS(t *testing.T, esVersion int, contents string, expecte
TS: config.TSOptions{
Parse: true,
},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{
compat.ES: {esVersion},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{esVersion}},
}),
})
}
Expand All @@ -142,8 +142,8 @@ func expectPrintedTargetExperimentalDecoratorTS(t *testing.T, esVersion int, con
ExperimentalDecorators: config.True,
},
},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{
compat.ES: {esVersion},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{esVersion}},
}),
})
}
Expand Down
16 changes: 8 additions & 8 deletions internal/js_printer/js_printer_test.go
Expand Up @@ -93,17 +93,17 @@ func expectPrintedMinifyASCII(t *testing.T, contents string, expected string) {
func expectPrintedTarget(t *testing.T, esVersion int, contents string, expected string) {
t.Helper()
expectPrintedCommon(t, contents, contents, expected, config.Options{
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{
compat.ES: {esVersion},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{esVersion}},
}),
})
}

func expectPrintedTargetMinify(t *testing.T, esVersion int, contents string, expected string) {
t.Helper()
expectPrintedCommon(t, contents+" [minified]", contents, expected, config.Options{
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{
compat.ES: {esVersion},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{esVersion}},
}),
MinifyWhitespace: true,
})
Expand All @@ -112,8 +112,8 @@ func expectPrintedTargetMinify(t *testing.T, esVersion int, contents string, exp
func expectPrintedTargetMangle(t *testing.T, esVersion int, contents string, expected string) {
t.Helper()
expectPrintedCommon(t, contents+" [mangled]", contents, expected, config.Options{
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{
compat.ES: {esVersion},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{esVersion}},
}),
MinifySyntax: true,
})
Expand All @@ -122,8 +122,8 @@ func expectPrintedTargetMangle(t *testing.T, esVersion int, contents string, exp
func expectPrintedTargetASCII(t *testing.T, esVersion int, contents string, expected string) {
t.Helper()
expectPrintedCommon(t, contents+" [ascii]", contents, expected, config.Options{
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{
compat.ES: {esVersion},
UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine]compat.Semver{
compat.ES: {Parts: []int{esVersion}},
}),
ASCIIOnly: true,
})
Expand Down

0 comments on commit ea9c644

Please sign in to comment.