Skip to content

Commit

Permalink
feat(turbopack): support analysing string concatenation (vercel/turbo…
Browse files Browse the repository at this point in the history
…repo#8823)

### Description

Adds support for `String.prototype.concat` which `dayjs` uses in an
`import()` pattern.

### Testing Instructions

Closes PACK-3118
Fixes #66826
  • Loading branch information
ForsakenHarmony authored Jul 24, 2024
1 parent 7df731e commit 8107374
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 3 deletions.
15 changes: 15 additions & 0 deletions crates/turbopack-ecmascript/src/analyzer/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,21 @@ pub fn replace_builtin(value: &mut JsValue) -> bool {
}
_ => {}
}

// matching calls on strings like `"dayjs/locale/".concat(userLocale, ".js")`
if obj.is_string() == Some(true) {
if let Some(str) = prop.as_str() {
// The String.prototype.concat method
if str == "concat" {
let mut values = vec![take(obj)];
values.extend(take(args));

*value = JsValue::concat(values);
return true;
}
}
}

// without special handling, we convert it into a normal call like
// `(obj.prop)(arg1, arg2, ...)`
*value = JsValue::call(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import * as a from "./dir/a.js";
import * as b from "./dir/b.ts";

const requireTemplate = (key) => require(`./dir/${key}`);
const requireConcat = (key) => require("./dir/" + key);
const requireAdd = (key) => require("./dir/" + key);
const requireConcat = (key) => require("./dir/".concat(key));
const importTemplate = (key) => import(`./dir/${key}`);
const importTemplateSuffix = (key) => import(`./dir/${key}.js`);
const importConcat = (key) => import("./dir/" + key);
const importConcatSuffix = (key) => import("./dir/" + key + ".js");
const importAdd = (key) => import("./dir/" + key);
const importAddSuffix = (key) => import("./dir/" + key + ".js");
const importConcat = (key) => import("./dir/".concat(key));
const importConcatSuffix = (key) => import("./dir/".concat(key, ".js"));

it("should support dynamic requests in require with template literals", () => {
expect(requireTemplate("a.js")).toBe(a);
Expand All @@ -15,6 +18,13 @@ it("should support dynamic requests in require with template literals", () => {
expect(requireTemplate("d.js")).toBe("d");
});

it("should support dynamic requests in require with addition", () => {
expect(requireAdd("a.js")).toBe(a);
expect(requireAdd("b.ts")).toBe(b);
expect(requireAdd("c.module.css")).toHaveProperty("class");
expect(requireAdd("d.js")).toBe("d");
});

it("should support dynamic requests in require with concatenation", () => {
expect(requireConcat("a.js")).toBe(a);
expect(requireConcat("b.ts")).toBe(b);
Expand All @@ -37,39 +47,59 @@ it("should support dynamic requests in import with template literals and suffix"
);
});

it("should support dynamic requests in import with addition", async () => {
await expect(importAdd("a.js")).resolves.toBe(a);
await expect(importAdd("b.ts")).resolves.toBe(b);
await expect(importAdd("c.module.css")).resolves.toHaveProperty("class");
await expect(importAdd("d.js")).resolves.toHaveProperty("default", "d");
});

it("should support dynamic requests in import with concatenation", async () => {
await expect(importConcat("a.js")).resolves.toBe(a);
await expect(importConcat("b.ts")).resolves.toBe(b);
await expect(importConcat("c.module.css")).resolves.toHaveProperty("class");
await expect(importConcat("d.js")).resolves.toHaveProperty("default", "d");
});

it("should support dynamic requests in import with addition and suffix", async () => {
await expect(importAddSuffix("a")).resolves.toBe(a);
await expect(importAddSuffix("d")).resolves.toHaveProperty("default", "d");
});

it("should support dynamic requests in import with concatenation and suffix", async () => {
await expect(importConcatSuffix("a")).resolves.toBe(a);
await expect(importConcatSuffix("d")).resolves.toHaveProperty("default", "d");
});

it("should throw an error when requesting a non-existent file", async () => {
expect(() => requireTemplate("e.js")).toThrowError();
expect(() => requireAdd("e.js")).toThrowError();
expect(() => requireConcat("e.js")).toThrowError();
await expect(importTemplate("e.js")).rejects.toThrowError();
await expect(importAdd("e.js")).rejects.toThrowError();
await expect(importConcat("e.js")).rejects.toThrowError();
});

it("should support dynamic requests without the extension", async () => {
expect(requireTemplate("a")).toBe(a);
expect(requireAdd("a")).toBe(a);
expect(requireConcat("a")).toBe(a);
expect(requireTemplate("d")).toBe("d");
expect(requireAdd("d")).toBe("d");
expect(requireConcat("d")).toBe("d");
await expect(importTemplate("a")).resolves.toBe(a);
await expect(importTemplate("d")).resolves.toHaveProperty("default", "d");
await expect(importAdd("a")).resolves.toBe(a);
await expect(importAdd("d")).resolves.toHaveProperty("default", "d");
await expect(importConcat("a")).resolves.toBe(a);
await expect(importConcat("d")).resolves.toHaveProperty("default", "d");
});

it("should not support dynamic requests with double extension", async () => {
await expect(importTemplateSuffix("a.js")).rejects.toThrowError();
await expect(importTemplateSuffix("d.js")).rejects.toThrowError();
await expect(importAddSuffix("a.js")).rejects.toThrowError();
await expect(importAddSuffix("d.js")).rejects.toThrowError();
await expect(importConcatSuffix("a.js")).rejects.toThrowError();
await expect(importConcatSuffix("d.js")).rejects.toThrowError();
});

0 comments on commit 8107374

Please sign in to comment.