diff --git a/packages/babel-helper-validator-identifier/.npmignore b/packages/babel-helper-validator-identifier/.npmignore index f980694583..2ed0b52967 100644 --- a/packages/babel-helper-validator-identifier/.npmignore +++ b/packages/babel-helper-validator-identifier/.npmignore @@ -1,3 +1,4 @@ +benchmark src test *.log diff --git a/packages/babel-helper-validator-identifier/benchmark/identifier.bench.mjs b/packages/babel-helper-validator-identifier/benchmark/identifier.bench.mjs new file mode 100644 index 0000000000..8a5f4f4af6 --- /dev/null +++ b/packages/babel-helper-validator-identifier/benchmark/identifier.bench.mjs @@ -0,0 +1,3 @@ +import "./isIdentifierChar.bench.mjs"; +import "./isIdentifierStart.bench.mjs"; +import "./isIdentifierName.bench.mjs"; diff --git a/packages/babel-helper-validator-identifier/benchmark/isIdentifierChar.bench.mjs b/packages/babel-helper-validator-identifier/benchmark/isIdentifierChar.bench.mjs new file mode 100644 index 0000000000..27f7ac51ef --- /dev/null +++ b/packages/babel-helper-validator-identifier/benchmark/isIdentifierChar.bench.mjs @@ -0,0 +1,31 @@ +import Benchmark from "benchmark"; +import baseline from "@babel/helper-validator-identifier-baseline"; +import current from "../lib/index.js"; +import { report } from "./util.mjs"; + +const suite = new Benchmark.Suite(); + +function benchCases(implementation, name) { + suite.add(name + "#isIdentifierChar on 4 ASCII characters", () => { + implementation.isIdentifierChar(0x61); + implementation.isIdentifierChar(0x7b); + implementation.isIdentifierChar(0x5f); + implementation.isIdentifierChar(0x24); + }); + + suite.add(name + "#isIdentifierChar on 4 non-ASCII characters", () => { + implementation.isIdentifierChar(0x80); + implementation.isIdentifierChar(0x4e00); + implementation.isIdentifierChar(0xffff); + implementation.isIdentifierChar(0x10000); + }); + + suite.add(name + "#isIdentifierChar on TIP character", () => { + implementation.isIdentifierChar(0x30000); + }); +} + +benchCases(baseline, "baseline"); +benchCases(current, "current"); + +suite.on("cycle", report).run(); diff --git a/packages/babel-helper-validator-identifier/benchmark/isIdentifierName.bench.mjs b/packages/babel-helper-validator-identifier/benchmark/isIdentifierName.bench.mjs new file mode 100644 index 0000000000..31ecfb3d71 --- /dev/null +++ b/packages/babel-helper-validator-identifier/benchmark/isIdentifierName.bench.mjs @@ -0,0 +1,30 @@ +import Benchmark from "benchmark"; +import baseline from "@babel/helper-validator-identifier-baseline"; +import current from "../lib/index.js"; +import { report } from "./util.mjs"; + +const suite = new Benchmark.Suite(); + +function benchCases(implementation, name) { + suite.add(name + "#isIdentifierName on 2 short ASCII words", () => { + implementation.isIdentifierName("aforementioned"); + implementation.isIdentifierName("zap cannon"); + }); + + suite.add(name + "#isIdentifierName on 1 long ASCII words", () => { + implementation.isIdentifierName( + "Pneumonoultramicroscopicsilicovolcanoconiosis" + ); + }); + + suite.add(name + "#isIdentifierName on 3 non-ASCII words", () => { + implementation.isIdentifierName("مذكور أعلاه"); + implementation.isIdentifierName("cañón de zap"); + implementation.isIdentifierName("𠡦𠧋𡆠囝〇𠁈𢘑𤯔𠀑埊"); + }); +} + +benchCases(baseline, "baseline"); +benchCases(current, "current"); + +suite.on("cycle", report).run(); diff --git a/packages/babel-helper-validator-identifier/benchmark/isIdentifierStart.bench.mjs b/packages/babel-helper-validator-identifier/benchmark/isIdentifierStart.bench.mjs new file mode 100644 index 0000000000..f106514cf5 --- /dev/null +++ b/packages/babel-helper-validator-identifier/benchmark/isIdentifierStart.bench.mjs @@ -0,0 +1,31 @@ +import Benchmark from "benchmark"; +import baseline from "@babel/helper-validator-identifier-baseline"; +import current from "../lib/index.js"; +import { report } from "./util.mjs"; + +const suite = new Benchmark.Suite(); + +function benchCases(implementation, name) { + suite.add(name + "#isIdentifierStart on 4 ASCII characters", () => { + implementation.isIdentifierStart(0x61); + implementation.isIdentifierStart(0x7b); + implementation.isIdentifierStart(0x5f); + implementation.isIdentifierStart(0x24); + }); + + suite.add(name + "#isIdentifierStart on 4 non-ASCII characters", () => { + implementation.isIdentifierStart(0x80); + implementation.isIdentifierStart(0x4e00); + implementation.isIdentifierStart(0xffff); + implementation.isIdentifierStart(0x10000); + }); + + suite.add(name + "#isIdentifierStart on TIP character", () => { + implementation.isIdentifierStart(0x30000); + }); +} + +benchCases(baseline, "baseline"); +benchCases(current, "current"); + +suite.on("cycle", report).run(); diff --git a/packages/babel-helper-validator-identifier/benchmark/isKeyword.bench.mjs b/packages/babel-helper-validator-identifier/benchmark/isKeyword.bench.mjs new file mode 100644 index 0000000000..2366ce6917 --- /dev/null +++ b/packages/babel-helper-validator-identifier/benchmark/isKeyword.bench.mjs @@ -0,0 +1,27 @@ +import Benchmark from "benchmark"; +import baseline from "@babel/helper-validator-identifier-baseline"; +import current from "../lib/index.js"; +import { report } from "./util.mjs"; + +const suite = new Benchmark.Suite(); + +function benchCases(implementation, name) { + suite.add(name + "#isKeyword on 4 keywords", () => { + implementation.isKeyword("debugger"); + implementation.isKeyword("throw"); + implementation.isKeyword("extends"); + implementation.isKeyword("instanceof"); + }); + + suite.add(name + "#isKeyword on 4 non-keywords", () => { + implementation.isKeyword("debuggerr"); + implementation.isKeyword("threw"); + implementation.isKeyword("extend"); + implementation.isKeyword("instanceOf"); + }); +} + +benchCases(baseline, "baseline"); +benchCases(current, "current"); + +suite.on("cycle", report).run(); diff --git a/packages/babel-helper-validator-identifier/benchmark/isStrictBindReservedWord.bench.mjs b/packages/babel-helper-validator-identifier/benchmark/isStrictBindReservedWord.bench.mjs new file mode 100644 index 0000000000..713e926d1e --- /dev/null +++ b/packages/babel-helper-validator-identifier/benchmark/isStrictBindReservedWord.bench.mjs @@ -0,0 +1,27 @@ +import Benchmark from "benchmark"; +import baseline from "@babel/helper-validator-identifier-baseline"; +import current from "../lib/index.js"; +import { report } from "./util.mjs"; + +const suite = new Benchmark.Suite(); + +function benchCases(implementation, name) { + suite.add(name + "#isStrictBindReservedWord on 4 keywords", () => { + implementation.isStrictBindReservedWord("arguments"); + implementation.isStrictBindReservedWord("eval"); + implementation.isStrictBindReservedWord("implements"); + implementation.isStrictBindReservedWord("instanceof"); + }); + + suite.add(name + "#isStrictBindReservedWord on 4 non-keywords", () => { + implementation.isStrictBindReservedWord("argumentss"); + implementation.isStrictBindReservedWord("evals"); + implementation.isStrictBindReservedWord("implement"); + implementation.isStrictBindReservedWord("instanceOf"); + }); +} + +benchCases(baseline, "baseline"); +benchCases(current, "current"); + +suite.on("cycle", report).run({ async: false }); diff --git a/packages/babel-helper-validator-identifier/benchmark/util.mjs b/packages/babel-helper-validator-identifier/benchmark/util.mjs new file mode 100644 index 0000000000..f88a43b54c --- /dev/null +++ b/packages/babel-helper-validator-identifier/benchmark/util.mjs @@ -0,0 +1,13 @@ +export function report(event) { + const bench = event.target; + const factor = bench.hz < 100 ? 100 : 1; + const timeMs = bench.stats.mean * 1000; + const time = + timeMs < 10 + ? `${Math.round(timeMs * 1000) / 1000}ms` + : `${Math.round(timeMs)}ms`; + const msg = `${bench.name}: ${ + Math.round(bench.hz * factor) / factor + } ops/sec ±${Math.round(bench.stats.rme * 100) / 100}% (${time})`; + console.log(msg); +} diff --git a/packages/babel-helper-validator-identifier/package.json b/packages/babel-helper-validator-identifier/package.json index a76a8fac11..d1c88c746a 100644 --- a/packages/babel-helper-validator-identifier/package.json +++ b/packages/babel-helper-validator-identifier/package.json @@ -14,7 +14,9 @@ "main": "./lib/index.js", "exports": "./lib/index.js", "devDependencies": { - "charcodes": "^0.2.0", - "unicode-13.0.0": "^0.8.0" + "@babel/helper-validator-identifier-baseline": "npm:@babel/helper-validator-identifier@7.10.4", + "@unicode/unicode-13.0.0": "^1.0.6", + "benchmark": "^2.1.4", + "charcodes": "^0.2.0" } } diff --git a/packages/babel-helper-validator-identifier/scripts/generate-identifier-regex.js b/packages/babel-helper-validator-identifier/scripts/generate-identifier-regex.js index 70b371508b..45276d51b2 100644 --- a/packages/babel-helper-validator-identifier/scripts/generate-identifier-regex.js +++ b/packages/babel-helper-validator-identifier/scripts/generate-identifier-regex.js @@ -4,14 +4,14 @@ // https://tc39.github.io/ecma262/#sec-conformance const version = "13.0.0"; -const start = require("unicode-" + +const start = require("@unicode/unicode-" + version + "/Binary_Property/ID_Start/code-points.js").filter(function (ch) { return ch > 0x7f; }); let last = -1; const cont = [0x200c, 0x200d].concat( - require("unicode-" + + require("@unicode/unicode-" + version + "/Binary_Property/ID_Continue/code-points.js").filter(function (ch) { return ch > 0x7f && search(start, ch, last + 1) == -1; diff --git a/packages/babel-helper-validator-identifier/src/identifier.ts b/packages/babel-helper-validator-identifier/src/identifier.ts index 0a25c7a9ab..4217cf458f 100644 --- a/packages/babel-helper-validator-identifier/src/identifier.ts +++ b/packages/babel-helper-validator-identifier/src/identifier.ts @@ -85,13 +85,23 @@ export function isIdentifierChar(code: number): boolean { export function isIdentifierName(name: string): boolean { let isFirst = true; - for (const char of Array.from(name)) { - const cp = char.codePointAt(0); + for (let i = 0; i < name.length; i++) { + // The implementation is based on + // https://source.chromium.org/chromium/chromium/src/+/master:v8/src/builtins/builtins-string-gen.cc;l=1455;drc=221e331b49dfefadbc6fa40b0c68e6f97606d0b3;bpv=0;bpt=1 + // We reimplement `codePointAt` because `codePointAt` is a V8 builtin which is not inlined by TurboFan (as of M91) + // since `name` is mostly ASCII, an inlined `charCodeAt` wins here + let cp = name.charCodeAt(i); + if ((cp & 0xfc00) === 0xd800 && i + 1 < name.length) { + const trail = name.charCodeAt(++i); + if ((trail & 0xfc00) === 0xdc00) { + cp = 0x10000 + ((cp & 0x3ff) << 10) + (trail & 0x3ff); + } + } if (isFirst) { + isFirst = false; if (!isIdentifierStart(cp)) { return false; } - isFirst = false; } else if (!isIdentifierChar(cp)) { return false; } diff --git a/packages/babel-helper-validator-identifier/test/identifier.spec.js b/packages/babel-helper-validator-identifier/test/identifier.spec.js index e06a3ac9f1..52b15b7183 100644 --- a/packages/babel-helper-validator-identifier/test/identifier.spec.js +++ b/packages/babel-helper-validator-identifier/test/identifier.spec.js @@ -4,13 +4,23 @@ describe("isIdentifierName", function () { it("returns false if provided string is empty", function () { expect(isIdentifierName("")).toBe(false); }); - it.each(["hello", "$", "ゆゆ式", "$20", "hello20", "_", "if"])( + it.each([ + "hello", + "$", + "ゆゆ式", + "$20", + "hello20", + "_", + "if", + "_\u200c", + "_\u200d", + ])( "returns true if provided string %p is an IdentifierName", function (word) { expect(isIdentifierName(word)).toBe(true); }, ); - it.each(["+hello", "0$", "-ゆゆ式", "#_", "_#"])( + it.each(["+hello", "0$", "-ゆゆ式", "#_", "_#", "\ud800\ud800"])( "returns false if provided string %p is not an IdentifierName", function (word) { expect(isIdentifierName(word)).toBe(false); @@ -19,4 +29,13 @@ describe("isIdentifierName", function () { it("supports astral symbols", function () { expect(isIdentifierName("x\uDB40\uDDD5")).toBe(true); }); + it("supports Unicode 13", () => { + expect(isIdentifierName("\u{30000}")).toBe(true); + }); + it("supports Unicode 12", () => { + expect(isIdentifierName("\u{10fe0}")).toBe(true); + }); + it("supports Unicode 11", () => { + expect(isIdentifierName("\u{10f00}")).toBe(true); + }); }); diff --git a/yarn.lock b/yarn.lock index 084a057b82..967e18dacb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -805,6 +805,13 @@ __metadata: languageName: unknown linkType: soft +"@babel/helper-validator-identifier-baseline@npm:@babel/helper-validator-identifier@7.10.4": + version: 7.10.4 + resolution: "@babel/helper-validator-identifier@npm:7.10.4" + checksum: 25098ef842e3ffecdd9a7216f6173da7ad7be1b0b3e454a9f6965055154b9ad7a4acd2f218ba3d2efc0821bdab97837b3cb815844af7d72f66f89d446a54efc6 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.12.11": version: 7.12.11 resolution: "@babel/helper-validator-identifier@npm:7.12.11" @@ -816,8 +823,10 @@ __metadata: version: 0.0.0-use.local resolution: "@babel/helper-validator-identifier@workspace:packages/babel-helper-validator-identifier" dependencies: + "@babel/helper-validator-identifier-baseline": "npm:@babel/helper-validator-identifier@7.10.4" + "@unicode/unicode-13.0.0": ^1.0.6 + benchmark: ^2.1.4 charcodes: ^0.2.0 - unicode-13.0.0: ^0.8.0 languageName: unknown linkType: soft @@ -4325,6 +4334,13 @@ __metadata: languageName: node linkType: hard +"@unicode/unicode-13.0.0@npm:^1.0.6": + version: 1.0.6 + resolution: "@unicode/unicode-13.0.0@npm:1.0.6" + checksum: 8971f6f8d302b4cb7e95db6225f4482301b21981c95062209851f1ebc93c392bbf5861029a8f761a857193fb10453cd4043ae376d15a52aed46fd1ada56825d9 + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.11.0": version: 1.11.0 resolution: "@webassemblyjs/ast@npm:1.11.0" @@ -5623,6 +5639,16 @@ __metadata: languageName: node linkType: hard +"benchmark@npm:^2.1.4": + version: 2.1.4 + resolution: "benchmark@npm:2.1.4" + dependencies: + lodash: ^4.17.4 + platform: ^1.3.3 + checksum: 3d3d8f4771b7f9b17f1a967b8f5e70319930fcec2691b35418062342bfbbd1a3221b3129aaf5938fc6a85703837f8cdf2f6875349c158c5aa574b6eb36ab6baa + languageName: node + linkType: hard + "big.js@npm:^5.2.2": version: 5.2.2 resolution: "big.js@npm:5.2.2" @@ -10838,10 +10864,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20": - version: 4.17.20 - resolution: "lodash@npm:4.17.20" - checksum: c62101d2500c383b5f174a7e9e6fe8098149ddd6e9ccfa85f36d4789446195f5c4afd3cfba433026bcaf3da271256566b04a2bf2618e5a39f6e67f8c12030cb6 +"lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.4": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: 4983720b9abca930a4a46f18db163d7dad8dd00dbed6db0cc7b499b33b717cce69f80928b27bbb1ff2cbd3b19d251ee90669a8b5ea466072ca81c2ebe91e7468 languageName: node linkType: hard @@ -12228,6 +12254,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"platform@npm:^1.3.3": + version: 1.3.6 + resolution: "platform@npm:1.3.6" + checksum: d4d10d5a55476c6d369b03e02b31df50a4e7f1c565efabe707379b8a119709fb2a66dec090ab7fe520a30b767fe3791e3c4a5aba985918e51a17df45e469189f + languageName: node + linkType: hard + "please-upgrade-node@npm:^3.1.1, please-upgrade-node@npm:^3.2.0": version: 3.2.0 resolution: "please-upgrade-node@npm:3.2.0" @@ -12673,9 +12706,9 @@ fsevents@^1.2.7: linkType: hard "regenerate@npm:^1.4.0": - version: 1.4.0 - resolution: "regenerate@npm:1.4.0" - checksum: d797b035730c0b5cbb7c230220b6a34610f84c1ea2369f0025292613c1ec88068cd87819fccf9c08f002670f26d59e63bbc309358181a6186f7fda185e93618a + version: 1.4.2 + resolution: "regenerate@npm:1.4.2" + checksum: 54275a99effd8a439bcdd88942b61f68a769133df841e90d94df9ae7c250cb6537c0a28dd913116539772b3415edbcb3c8d81c22275595d3755cf0353976dfa4 languageName: node linkType: hard @@ -14644,13 +14677,6 @@ typescript@~4.2.3: languageName: node linkType: hard -"unicode-13.0.0@npm:^0.8.0": - version: 0.8.0 - resolution: "unicode-13.0.0@npm:0.8.0" - checksum: 5a1b05faae6d92b408ba7dadc05ce5eaf6e9695257487b513ec5b67117b717e98c120f768efafb9da7e9d4a880e8f06ad4f1f8ccdf553c071875007a4cfca7b5 - languageName: node - linkType: hard - "unicode-canonical-property-names-ecmascript@npm:^1.0.4": version: 1.0.4 resolution: "unicode-canonical-property-names-ecmascript@npm:1.0.4"