Build: transform String.raw (#18134)
This commit is contained in:
1 parent
eca8348812
commit
b99012b48c
10 files changed
+147
-45
No files matched your search
@@ -128,6 +128,7 @@
|
||||
"@eslint/js": "9.38.0",
|
||||
"@stylistic/eslint-plugin": "5.5.0",
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/jsesc": "3.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.2",
|
||||
"@typescript-eslint/parser": "8.46.2",
|
||||
"browserslist": "4.27.0",
|
||||
@@ -156,6 +157,7 @@
|
||||
"jest-snapshot-serializer-ansi": "2.2.1",
|
||||
"jest-snapshot-serializer-raw": "2.0.0",
|
||||
"jest-watch-typeahead": "3.0.1",
|
||||
"jsesc": "3.1.0",
|
||||
"knip": "5.66.2",
|
||||
"magic-string": "0.30.19",
|
||||
"nano-spawn": "2.0.0",
|
||||
|
||||
@@ -56,13 +56,16 @@ function transform(original, file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
transform.transform(node);
|
||||
changed ||= true;
|
||||
|
||||
if (transform.inject) {
|
||||
injected.add(transform.inject);
|
||||
}
|
||||
|
||||
changed ||= true;
|
||||
const replacement = transform.transform(node);
|
||||
if (replacement && replacement !== node) {
|
||||
replaceNode(node, replacement);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -95,4 +98,12 @@ function transform(original, file) {
|
||||
return code;
|
||||
}
|
||||
|
||||
function replaceNode(original, object) {
|
||||
for (const key of Object.keys(original)) {
|
||||
delete original[key];
|
||||
}
|
||||
|
||||
Object.assign(original, object);
|
||||
}
|
||||
|
||||
export default transform;
|
||||
@@ -1,4 +1,5 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createIdentifier, isIdentifier } from "./utilities.js";
|
||||
|
||||
/**
|
||||
* @param {import("@babel/types").Node} node
|
||||
@@ -15,8 +16,7 @@ function isMethodCall(node, { methodName, argumentsLength }) {
|
||||
node.callee.type === "OptionalMemberExpression") &&
|
||||
!node.callee.computed &&
|
||||
node.callee.object.type !== "ThisExpression" &&
|
||||
node.callee.property.type === "Identifier" &&
|
||||
node.callee.property.name === methodName
|
||||
isIdentifier(node.callee.property, methodName)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,16 +29,22 @@ function isMethodCall(node, { methodName, argumentsLength }) {
|
||||
*/
|
||||
function transformMethodCallToFunctionCall(node, functionName) {
|
||||
// `__at(isOptionalObject, object, ...arguments)`
|
||||
node.arguments.unshift(
|
||||
{
|
||||
type: "BooleanLiteral",
|
||||
value: node.callee.type === "OptionalMemberExpression",
|
||||
leadingComments: [{ type: "CommentBlock", value: " isOptionalObject " }],
|
||||
},
|
||||
node.callee.object,
|
||||
);
|
||||
|
||||
node.callee = { type: "Identifier", name: functionName };
|
||||
return {
|
||||
...node,
|
||||
callee: createIdentifier(functionName),
|
||||
arguments: [
|
||||
{
|
||||
type: "BooleanLiteral",
|
||||
value: node.callee.type === "OptionalMemberExpression",
|
||||
leadingComments: [
|
||||
{ type: "CommentBlock", value: " isOptionalObject " },
|
||||
],
|
||||
},
|
||||
node.callee.object,
|
||||
...node.arguments,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function createMethodCallTransform({
|
||||
|
||||
@@ -3,6 +3,7 @@ import transformArrayFindLastIndex from "./transform-array-find-last-index.js";
|
||||
import transformArrayToReversed from "./transform-array-to-reversed.js";
|
||||
import transformObjectHasOwnCall from "./transform-object-has-own.js";
|
||||
import transformRelativeIndexing from "./transform-relative-indexing.js";
|
||||
import transformStringRaw from "./transform-string-raw.js";
|
||||
import transformStringReplaceAll from "./transform-string-replace-all.js";
|
||||
|
||||
// These transforms are like Babel and core-js
|
||||
@@ -15,10 +16,12 @@ export default [
|
||||
transformArrayFindLastIndex,
|
||||
// Node.js 16.9.0
|
||||
transformObjectHasOwnCall,
|
||||
// Node.js 16.6.0
|
||||
// Node.js 16.6.0, and for performance
|
||||
transformRelativeIndexing,
|
||||
// Node.js 15.0.0
|
||||
transformStringReplaceAll,
|
||||
// Node.js 20
|
||||
transformArrayToReversed,
|
||||
// For performance
|
||||
transformStringRaw,
|
||||
];
|
||||
@@ -1,3 +1,5 @@
|
||||
import { createMemberExpression, isIdentifier } from "./utilities.js";
|
||||
|
||||
/*
|
||||
`Object.hasOwn(foo, "bar")` -> `Object.prototype.hasOwnProperty.call(foo, "bar")`
|
||||
*/
|
||||
@@ -8,40 +10,24 @@ const transformObjectHasOwnCall = {
|
||||
* @param {import("@babel/types").Node} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
test(node) {
|
||||
return (
|
||||
node.type === "CallExpression" &&
|
||||
!node.optional &&
|
||||
node.arguments.length === 2 &&
|
||||
node.arguments.every(({ type }) => type !== "SpreadElement") &&
|
||||
node.callee.type === "MemberExpression" &&
|
||||
!node.callee.optional &&
|
||||
!node.callee.computed &&
|
||||
node.callee.object.type === "Identifier" &&
|
||||
node.callee.object.name === "Object" &&
|
||||
node.callee.property.type === "Identifier" &&
|
||||
node.callee.property.name === "hasOwn"
|
||||
);
|
||||
},
|
||||
test: (node) =>
|
||||
node.type === "CallExpression" &&
|
||||
!node.optional &&
|
||||
node.arguments.length === 2 &&
|
||||
node.arguments.every(({ type }) => type !== "SpreadElement") &&
|
||||
node.callee.type === "MemberExpression" &&
|
||||
!node.callee.optional &&
|
||||
!node.callee.computed &&
|
||||
isIdentifier(node.callee.object, "Object") &&
|
||||
isIdentifier(node.callee.property, "hasOwn"),
|
||||
/**
|
||||
* @param {import("@babel/types").CallExpression} node
|
||||
* @returns {import("@babel/types").CallExpression}
|
||||
*/
|
||||
transform(node) {
|
||||
node.callee = {
|
||||
type: "MemberExpression",
|
||||
object: {
|
||||
type: "MemberExpression",
|
||||
object: {
|
||||
type: "MemberExpression",
|
||||
object: { type: "Identifier", name: "Object" },
|
||||
property: { type: "Identifier", name: "prototype" },
|
||||
},
|
||||
property: { type: "Identifier", name: "hasOwnProperty" },
|
||||
},
|
||||
property: { type: "Identifier", name: "call" },
|
||||
};
|
||||
},
|
||||
transform: (node) => ({
|
||||
...node,
|
||||
callee: createMemberExpression("Object.prototype.hasOwnProperty.call"),
|
||||
}),
|
||||
};
|
||||
|
||||
export default transformObjectHasOwnCall;
|
||||
@@ -0,0 +1,47 @@
|
||||
import jsesc from "jsesc";
|
||||
import { isIdentifier } from "./utilities.js";
|
||||
|
||||
/**
|
||||
* @param {import("@babel/types").Node} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isStringRaw(node) {
|
||||
return (
|
||||
node.type === "TaggedTemplateExpression" &&
|
||||
node.tag.type === "MemberExpression" &&
|
||||
!node.tag.computed &&
|
||||
!node.tag.optional &&
|
||||
isIdentifier(node.tag.object, "String") &&
|
||||
isIdentifier(node.tag.property, "raw")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* `` String.raw`foo` `` -> `"foo"`
|
||||
* `` String.raw`foo ${"bar"}` `` -> `` `foo ${"bar"}` ``
|
||||
*
|
||||
* @param {import("@babel/types").TaggedTemplateExpression} node
|
||||
* @returns {import("@babel/types").TaggedTemplateExpression}
|
||||
*/
|
||||
function transformStringRaw(taggedTemplateExpression) {
|
||||
const templateLiteral = taggedTemplateExpression.quasi;
|
||||
|
||||
if (templateLiteral.expressions.length === 0) {
|
||||
const { raw } = templateLiteral.quasis[0].value;
|
||||
const value = new Function(`return \`${jsesc(raw)}\``)();
|
||||
return { type: "StringLiteral", value };
|
||||
}
|
||||
|
||||
const quasis = templateLiteral.quasis.map((quasis) => {
|
||||
const { raw } = quasis.value;
|
||||
return { ...quasis, value: { ...quasis, raw: jsesc(raw) } };
|
||||
});
|
||||
|
||||
return { ...templateLiteral, quasis };
|
||||
}
|
||||
|
||||
export default {
|
||||
shouldSkip: (text /* , file*/) => !text.includes("String.raw`"),
|
||||
test: isStringRaw,
|
||||
transform: transformStringRaw,
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
function isIdentifier(node, name) {
|
||||
return node.type === "Identifier" && node.name === name;
|
||||
}
|
||||
|
||||
function createIdentifier(name) {
|
||||
return { type: "Identifier", name };
|
||||
}
|
||||
|
||||
function createMemberExpression(path) {
|
||||
let node;
|
||||
|
||||
for (const name of path.split(".")) {
|
||||
node = node
|
||||
? {
|
||||
type: "MemberExpression",
|
||||
object: node,
|
||||
property: createIdentifier(name),
|
||||
}
|
||||
: createIdentifier(name);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
export { createIdentifier, createMemberExpression, isIdentifier };
|
||||
@@ -20,6 +20,10 @@ const restrictedSyntaxes = [
|
||||
selector: String.raw`CallExpression[callee.name="require"][arguments.0.value=/^\..*?\.mjs$/]`,
|
||||
message: ".mjs file can't be `require()`d",
|
||||
},
|
||||
{
|
||||
selector: String.raw`TaggedTemplateExpression > MemberExpression.tag[object.name="String"][property.name="raw"]`,
|
||||
message: "`String.raw` should be transformed.",
|
||||
},
|
||||
];
|
||||
|
||||
const browserRestrictedSyntaxes = [
|
||||
|
||||
@@ -109,3 +109,12 @@ test("Array#toReversed", () => {
|
||||
`"foo.toReversed(extraArgument)"`,
|
||||
);
|
||||
});
|
||||
|
||||
test("String.raw", () => {
|
||||
expect(transform("String.raw`\\\\\\uINVALID`")).toMatchInlineSnapshot(
|
||||
String.raw`""\\\\\\uINVALID""`,
|
||||
);
|
||||
expect(transform("String.raw`\\uINVALID${'world'}`")).toMatchInlineSnapshot(
|
||||
`" \`\\\\uINVALID\${'world'}\`"`,
|
||||
);
|
||||
});
|
||||
@@ -2433,6 +2433,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/jsesc@npm:3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "@types/jsesc@npm:3.0.3"
|
||||
checksum: 10/8e97013e91fdbffc7ce23f5be7f0b8a1208e9794ccae887f1a9fd606633d8b570424ab2de81ada47ff9a062812191a7aa764a42a293a37b86fd76f13329a0c0f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/jsesc@npm:^2.5.0":
|
||||
version: 2.5.1
|
||||
resolution: "@types/jsesc@npm:2.5.1"
|
||||
@@ -6111,7 +6118,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsesc@npm:^3.0.2, jsesc@npm:^3.1.0, jsesc@npm:~3.1.0":
|
||||
"jsesc@npm:3.1.0, jsesc@npm:^3.0.2, jsesc@npm:^3.1.0, jsesc@npm:~3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "jsesc@npm:3.1.0"
|
||||
bin:
|
||||
@@ -7256,6 +7263,7 @@ __metadata:
|
||||
"@prettier/parse-srcset": "npm:3.1.0"
|
||||
"@stylistic/eslint-plugin": "npm:5.5.0"
|
||||
"@types/estree": "npm:1.0.8"
|
||||
"@types/jsesc": "npm:3.0.3"
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.46.2"
|
||||
"@typescript-eslint/parser": "npm:8.46.2"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.46.2"
|
||||
@@ -7318,6 +7326,7 @@ __metadata:
|
||||
jest-snapshot-serializer-ansi: "npm:2.2.1"
|
||||
jest-snapshot-serializer-raw: "npm:2.0.0"
|
||||
jest-watch-typeahead: "npm:3.0.1"
|
||||
jsesc: "npm:3.1.0"
|
||||
json5: "npm:2.2.3"
|
||||
knip: "npm:5.66.2"
|
||||
leven: "npm:4.1.0"
|
||||
|
||||
Reference in new issue
Block a user