Skip to content

Commit

Permalink
feat(@formatjs/intl-segmenter): Intl.Segmenter polyfill (stage 4) (#3917
Browse files Browse the repository at this point in the history
)
  • Loading branch information
matijagaspar committed Jan 16, 2023
1 parent cc3a81f commit 3020876
Show file tree
Hide file tree
Showing 36 changed files with 18,805 additions and 43 deletions.
1 change: 1 addition & 0 deletions .bazelignore
Expand Up @@ -35,4 +35,5 @@ packages/swc-plugin/node_modules
packages/ts-transformer/integration-tests/node_modules
packages/ts-transformer/node_modules
packages/vue-intl/node_modules
packages/intl-segmenter/node_modules
website/node_modules
3 changes: 2 additions & 1 deletion .prettierignore
Expand Up @@ -14,4 +14,5 @@ test262/
website/.docusaurus
website/.now
website/build
website/CHANGELOG.md
website/CHANGELOG.md
packages/intl-segmenter/unicodeFiles/*
27 changes: 16 additions & 11 deletions BUILD
Expand Up @@ -56,6 +56,7 @@ PACKAGES_TO_DIST = [
"//packages/intl-numberformat",
"//packages/intl-pluralrules",
"//packages/intl-relativetimeformat",
"//packages/intl-segmenter",
"//packages/react-intl",
"//packages/swc-plugin",
"//packages/swc-plugin-experimental",
Expand Down Expand Up @@ -105,6 +106,11 @@ KARMA_TESTS = [

karma_bin.karma_test(
name = "karma",
args = [
"start",
"$(rootpath //:karma.conf.js)",
"--no-single-run",
],
# configuration_env_vars = [
# "SAUCE_USERNAME",
# "SAUCE_ACCESS_KEY",
Expand All @@ -117,16 +123,17 @@ karma_bin.karma_test(
"//:node_modules/karma-jasmine-matchers",
] + KARMA_TESTS,
tags = ["manual"],
args = [
"start",
"$(rootpath //:karma.conf.js)",
"--no-single-run",
],
)

karma_bin.karma_test(
name = "karma-ci",
size = "large",
args = [
"start",
"$(rootpath //:karma.conf.js)",
"--browsers",
"sl_edge,sl_chrome,sl_firefox,sl_ie_11,sl_safari",
],
# configuration_env_vars = [
# "SAUCE_USERNAME",
# "SAUCE_ACCESS_KEY",
Expand All @@ -140,12 +147,6 @@ karma_bin.karma_test(
"//:node_modules/karma-jasmine-matchers",
] + KARMA_TESTS,
tags = ["manual"],
args = [
"start",
"$(rootpath //:karma.conf.js)",
"--browsers",
"sl_edge,sl_chrome,sl_firefox,sl_ie_11,sl_safari",
],
)

multirun(
Expand All @@ -172,6 +173,7 @@ multirun(
"//packages/intl-numberformat:prettier",
"//packages/intl-pluralrules:prettier",
"//packages/intl-relativetimeformat:prettier",
"//packages/intl-segmenter:prettier",
"//packages/intl:prettier",
"//packages/react-intl:prettier",
"//packages/swc-plugin:prettier",
Expand All @@ -198,6 +200,8 @@ multirun(
"//packages/intl-pluralrules:generated-test-files",
"//packages/intl-relativetimeformat:test262-main",
"//packages/intl-relativetimeformat:generated-test-files",
"//packages/intl-segmenter:test262-main",
"//packages/intl-segmenter:generated-file",
"//packages/intl-getcanonicallocales:aliases",
],
)
Expand All @@ -212,6 +216,7 @@ multirun(
"//packages/intl-numberformat:generated-files",
"//packages/intl-pluralrules:generated-files",
"//packages/intl-relativetimeformat:generated-files",
"//packages/intl-segmenter:generated-file",
],
)

Expand Down
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -83,6 +83,8 @@
"cldr-misc-full": "40",
"cldr-numbers-full": "40",
"cldr-units-full": "40",
"cldr-segments-full": "40",
"regexpu-core": "^5.2.2",
"clsx": "1",
"commander": "8",
"core-js": "^3.6.5",
Expand Down
175 changes: 175 additions & 0 deletions packages/intl-segmenter/BUILD
@@ -0,0 +1,175 @@
load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files")
load("@aspect_rules_js//npm/private:npm_package.bzl", "npm_package")
load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
load("@npm//:defs.bzl", "npm_link_all_packages")
load("@aspect_rules_esbuild//esbuild:defs.bzl", "esbuild")
load("//:index.bzl", "ZONES")
load("@npm//:test262-harness/package_json.bzl", test262_harness_bin = "bin")
load("//tools:index.bzl", "check_format", "generate_src_file", "ts_compile")
load("//tools:jest.bzl", "jest_test")

npm_link_all_packages(name = "node_modules")

PACKAGE_NAME = "intl-segmenter"

npm_package(
name = PACKAGE_NAME,
srcs = [
"LICENSE.md",
"README.md",
"package.json",
":dist",
# polyfill-library uses this
"polyfill.iife.js",
],
package = "@formatjs/%s" % PACKAGE_NAME,
visibility = ["//visibility:public"],
)

SRC_DEPS = [
":node_modules/@formatjs/ecma402-abstract",
":node_modules/@formatjs/intl-localematcher",
]

TEST_DEPS = SRC_DEPS + [
"//:node_modules/@types/node",
"tests/test-utils.ts",
"unicodeFiles/GraphemeBreakTest.txt",
"unicodeFiles/WordBreakTest.txt",
"unicodeFiles/SentenceBreakTest.txt",
]

SRCS = glob(
[
"src/**/*.ts",
"*.ts",
],
exclude = [
#for development only
"debug.ts",
"benchmark.ts",
"tests/**/*",
],
)

