From 32d4f6e67f911132eea54bbb41f6ccc38bc9e49b Mon Sep 17 00:00:00 2001 From: liuxingbaoyu <30521560+liuxingbaoyu@users.noreply.github.com> Date: Thu, 21 Jul 2022 23:56:23 +0800 Subject: [PATCH] feat: Automatically generate `cooked` for `templateElement` (#14757) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nicolò Ribaudo --- .../babel-helper-string-parser/src/index.ts | 1 + packages/babel-types/package.json | 1 + .../scripts/utils/stringifyValidator.js | 5 +- packages/babel-types/src/definitions/core.ts | 57 ++++++++++++++++--- packages/babel-types/src/definitions/utils.ts | 2 +- .../__snapshots__/templateElement.js.snap | 41 +++++++++++++ .../test/builders/es2015/templateElement.js | 16 ++++++ yarn.lock | 1 + 8 files changed, 114 insertions(+), 10 deletions(-) diff --git a/packages/babel-helper-string-parser/src/index.ts b/packages/babel-helper-string-parser/src/index.ts index edb89f7dc2ef..beb7bccf8381 100644 --- a/packages/babel-helper-string-parser/src/index.ts +++ b/packages/babel-helper-string-parser/src/index.ts @@ -65,6 +65,7 @@ export function readStringContents( for (;;) { if (pos >= length) { errors.unterminated(initialPos, initialLineStart, initialCurLine); + out += input.slice(chunkStart, pos); break; } const ch = input.charCodeAt(pos); diff --git a/packages/babel-types/package.json b/packages/babel-types/package.json index 972efa7315ea..d9dadbda21f1 100644 --- a/packages/babel-types/package.json +++ b/packages/babel-types/package.json @@ -24,6 +24,7 @@ } }, "dependencies": { + "@babel/helper-string-parser": "workspace:^", "@babel/helper-validator-identifier": "workspace:^", "to-fast-properties": "condition:BABEL_8_BREAKING ? ^3.0.0 : ^2.0.0" }, diff --git a/packages/babel-types/scripts/utils/stringifyValidator.js b/packages/babel-types/scripts/utils/stringifyValidator.js index 58d9e5ede0a3..a3da470ad7c8 100644 --- a/packages/babel-types/scripts/utils/stringifyValidator.js +++ b/packages/babel-types/scripts/utils/stringifyValidator.js @@ -8,7 +8,10 @@ export default function stringifyValidator(validator, nodePrefix) { } if (validator.chainOf) { - return stringifyValidator(validator.chainOf[1], nodePrefix); + const ret = stringifyValidator(validator.chainOf[1], nodePrefix); + return Array.isArray(ret) && ret.length === 1 && ret[0] === "any" + ? stringifyValidator(validator.chainOf[0], nodePrefix) + : ret; } if (validator.oneOf) { diff --git a/packages/babel-types/src/definitions/core.ts b/packages/babel-types/src/definitions/core.ts index 920078e0cfdc..66213c683c68 100644 --- a/packages/babel-types/src/definitions/core.ts +++ b/packages/babel-types/src/definitions/core.ts @@ -2,6 +2,7 @@ import is from "../validators/is"; import isValidIdentifier from "../validators/isValidIdentifier"; import { isKeyword, isReservedWord } from "@babel/helper-validator-identifier"; import type * as t from ".."; +import { readStringContents } from "@babel/helper-string-parser"; import { BINARY_OPERATORS, @@ -1965,15 +1966,55 @@ defineType("TemplateElement", { builder: ["value", "tail"], fields: { value: { - validate: assertShape({ - raw: { - validate: assertValueType("string"), - }, - cooked: { - validate: assertValueType("string"), - optional: true, + validate: chain( + assertShape({ + raw: { + validate: assertValueType("string"), + }, + cooked: { + validate: assertValueType("string"), + optional: true, + }, + }), + function templateElementCookedValidator(node: t.TemplateElement) { + const raw = node.value.raw; + + let str, + containsInvalid, + unterminatedCalled = false; + try { + const error = () => { + throw new Error(); + }; + ({ str, containsInvalid } = readStringContents( + "template", + raw, + 0, + 0, + 0, + { + unterminated() { + unterminatedCalled = true; + }, + strictNumericEscape: error, + invalidEscapeSequence: error, + numericSeparatorInEscapeSequence: error, + unexpectedNumericSeparator: error, + invalidDigit: error, + invalidCodePoint: error, + }, + )); + } catch { + // TODO: When https://github.com/babel/babel/issues/14775 is fixed + // we can remove the try/catch block. + unterminatedCalled = true; + containsInvalid = true; + } + if (!unterminatedCalled) throw new Error("Invalid raw"); + + node.value.cooked = containsInvalid ? null : str; }, - }), + ), }, tail: { default: false, diff --git a/packages/babel-types/src/definitions/utils.ts b/packages/babel-types/src/definitions/utils.ts index 687b907ef07c..bf2676ba99a2 100644 --- a/packages/babel-types/src/definitions/utils.ts +++ b/packages/babel-types/src/definitions/utils.ts @@ -51,7 +51,7 @@ export type Validator = ( ((node: t.Node, key: string, val: any) => void); export type FieldOptions = { - default?: any; + default?: string | number | boolean | []; optional?: boolean; validate?: Validator; }; diff --git a/packages/babel-types/test/builders/es2015/__snapshots__/templateElement.js.snap b/packages/babel-types/test/builders/es2015/__snapshots__/templateElement.js.snap index 9c78963dc5f5..ae5f92183e03 100644 --- a/packages/babel-types/test/builders/es2015/__snapshots__/templateElement.js.snap +++ b/packages/babel-types/test/builders/es2015/__snapshots__/templateElement.js.snap @@ -16,6 +16,7 @@ Object { "tail": false, "type": "TemplateElement", "value": Object { + "cooked": "foo", "raw": "foo", }, } @@ -36,6 +37,43 @@ exports[`builders es2015 templateElement should validate 5`] = ` Property raw expected type of string but got undefined" `; +exports[`builders es2015 templateElement should validate 6`] = ` +Object { + "tail": false, + "type": "TemplateElement", + "value": Object { + "cooked": null, + "raw": "\\\\u", + }, +} +`; + +exports[`builders es2015 templateElement should validate 7`] = ` +Object { + "tail": false, + "type": "TemplateElement", + "value": Object { + "cooked": "B", + "raw": "\\\\x42", + }, +} +`; + +exports[`builders es2015 templateElement should validate 8`] = ` +Object { + "tail": false, + "type": "TemplateElement", + "value": Object { + "cooked": "B", + "raw": "\\\\x42", + }, +} +`; + +exports[`builders es2015 templateElement should validate 9`] = `"Invalid raw"`; + +exports[`builders es2015 templateElement should validate 10`] = `"Invalid raw"`; + exports[`builders es2015 templateLiteral should validate 1`] = ` Object { "expressions": Array [], @@ -44,6 +82,7 @@ Object { "tail": false, "type": "TemplateElement", "value": Object { + "cooked": "foo", "raw": "foo", }, }, @@ -65,6 +104,7 @@ Object { "tail": false, "type": "TemplateElement", "value": Object { + "cooked": "foo", "raw": "foo", }, }, @@ -72,6 +112,7 @@ Object { "tail": false, "type": "TemplateElement", "value": Object { + "cooked": "bar", "raw": "bar", }, }, diff --git a/packages/babel-types/test/builders/es2015/templateElement.js b/packages/babel-types/test/builders/es2015/templateElement.js index 0ef6d37f7173..418187f98fd8 100644 --- a/packages/babel-types/test/builders/es2015/templateElement.js +++ b/packages/babel-types/test/builders/es2015/templateElement.js @@ -19,6 +19,22 @@ describe("builders", function () { ).toThrowErrorMatchingSnapshot(); expect(() => t.templateElement("foo")).toThrowErrorMatchingSnapshot(); + + expect(t.templateElement({ raw: "\\u" })).toMatchSnapshot(); + + expect(t.templateElement({ raw: "\\x42" })).toMatchSnapshot(); + + expect( + t.templateElement({ raw: "\\x42", cooked: "123" }), + ).toMatchSnapshot(); + + expect(() => + t.templateElement({ raw: "`" }), + ).toThrowErrorMatchingSnapshot(); + + expect(() => + t.templateElement({ raw: "${" }), + ).toThrowErrorMatchingSnapshot(); }); }); describe("templateLiteral", function () { diff --git a/yarn.lock b/yarn.lock index 3e360941a96a..715da6f14bdd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3748,6 +3748,7 @@ __metadata: resolution: "@babel/types@workspace:packages/babel-types" dependencies: "@babel/generator": "workspace:^" + "@babel/helper-string-parser": "workspace:^" "@babel/helper-validator-identifier": "workspace:^" "@babel/parser": "workspace:^" chalk: ^4.1.0