Repository: ogen-go/ogen
Pull request #665 (2022-11-09 04:06:52)

fix(ogenregex): try to convert ECMA-262 to RE2

codecov[bot]: # [Codecov](https://codecov.io/gh/ogen-go/ogen/pull/665?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go) Report
Base: **73.73**% // Head: **72.96**% // Decreases project coverage by **`-0.76%`** :warning:
> Coverage data is based on head [(`20bdcc7`)](https://codecov.io/gh/ogen-go/ogen/pull/665?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go) compared to base [(`d555b32`)](https://codecov.io/gh/ogen-go/ogen/commit/d555b32df6acf849585bdb97ed8ea7ce9e6a99f6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go).
> Patch coverage: 46.49% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #665      +/-   ##
==========================================
- Coverage   73.73%   72.96%   -0.77%     
==========================================
  Files         171      172       +1     
  Lines       11995    12334     +339     
==========================================
+ Hits         8844     9000     +156     
- Misses       2682     2853     +171     
- Partials      469      481      +12     
```


| [Impacted Files](https://codecov.io/gh/ogen-go/ogen/pull/665?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go) | Coverage Δ | |
|---|---|---|
| [ogenregex/convert.go](https://codecov.io/gh/ogen-go/ogen/pull/665/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go#diff-b2dlbnJlZ2V4L2NvbnZlcnQuZ28=) | `45.85% <45.85%> (ø)` | |
| [ogenregex/ogenregex.go](https://codecov.io/gh/ogen-go/ogen/pull/665/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go#diff-b2dlbnJlZ2V4L29nZW5yZWdleC5nbw==) | `84.00% <100.00%> (+0.66%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/ogen-go/ogen/pull/665?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ogen-go).


Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 5d3fa1bc..7826917b 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -123,6 +123,11 @@ issues:
       linters: [ gocritic ]
       text: sprintfQuotedString
 
+    # Keep empty fallthrough for better readability.
+    - path: ogenregex[/\\]convert\.go
+      linters: [ gocritic ]
+      text: emptyFallthrough
+
     # Not supposed to be constant.
     - linters: [ goconst ]
       text: 'string `(string|number|u?int\d{0,2})`'
diff --git a/examples/go.mod b/examples/go.mod
index a45d9e6d..0121b595 100644
--- a/examples/go.mod
+++ b/examples/go.mod
@@ -33,6 +33,7 @@ require (
 	go.uber.org/atomic v1.9.0 // indirect
 	golang.org/x/sync v0.1.0 // indirect
 	golang.org/x/sys v0.2.0 // indirect
+	golang.org/x/text v0.4.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/examples/go.sum b/examples/go.sum
index 958464a2..4f06dd70 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -73,6 +73,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
diff --git a/go.mod b/go.mod
index 6320409d..194b1aa2 100644
--- a/go.mod
+++ b/go.mod
@@ -21,6 +21,7 @@ require (
 	golang.org/x/exp v0.0.0-20221106115401-f9659909a136
 	golang.org/x/net v0.2.0
 	golang.org/x/sync v0.1.0
+	golang.org/x/text v0.4.0
 	golang.org/x/tools v0.2.0
 )
 
@@ -40,7 +41,6 @@ require (
 	go.uber.org/atomic v1.9.0 // indirect
 	golang.org/x/mod v0.6.0 // indirect
 	golang.org/x/sys v0.2.0 // indirect
-	golang.org/x/text v0.4.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/internal/integration/sample_api/oas_cfg_gen.go b/internal/integration/sample_api/oas_cfg_gen.go
index 1bd0c5d6..77c29b46 100644
--- a/internal/integration/sample_api/oas_cfg_gen.go
+++ b/internal/integration/sample_api/oas_cfg_gen.go
@@ -21,9 +21,9 @@ import (
 
 var regexMap = map[string]ogenregex.Regexp{
 	"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$": ogenregex.MustCompile("^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$"),
-	"^\\d-\\d$": ogenregex.MustCompile("^\\d-\\d$"),
-	"foo.*":     ogenregex.MustCompile("foo.*"),
-	"string_.*": ogenregex.MustCompile("string_.*"),
+	"^\\d-\\d$":                   ogenregex.MustCompile("^\\d-\\d$"),
+	"foo[^\r\n\u2028\u2029]*":     ogenregex.MustCompile("foo[^\r\n\u2028\u2029]*"),
+	"string_[^\r\n\u2028\u2029]*": ogenregex.MustCompile("string_[^\r\n\u2028\u2029]*"),
 }
 var ratMap = map[string]*big.Rat{
 	"10": func() *big.Rat {
diff --git a/internal/integration/sample_api/oas_json_gen.go b/internal/integration/sample_api/oas_json_gen.go
index f4c89cde..1fab8573 100644
--- a/internal/integration/sample_api/oas_json_gen.go
+++ b/internal/integration/sample_api/oas_json_gen.go
@@ -4672,7 +4672,7 @@ func (s *OnlyPatternedPropsObject) Decode(d *jx.Decoder) error {
 		return errors.New("invalid: unable to decode OnlyPatternedPropsObject to nil")
 	}
 	m := s.init()
-	pattern := regexMap["string_.*"]
+	pattern := regexMap["string_[^\r\n\u2028\u2029]*"]
 	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
 		switch match, err := pattern.Match(k); {
 		case err != nil:
@@ -6069,7 +6069,7 @@ func (s *PatternRecursiveMap) Decode(d *jx.Decoder) error {
 		return errors.New("invalid: unable to decode PatternRecursiveMap to nil")
 	}
 	m := s.init()
-	pattern := regexMap["foo.*"]
+	pattern := regexMap["foo[^\r\n\u2028\u2029]*"]
 	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
 		switch match, err := pattern.Match(k); {
 		case err != nil:
@@ -7198,7 +7198,7 @@ func (s *StringIntMap) Decode(d *jx.Decoder) error {
 		switch string(k) {
 		default:
 			var handled bool
-			switch match, err := regexMap["string_.*"].Match(k); {
+			switch match, err := regexMap["string_[^\r\n\u2028\u2029]*"].Match(k); {
 			case err != nil:
 				return errors.Wrap(err, "execute regex")
 			case match:
@@ -7331,7 +7331,7 @@ func (s *StringIntMapPattern0) Decode(d *jx.Decoder) error {
 		return errors.New("invalid: unable to decode StringIntMapPattern0 to nil")
 	}
 	m := s.init()
-	pattern := regexMap["string_.*"]
+	pattern := regexMap["string_[^\r\n\u2028\u2029]*"]
 	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
 		switch match, err := pattern.Match(k); {
 		case err != nil:
diff --git a/internal/integration/sample_api/oas_schemas_gen.go b/internal/integration/sample_api/oas_schemas_gen.go
index fb3026a9..b399fecb 100644
--- a/internal/integration/sample_api/oas_schemas_gen.go
+++ b/internal/integration/sample_api/oas_schemas_gen.go
@@ -4607,7 +4607,7 @@ func (s *RecursiveMapAdditional) init() RecursiveMapAdditional {
 // Ref: #/components/schemas/StringIntMap
 type StringIntMap struct {
 	AdditionalProps StringIntMapAdditional
-	// Pattern: "string_.*".
+	// Pattern: "string_[^\r\n\u2028\u2029]*".
 	Pattern0Props StringIntMapPattern0
 }
 
diff --git a/internal/integration/sample_api_nc/oas_cfg_gen.go b/internal/integration/sample_api_nc/oas_cfg_gen.go
index 5c557ff6..f800f5ba 100644
--- a/internal/integration/sample_api_nc/oas_cfg_gen.go
+++ b/internal/integration/sample_api_nc/oas_cfg_gen.go
@@ -20,9 +20,9 @@ import (
 
 var regexMap = map[string]ogenregex.Regexp{
 	"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$": ogenregex.MustCompile("^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$"),
-	"^\\d-\\d$": ogenregex.MustCompile("^\\d-\\d$"),
-	"foo.*":     ogenregex.MustCompile("foo.*"),
-	"string_.*": ogenregex.MustCompile("string_.*"),
+	"^\\d-\\d$":                   ogenregex.MustCompile("^\\d-\\d$"),
+	"foo[^\r\n\u2028\u2029]*":     ogenregex.MustCompile("foo[^\r\n\u2028\u2029]*"),
+	"string_[^\r\n\u2028\u2029]*": ogenregex.MustCompile("string_[^\r\n\u2028\u2029]*"),
 }
 var ratMap = map[string]*big.Rat{
 	"10": func() *big.Rat {
diff --git a/internal/integration/sample_api_nc/oas_json_gen.go b/internal/integration/sample_api_nc/oas_json_gen.go
index f4c89cde..1fab8573 100644
--- a/internal/integration/sample_api_nc/oas_json_gen.go
+++ b/internal/integration/sample_api_nc/oas_json_gen.go
@@ -4672,7 +4672,7 @@ func (s *OnlyPatternedPropsObject) Decode(d *jx.Decoder) error {
 		return errors.New("invalid: unable to decode OnlyPatternedPropsObject to nil")
 	}
 	m := s.init()
-	pattern := regexMap["string_.*"]
+	pattern := regexMap["string_[^\r\n\u2028\u2029]*"]
 	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
 		switch match, err := pattern.Match(k); {
 		case err != nil:
@@ -6069,7 +6069,7 @@ func (s *PatternRecursiveMap) Decode(d *jx.Decoder) error {
 		return errors.New("invalid: unable to decode PatternRecursiveMap to nil")
 	}
 	m := s.init()
-	pattern := regexMap["foo.*"]
+	pattern := regexMap["foo[^\r\n\u2028\u2029]*"]
 	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
 		switch match, err := pattern.Match(k); {
 		case err != nil:
@@ -7198,7 +7198,7 @@ func (s *StringIntMap) Decode(d *jx.Decoder) error {
 		switch string(k) {
 		default:
 			var handled bool
-			switch match, err := regexMap["string_.*"].Match(k); {
+			switch match, err := regexMap["string_[^\r\n\u2028\u2029]*"].Match(k); {
 			case err != nil:
 				return errors.Wrap(err, "execute regex")
 			case match:
@@ -7331,7 +7331,7 @@ func (s *StringIntMapPattern0) Decode(d *jx.Decoder) error {
 		return errors.New("invalid: unable to decode StringIntMapPattern0 to nil")
 	}
 	m := s.init()
-	pattern := regexMap["string_.*"]
+	pattern := regexMap["string_[^\r\n\u2028\u2029]*"]
 	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
 		switch match, err := pattern.Match(k); {
 		case err != nil:
diff --git a/internal/integration/sample_api_nc/oas_schemas_gen.go b/internal/integration/sample_api_nc/oas_schemas_gen.go
index fb3026a9..b399fecb 100644
--- a/internal/integration/sample_api_nc/oas_schemas_gen.go
+++ b/internal/integration/sample_api_nc/oas_schemas_gen.go
@@ -4607,7 +4607,7 @@ func (s *RecursiveMapAdditional) init() RecursiveMapAdditional {
 // Ref: #/components/schemas/StringIntMap
 type StringIntMap struct {
 	AdditionalProps StringIntMapAdditional
-	// Pattern: "string_.*".
+	// Pattern: "string_[^\r\n\u2028\u2029]*".
 	Pattern0Props StringIntMapPattern0
 }
 
diff --git a/internal/integration/sample_api_ns/oas_cfg_gen.go b/internal/integration/sample_api_ns/oas_cfg_gen.go
index 64a2e786..9514886c 100644
--- a/internal/integration/sample_api_ns/oas_cfg_gen.go
+++ b/internal/integration/sample_api_ns/oas_cfg_gen.go
@@ -19,9 +19,9 @@ import (
 
 var regexMap = map[string]ogenregex.Regexp{
 	"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$": ogenregex.MustCompile("^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$"),
-	"^\\d-\\d$": ogenregex.MustCompile("^\\d-\\d$"),
-	"foo.*":     ogenregex.MustCompile("foo.*"),
-	"string_.*": ogenregex.MustCompile("string_.*"),
+	"^\\d-\\d$":                   ogenregex.MustCompile("^\\d-\\d$"),
+	"foo[^\r\n\u2028\u2029]*":     ogenregex.MustCompile("foo[^\r\n\u2028\u2029]*"),
+	"string_[^\r\n\u2028\u2029]*": ogenregex.MustCompile("string_[^\r\n\u2028\u2029]*"),
 }
 var ratMap = map[string]*big.Rat{
 	"10": func() *big.Rat {
diff --git a/internal/integration/sample_api_ns/oas_json_gen.go b/internal/integration/sample_api_ns/oas_json_gen.go
index f4c89cde..1fab8573 100644
--- a/internal/integration/sample_api_ns/oas_json_gen.go
+++ b/internal/integration/sample_api_ns/oas_json_gen.go
@@ -4672,7 +4672,7 @@ func (s *OnlyPatternedPropsObject) Decode(d *jx.Decoder) error {
 		return errors.New("invalid: unable to decode OnlyPatternedPropsObject to nil")
 	}
 	m := s.init()
-	pattern := regexMap["string_.*"]
+	pattern := regexMap["string_[^\r\n\u2028\u2029]*"]
 	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
 		switch match, err := pattern.Match(k); {
 		case err != nil:
@@ -6069,7 +6069,7 @@ func (s *PatternRecursiveMap) Decode(d *jx.Decoder) error {
 		return errors.New("invalid: unable to decode PatternRecursiveMap to nil")
 	}
 	m := s.init()
-	pattern := regexMap["foo.*"]
+	pattern := regexMap["foo[^\r\n\u2028\u2029]*"]
 	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
 		switch match, err := pattern.Match(k); {
 		case err != nil:
@@ -7198,7 +7198,7 @@ func (s *StringIntMap) Decode(d *jx.Decoder) error {
 		switch string(k) {
 		default:
 			var handled bool
-			switch match, err := regexMap["string_.*"].Match(k); {
+			switch match, err := regexMap["string_[^\r\n\u2028\u2029]*"].Match(k); {
 			case err != nil:
 				return errors.Wrap(err, "execute regex")
 			case match:
@@ -7331,7 +7331,7 @@ func (s *StringIntMapPattern0) Decode(d *jx.Decoder) error {
 		return errors.New("invalid: unable to decode StringIntMapPattern0 to nil")
 	}
 	m := s.init()
-	pattern := regexMap["string_.*"]
+	pattern := regexMap["string_[^\r\n\u2028\u2029]*"]
 	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
 		switch match, err := pattern.Match(k); {
 		case err != nil:
diff --git a/internal/integration/sample_api_ns/oas_schemas_gen.go b/internal/integration/sample_api_ns/oas_schemas_gen.go
index fb3026a9..b399fecb 100644
--- a/internal/integration/sample_api_ns/oas_schemas_gen.go
+++ b/internal/integration/sample_api_ns/oas_schemas_gen.go
@@ -4607,7 +4607,7 @@ func (s *RecursiveMapAdditional) init() RecursiveMapAdditional {
 // Ref: #/components/schemas/StringIntMap
 type StringIntMap struct {
 	AdditionalProps StringIntMapAdditional
-	// Pattern: "string_.*".
+	// Pattern: "string_[^\r\n\u2028\u2029]*".
 	Pattern0Props StringIntMapPattern0
 }
 
diff --git a/internal/integration/sample_api_nsnc/oas_cfg_gen.go b/internal/integration/sample_api_nsnc/oas_cfg_gen.go
index 80496a96..bdc50cc6 100644
--- a/internal/integration/sample_api_nsnc/oas_cfg_gen.go
+++ b/internal/integration/sample_api_nsnc/oas_cfg_gen.go
@@ -11,9 +11,9 @@ import (
 
 var regexMap = map[string]ogenregex.Regexp{
 	"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$": ogenregex.MustCompile("^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$"),
-	"^\\d-\\d$": ogenregex.MustCompile("^\\d-\\d$"),
-	"foo.*":     ogenregex.MustCompile("foo.*"),
-	"string_.*": ogenregex.MustCompile("string_.*"),
+	"^\\d-\\d$":                   ogenregex.MustCompile("^\\d-\\d$"),
+	"foo[^\r\n\u2028\u2029]*":     ogenregex.MustCompile("foo[^\r\n\u2028\u2029]*"),
+	"string_[^\r\n\u2028\u2029]*": ogenregex.MustCompile("string_[^\r\n\u2028\u2029]*"),
 }
 var ratMap = map[string]*big.Rat{
 	"10": func() *big.Rat {
diff --git a/internal/integration/sample_api_nsnc/oas_json_gen.go b/internal/integration/sample_api_nsnc/oas_json_gen.go
index f4c89cde..1fab8573 100644
--- a/internal/integration/sample_api_nsnc/oas_json_gen.go
+++ b/internal/integration/sample_api_nsnc/oas_json_gen.go
@@ -4672,7 +4672,7 @@ func (s *OnlyPatternedPropsObject) Decode(d *jx.Decoder) error {
 		return errors.New("invalid: unable to decode OnlyPatternedPropsObject to nil")
 	}
 	m := s.init()
-	pattern := regexMap["string_.*"]
+	pattern := regexMap["string_[^\r\n\u2028\u2029]*"]
 	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
 		switch match, err := pattern.Match(k); {
 		case err != nil:
@@ -6069,7 +6069,7 @@ func (s *PatternRecursiveMap) Decode(d *jx.Decoder) error {
 		return errors.New("invalid: unable to decode PatternRecursiveMap to nil")
 	}
 	m := s.init()
-	pattern := regexMap["foo.*"]
+	pattern := regexMap["foo[^\r\n\u2028\u2029]*"]
 	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
 		switch match, err := pattern.Match(k); {
 		case err != nil:
@@ -7198,7 +7198,7 @@ func (s *StringIntMap) Decode(d *jx.Decoder) error {
 		switch string(k) {
 		default:
 			var handled bool
-			switch match, err := regexMap["string_.*"].Match(k); {
+			switch match, err := regexMap["string_[^\r\n\u2028\u2029]*"].Match(k); {
 			case err != nil:
 				return errors.Wrap(err, "execute regex")
 			case match:
@@ -7331,7 +7331,7 @@ func (s *StringIntMapPattern0) Decode(d *jx.Decoder) error {
 		return errors.New("invalid: unable to decode StringIntMapPattern0 to nil")
 	}
 	m := s.init()
-	pattern := regexMap["string_.*"]
+	pattern := regexMap["string_[^\r\n\u2028\u2029]*"]
 	if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
 		switch match, err := pattern.Match(k); {
 		case err != nil:
diff --git a/internal/integration/sample_api_nsnc/oas_schemas_gen.go b/internal/integration/sample_api_nsnc/oas_schemas_gen.go
index fb3026a9..b399fecb 100644
--- a/internal/integration/sample_api_nsnc/oas_schemas_gen.go
+++ b/internal/integration/sample_api_nsnc/oas_schemas_gen.go
@@ -4607,7 +4607,7 @@ func (s *RecursiveMapAdditional) init() RecursiveMapAdditional {
 // Ref: #/components/schemas/StringIntMap
 type StringIntMap struct {
 	AdditionalProps StringIntMapAdditional
-	// Pattern: "string_.*".
+	// Pattern: "string_[^\r\n\u2028\u2029]*".
 	Pattern0Props StringIntMapPattern0
 }
 
diff --git a/ogenregex/convert.go b/ogenregex/convert.go
new file mode 100644
index 00000000..a08d3592
--- /dev/null
+++ b/ogenregex/convert.go
@@ -0,0 +1,470 @@
+package ogenregex
+
+import (
+	"strconv"
+	"strings"
+	"unicode"
+	"unicode/utf8"
+
+	"github.com/go-faster/errors"
+	"golang.org/x/text/unicode/rangetable"
+)
+
+// Copied from dop251/goja, to avoid dependency.
+//
+// All rights belong to the original author.
+//
+// https://github.com/dop251/goja/blob/3b8a68ca89b4fa7086a4236695032e10a69b2472/parser/regexp.go#L58
+
+const (
+	whitespaceChars = " \f\n\r\t\v" +
+		"\u00a0\u1680" +
+		"\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a" +
+		"\u2028\u2029" +
+		"\u202f\u205f" +
+		"\u3000\ufeff"
+	re2Dot = "[^\r\n\u2028\u2029]"
+)
+
+func digitValue(chr rune) int {
+	switch {
+	case '0' <= chr && chr <= '9':
+		return int(chr - '0')
+	case 'a' <= chr && chr <= 'f':
+		return int(chr - 'a' + 10)
+	case 'A' <= chr && chr <= 'F':
+		return int(chr - 'A' + 10)
+	}
+	return 16 // Larger than any legal digit value
+}
+
+var (
+	unicodeRangeIDNeg      = rangetable.Merge(unicode.Pattern_Syntax, unicode.Pattern_White_Space)
+	unicodeRangeIDStartPos = rangetable.Merge(unicode.Letter, unicode.Nl, unicode.Other_ID_Start)
+	unicodeRangeIDContPos  = rangetable.Merge(
+		unicodeRangeIDStartPos,
+		unicode.Mn,
+		unicode.Mc,
+		unicode.Nd,
+		unicode.Pc,
+		unicode.Other_ID_Continue,
+	)
+)
+
+func isIDPartUnicode(r rune) bool {
+	return unicode.Is(unicodeRangeIDContPos, r) && !unicode.Is(unicodeRangeIDNeg, r) || r == '\u200C' || r == '\u200D'
+}
+
+func isIdentifierPart(chr rune) bool {
+	return chr == '$' || chr == '_' || chr == '\\' ||
+		'a' <= chr && chr <= 'z' || 'A' <= chr && chr <= 'Z' ||
+		'0' <= chr && chr <= '9' ||
+		chr >= utf8.RuneSelf && isIDPartUnicode(chr)
+}
+
+// Convert converts a ECMA-262 regular expression to Go's regular expression.
+//
+// If the conversion is not possible, ("", false) is returned.
+func Convert(pattern string) (string, bool) {
+	if pattern == "" {
+		return "", true
+	}
+
+	p := parser{
+		str:    pattern,
+		length: len(pattern),
+	}
+	if err := p.parse(); err != nil {
+		return "", false
+	}
+
+	return p.ResultString(), true
+}
+
+type parser struct {
+	str    string
+	length int
+
+	chr       rune // The current character
+	chrOffset int  // The offset of current character
+	offset    int  // The offset after current character (may be greater than 1)
+
+	err error
+
+	goRegexp   strings.Builder
+	passOffset int
+}
+
+func (p *parser) ResultString() string {
+	if p.passOffset != -1 {
+		return p.str[:p.passOffset]
+	}
+	return p.goRegexp.String()
+}
+
+func (p *parser) parse() error {
+	p.read() // Pull in the first character
+	p.scan()
+	return p.err
+}
+
+func (p *parser) read() {
+	if p.offset < p.length {
+		p.chrOffset = p.offset
+		chr, width := rune(p.str[p.offset]), 1
+		if chr >= utf8.RuneSelf { // !ASCII
+			chr, width = utf8.DecodeRuneInString(p.str[p.offset:])
+			if chr == utf8.RuneError && width == 1 {
+				p.error(true, "Invalid UTF-8 character")
+				return
+			}
+		}
+		p.offset += width
+		p.chr = chr
+	} else {
+		p.chrOffset = p.length
+		p.chr = -1 // EOF
+	}
+}
+
+func (p *parser) stopPassing() {
+	p.goRegexp.Grow(3 * len(p.str) / 2)
+	p.goRegexp.WriteString(p.str[:p.passOffset])
+	p.passOffset = -1
+}
+
+func (p *parser) write(data []byte) {
+	if p.passOffset != -1 {
+		p.stopPassing()
+	}
+	p.goRegexp.Write(data)
+}
+
+func (p *parser) writeByte(b byte) {
+	if p.passOffset != -1 {
+		p.stopPassing()
+	}
+	p.goRegexp.WriteByte(b)
+}
+
+func (p *parser) writeString(s string) {
+	if p.passOffset != -1 {
+		p.stopPassing()
+	}
+	p.goRegexp.WriteString(s)
+}
+
+func (p *parser) scan() {
+	for p.chr != -1 {
+		switch p.chr {
+		case '\\':
+			p.read()
+			p.scanEscape(false)
+		case '(':
+			p.pass()
+			p.scanGroup()
+		case '[':
+			p.scanBracket()
+		case ')':
+			p.error(true, "Unmatched ')'")
+			return
+		case '.':
+			p.writeString(re2Dot)
+			p.read()
+		default:
+			p.pass()
+		}
+	}
+}
+
+// (...)
+func (p *parser) scanGroup() {
+	str := p.str[p.chrOffset:]
+	if len(str) > 1 { // A possibility of (?= or (?!
+		if str[0] == '?' {
+			ch := str[1]
+			switch {
+			case ch == '=' || ch == '!':
+				p.error(false, "re2: Invalid (%s) <lookahead>", p.str[p.chrOffset:p.chrOffset+2])
+				return
+			case ch == '<':
+				p.error(false, "re2: Invalid (%s) <lookbehind>", p.str[p.chrOffset:p.chrOffset+2])
+				return
+			case ch != ':':
+				p.error(true, "Invalid group")
+				return
+			}
+		}
+	}
+	for p.chr != -1 && p.chr != ')' {
+		switch p.chr {
+		case '\\':
+			p.read()
+			p.scanEscape(false)
+		case '(':
+			p.pass()
+			p.scanGroup()
+		case '[':
+			p.scanBracket()
+		case '.':
+			p.writeString(re2Dot)
+			p.read()
+		default:
+			p.pass()
+			continue
+		}
+	}
+	if p.chr != ')' {
+		p.error(true, "Unterminated group")
+		return
+	}
+	p.pass()
+}
+
+// [...]
+func (p *parser) scanBracket() {
+	str := p.str[p.chrOffset:]
+	if strings.HasPrefix(str, "[]") {
+		// [] -- Empty character class
+		p.writeString("[^\u0000-\U0001FFFF]")
+		p.offset++
+		p.read()
+		return
+	}
+
+	if strings.HasPrefix(str, "[^]") {
+		p.writeString("[\u0000-\U0001FFFF]")
+		p.offset += 2
+		p.read()
+		return
+	}
+
+	p.pass()
+	for p.chr != -1 {
+		if p.chr == ']' {
+			break
+		} else if p.chr == '\\' {
+			p.read()
+			p.scanEscape(true)
+			continue
+		}
+		p.pass()
+	}
+	if p.chr != ']' {
+		p.error(true, "Unterminated character class")
+		return
+	}
+	p.pass()
+}
+
+// \...
+func (p *parser) scanEscape(inClass bool) {
+	offset := p.chrOffset
+
+	var length, base uint32
+	switch p.chr {
+	case '0', '1', '2', '3', '4', '5', '6', '7':
+		var value int64
+		size := 0
+		for {
+			digit := int64(digitValue(p.chr))
+			if digit >= 8 {
+				// Not a valid digit
+				break
+			}
+			value = value*8 + digit
+			p.read()
+			size++
+		}
+		if size == 1 { // The number of characters read
+			if value != 0 {
+				// An invalid backreference
+				p.error(false, "re2: Invalid \\%d <backreference>", value)
+				return
+			}
+			p.passString(offset-1, p.chrOffset)
+			return
+		}
+		tmp := []byte{'\\', 'x', '0', 0}
+		if value >= 16 {
+			tmp = tmp[0:2]
+		} else {
+			tmp = tmp[0:3]
+		}
+		tmp = strconv.AppendInt(tmp, value, 16)
+		p.write(tmp)
+		return
+
+	case '8', '9':
+		p.read()
+		p.error(false, "re2: Invalid \\%s <backreference>", p.str[offset:p.chrOffset])
+		return
+
+	case 'x':
+		p.read()
+		length, base = 2, 16
+
+	case 'u':
+		p.read()
+		if p.chr == '{' {
+			p.read()
+			length, base = 0, 16
+		} else {
+			length, base = 4, 16
+		}
+
+	case 'b':
+		if inClass {
+			p.write([]byte{'\\', 'x', '0', '8'})
+			p.read()
+			return
+		}
+		fallthrough
+
+	case 'B':
+		fallthrough
+
+	case 'd', 'D', 'w', 'W':
+		// This is slightly broken, because ECMAScript
+		// includes \v in \s, \S, while re2 does not
+		fallthrough
+
+	case '\\':
+		fallthrough
+
+	case 'f', 'n', 'r', 't', 'v':
+		p.passString(offset-1, p.offset)
+		p.read()
+		return
+
+	case 'c':
+		p.read()
+		var value int64
+		switch {
+		case 'a' <= p.chr && p.chr <= 'z':
+			value = int64(p.chr - 'a' + 1)
+		case 'A' <= p.chr && p.chr <= 'Z':
+			value = int64(p.chr - 'A' + 1)
+		default:
+			p.writeByte('c')
+			return
+		}
+		tmp := []byte{'\\', 'x', '0', 0}
+		if value >= 16 {
+			tmp = tmp[0:2]
+		} else {
+			tmp = tmp[0:3]
+		}
+		tmp = strconv.AppendInt(tmp, value, 16)
+		p.write(tmp)
+		p.read()
+		return
+	case 's':
+		if inClass {
+			p.writeString(whitespaceChars)
+		} else {
+			p.writeString("[" + whitespaceChars + "]")
+		}
+		p.read()
+		return
+	case 'S':
+		if inClass {
+			p.error(false, "S in class")
+			return
+		}
+		p.writeString("[^" + whitespaceChars + "]")
+		p.read()
+		return
+	default:
+		// $ is an identifier character, so we have to have
+		// a special case for it here
+		if p.chr == '$' || p.chr < utf8.RuneSelf && !isIdentifierPart(p.chr) {
+			// A non-identifier character needs escaping
+			p.passString(offset-1, p.offset)
+			p.read()
+			return
+		}
+		// Unescape the character for re2
+		p.pass()
+		return
+	}
+
+	// Otherwise, we're a \u.... or \x...
+	valueOffset := p.chrOffset
+
+	if length > 0 {
+		for length := length; length > 0; length-- {
+			digit := uint32(digitValue(p.chr))
+			if digit >= base {
+				// Not a valid digit
+				goto skip
+			}
+			p.read()
+		}
+	} else {
+		for p.chr != '}' && p.chr != -1 {
+			digit := uint32(digitValue(p.chr))
+			if digit >= base {
+				// Not a valid digit
+				goto skip
+			}
+			p.read()
+		}
+	}
+
+	switch length {
+	case 0, 4:
+		p.write([]byte{'\\', 'x', '{'})
+		p.passString(valueOffset, p.chrOffset)
+		if length != 0 {
+			p.writeByte('}')
+		}
+	case 2:
+		p.passString(offset-1, valueOffset+2)
+	default:
+		// Should never, ever get here...
+		p.error(true, "re2: Illegal branch in scanEscape")
+		return
+	}
+
+	return
+skip:
+	p.passString(offset, p.chrOffset)
+}
+
+func (p *parser) pass() {
+	if p.passOffset == p.chrOffset {
+		p.passOffset = p.offset
+	} else {
+		if p.passOffset != -1 {
+			p.stopPassing()
+		}
+		if p.chr != -1 {
+			p.goRegexp.WriteRune(p.chr)
+		}
+	}
+	p.read()
+}
+
+func (p *parser) passString(start, end int) {
+	if p.passOffset == start {
+		p.passOffset = end
+		return
+	}
+	if p.passOffset != -1 {
+		p.stopPassing()
+	}
+	p.goRegexp.WriteString(p.str[start:end])
+}
+
+func (p *parser) error(fatal bool, format string, args ...interface{}) {
+	if p.err != nil {
+		return
+	}
+	p.err = errors.Errorf(format, args...)
+	if fatal {
+		p.err = errors.Wrap(p.err, "syntax")
+	}
+	p.offset = p.length
+	p.chr = -1
+}
diff --git a/ogenregex/ogenregex.go b/ogenregex/ogenregex.go
index 4cc78ab4..4cc0f7e5 100644
--- a/ogenregex/ogenregex.go
+++ b/ogenregex/ogenregex.go
@@ -65,8 +65,10 @@ type Regexp interface {
 // NOTE: this function may compile the same expression multiple times and can
 // be slow. Compile the expression once and reuse it.
 func Compile(exp string) (Regexp, error) {
-	if re, err := regexp.Compile(exp); err == nil {
-		return goRegexp{re}, nil
+	if converted, ok := Convert(exp); ok {
+		if re, err := regexp.Compile(converted); err == nil {
+			return goRegexp{re}, nil
+		}
 	}
 	re, err := regexp2.Compile(exp, regexp2.ECMAScript|regexp2.Unicode)
 	if err != nil {


Test patch:
diff --git a/ogenregex/ogenregex_test.go b/ogenregex/ogenregex_test.go
index 9a06f949..bb6d09b4 100644
--- a/ogenregex/ogenregex_test.go
+++ b/ogenregex/ogenregex_test.go
@@ -8,17 +8,69 @@ import (
 )
 
 func TestCompile(t *testing.T) {
-	tests := []struct {
+	type testCase struct {
 		input      string
 		wantType   Regexp
 		wantString string
 		wantErr    bool
-	}{
+	}
+
+	tests := []testCase{
+		// Conversion is not required.
+		{`\x20`, goRegexp{}, `\x20`, false},
+		{`\v`, goRegexp{}, `\v`, false},
+		{`\t`, goRegexp{}, `\t`, false},
+		{`\n`, goRegexp{}, `\n`, false},
 		{`\d`, goRegexp{}, `\d`, false},
 		{`\w`, goRegexp{}, `\w`, false},
+		{`\w{1}`, goRegexp{}, `\w{1}`, false},
+		{`\w{1,}`, goRegexp{}, `\w{1,}`, false},
+		{`\w{1,2}`, goRegexp{}, `\w{1,2}`, false},
+		{`\b`, goRegexp{}, `\b`, false},
+		{`\B`, goRegexp{}, `\B`, false},
+		{`\.`, goRegexp{}, `\.`, false},
+		{`\[`, goRegexp{}, `\[`, false},
+		{`\]`, goRegexp{}, `\]`, false},
+		{`\(`, goRegexp{}, `\(`, false},
+		{`\)`, goRegexp{}, `\)`, false},
+		{`\{`, goRegexp{}, `\{`, false},
+		{`\}`, goRegexp{}, `\}`, false},
+		{`\\`, goRegexp{}, `\\`, false},
+		{`\$`, goRegexp{}, `\$`, false},
+
+		// Simplification.
+		{`\u000a`, goRegexp{}, `\x{000a}`, false},
+		{`\u{000a}`, goRegexp{}, `\x{000a}`, false},
+		// "\z" just unnecessarily escapes the 'z'.
+		{`\z`, goRegexp{}, `z`, false},
+
+		// Conversion is required.
+		//
+		// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes#types.
+		//
+		// In ECMA-262, \c + [a-fA-F] is a control character.
+		{`\ca`, goRegexp{}, `\x01`, false},
+		{`\cA`, goRegexp{}, `\x01`, false},
+		{`\cb`, goRegexp{}, `\x02`, false},
+		{`\cB`, goRegexp{}, `\x02`, false},
+		// In ECMA-262, \b in a character class is a backspace.
+		{`[\b]`, goRegexp{}, `[\x08]`, false},
+		// ECMA-262 dot matches any single character except line terminators: \n, \r, \u2028 or \u2029.
+		{`.*`, goRegexp{}, re2Dot + `*`, false},
+		// Whitespace characters in ECMA-262 differ from those in RE2.
+		//
+		// Whitespace characters in ECMA-262:
+		// [ \f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]
+		{`\s`, goRegexp{}, `[` + whitespaceChars + `]`, false},
+		{`\S`, goRegexp{}, `[^` + whitespaceChars + `]`, false},
+		{`[\s]`, goRegexp{}, `[` + whitespaceChars + `]`, false},
+
+		// Use regexp2.
 		{`^(?!examples/)`, regexp2Regexp{}, `^(?!examples/)`, false},
 
+		// Error.
 		{")", nil, ``, true},
+		{"(?`)", nil, ``, true},
 	}
 	for i, tt := range tests {
 		tt := tt