TESTS = glob([
"tests/*.test.ts",
])

ts_compile(
name = "dist",
srcs = SRCS,
package = "@formatjs/%s" % PACKAGE_NAME,
skip_esm = False,
deps = SRC_DEPS,
)

jest_test(
name = "unit",
srcs = SRCS + TESTS,
deps = TEST_DEPS,
)

generate_src_file(
name = "test262-main",
src = "test262-main.ts",
entry_point = "scripts/test262-main-gen.ts",
visibility = [
"//:__pkg__",
],
)

# Test262
ts_project(
name = "test262-main-bundle",
srcs = SRCS,
declaration = True,
declaration_map = True,
out_dir = "test262",
resolve_json_module = True,
tsconfig = "//:tsconfig.es6",
deps = SRC_DEPS,
)

esbuild(
name = "test262-polyfill",
entry_point = "test262/test262-main.js",
target = "es6",
deps = [
":test262-main-bundle",
] + SRC_DEPS,
)

test262_harness_bin.test262_harness_test(
name = "test262",
size = "large",
args = [
"--reporter-keys",
"file,attrs,result",
"--errorForFailures",
"--timeout",
"30000",
"--prelude",
"$(rootpath test262-polyfill.js)",
"--test262Dir",
"../com_github_tc39_test262",
"../com_github_tc39_test262/test/intl402/Segmenter/**/*.js",
],
data = [
"test262-polyfill.js",
"@com_github_tc39_test262//:test262-harness-copy",
"@com_github_tc39_test262//:test262-segmenter-copy",
],
# TODO: fix
tags = ["manual"],
)

write_source_files(
name = "generated-files",
files = {"tsconfig.json": "//tools:tsconfig.golden.json"},
visibility = ["//:__pkg__"],
)

esbuild(
name = "polyfill.iife",
entry_point = "lib/polyfill.js",
target = "es6",
# TODO: fix this and set it back to es5
deps = [
":dist-esm",
"//:node_modules/tslib",
] + SRC_DEPS,
)

check_format(
name = "prettier",
srcs = glob(
[
"**/*",
],
exclude = [
"CHANGELOG.md",
"src/*.generated.*",
"unicodeFiles/*.txt",
"test262-main.ts",
],
),
)

# Segmentation UCDs
generate_src_file(
name = "generate-cldr-segmentation-rules",
src = "src/cldr-segmentation-rules.generated.ts",
data = [
"unicodeFiles/DerivedCombiningClass.txt",
"unicodeFiles/DerivedEastAsianWidth.txt",
"unicodeFiles/GraphemeBreakProperty.txt",
"unicodeFiles/IndicSyllabicCategory.txt",
"unicodeFiles/SentenceBreakProperty.txt",
"unicodeFiles/WordBreakProperty.txt",
"//:node_modules/@types/regenerate",
"//:node_modules/cldr-segments-full",
"//:node_modules/regexpu-core",
],
entry_point = "scripts/generate-cldr-segmentation-rules.ts",
)
9 changes: 9 additions & 0 deletions packages/intl-segmenter/LICENSE.md
@@ -0,0 +1,9 @@
MIT License

Copyright (c) 2022 FormatJS

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
99 changes: 99 additions & 0 deletions packages/intl-segmenter/NOTES.md
@@ -0,0 +1,99 @@
# Notes and links

## Unicode Text Segmentation (UAX29)

The official document describing the unicode segmentation
<http://unicode.org/reports/tr29/>

## CLDR locale segmentation rules

Unicode Common Locale Data Repository where locale (and root) segmentation rules are specified

CLDR segmentation rules: <https://github.com/unicode-org/cldr/tree/main/common/segments>

information about the rules: <https://unicode.org/reports/tr35/tr35-general.html#Segmentations>

JSON CLDR: <https://github.com/unicode-org/cldr-json/tree/main/cldr-json/cldr-segments-full> (`und` is the root ruleset)

### CLDR Rules notes

CLDR Rules use unicode flavuored regex (<https://unicode.org/reports/tr18/>) which is not directly compatible with js regex.

Modern JS (es6) implements `/u` flag that allows some unicode regex features: [property escapes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes) `\p{..}`, handling of 4 bytes characters. (`regexpu` can transpile them to es5)
There is also the `/v` flag proposal (<https://github.com/tc39/proposal-regexp-v-flag>) that adds set operations (`--` and `&&`) and nested character groups (both used by the CLDR rules), `regexpu` can transpile those too

CLDR rules use some character classes that are not implemented by `regexpu`, but the code lists are avalible in the UCD files (see [UCD](#UCD) Notes)

More details about syntax incompatiblities in [CLDR Rules RegExes](#CLDRRulesRegex)

## <a name="UCD"></a>UCD

unicode character database: <https://unicode.org/ucd/>

UCD Files: <https://www.unicode.org/Public/15.0.0/ucd/>

Segmentation properties and tests: <https://www.unicode.org/Public/15.0.0/ucd/auxiliary/>

Character sets used by the CLDRs, that are missing from es5 regex and regexpu-core:

- `Grapheme_Cluster_Break` : <https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakProperty.txt> (more information @ <https://unicode.org/reports/tr29/#Grapheme_Cluster_Break_Property_Values>)
- `Sentence_Break`: <https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/SentenceBreakProperty>
- `Word_Break`: <https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/WordBreakProperty.txt>
- `Indic_Syllabic_Category`: <https://unicode.org/Public/UCD/latest/ucd/IndicSyllabicCategory.txt>
- `ea=` => East_Asian_Width => : <https://unicode.org/Public/UCD/latest/ucd/extracted/DerivedEastAsianWidth.txt>
- `ccc` => Canonical_Combining_Class => <https://unicode.org/Public/UCD/latest/ucd/extracted/DerivedCombiningClass.txt>

Properties can use aliases: [PropertyAliases.txt](https://unicode.org/Public/UCD/latest/ucd/PropertyAliases.txt)

Property values can use aliases: [PropertyValueAliases.txt](https://unicode.org/Public/UCD/latest/ucd/PropertyValueAliases.txt)

### Segmentation Tests

<https://unicode.org/reports/tr41/tr41-26.html#Tests29>

About test files: <https://www.unicode.org/reports/tr44/#Segmentation_Test_Files>

Grapheme segmentation tests: <https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakTest.txt>
Sentence segmentation tests: <https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/SentenceBreakTest.txt>
Word break segmentation tests: <https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/WordBreakTest.txt>

## Other segmentation implementations found

- JS: <https://github.com/orling/grapheme-splitter>
- JS Polyfill: <https://www.npmjs.com/package/intl-segmenter-polyfill> (using icu C compiled in wasm+)
- GO: <https://github.com/clipperhouse/uax29>
- Rust: <https://github.com/unicode-rs/unicode-segmentation>

- Java <https://github.com/unicode-org/unicodetools/blob/70dce2c89f185c65b436c28404ae5b7bdb32c2d1/unicodetools/src/main/java/org/unicode/tools/Segmenter.java#L485>
<https://github.com/unicode-org/unicodetools/blob/70dce2c89f185c65b436c28404ae5b7bdb32c2d1/unicodetools/src/test/java/org/unicode/test/CompareBoundaries.java#L473>

## <a name="CLDRRulesRegex"></a>CLDR Rules RegExes

CLDR Rules use unicode regex (unsupported by JS).

There is a set of utils in the unicode repo that allows transformation of unicode regex to java compatible regex:

<https://github.com/unicode-org/unicodetools>

and UnicodeJsps hosted on the unicode.org: <https://util.unicode.org/UnicodeJsps>

### JS Regex utils for unicode

<https://github.com/mathiasbynens/regenerate> can generate es5 regex given a list of unicode symbols
<https://github.com/mathiasbynens/regexpu-core> Can transpile es2015 (`/u`) regex to es5 and `/v` to unicode regex (with [some limitations](https://github.com/mathiasbynens/regexpu-core#caveats))

The unicode regexs have other syntax incompatiblities:

- set operands are single character `-` instead of `--`
- there are spaces in the regex that need to be ignored `[[$Extend-\\\\p{ccc=0}] $ZWJ]`
- Some properties in CLDR do not seem to follow the spec strictly: `\p{Gujr}` should be `\{sc=Gujr}`
- it seems muliple character classes in a row are treated as signle class when performing set operations: `\p{..}\p{...}&[..]` in unicode is treated as `[...]&[...]` but regexpu `\v` treates it as `[...][...]&[...]`
- The way rules are constructed, have issues with negated and normal transpiled character classes `[^(?:...)]` `[(?:..)]`.

Even after accounting for all of those, there are issues left:

`cldr-segments-full/segments/el/suppressions.json` has a rule `"$STerm": "[[$STerm] [\\u003B \\u037E]]"` which I currently do not know how to correctly process.

## isWordLike

<https://github.com/tc39/proposal-intl-segmenter/issues/100>
3 changes: 3 additions & 0 deletions packages/intl-segmenter/README.md
@@ -0,0 +1,3 @@
# `intl-segmenter`

We've migrated the docs to https://formatjs.io/docs/polyfills/intl-segmenter.

0 comments on commit 3020876

Please sign in to comment.