diff --git a/src/js_printer.zig b/src/js_printer.zig index 6f744027cfef15..d61055583a1612 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -1217,7 +1217,7 @@ fn NewPrinter( pub fn printClauseAlias(p: *Printer, alias: string) void { std.debug.assert(alias.len > 0); - if (!strings.containsNonBmpCodePoint(alias)) { + if (!strings.containsNonBmpCodePointOrIsInvalidIdentifier(alias)) { p.printSpaceBeforeIdentifier(); p.printIdentifier(alias); } else { diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 62393cb23fa0da..042abb2d0ceef6 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -7,6 +7,7 @@ const CodePoint = bun.CodePoint; const bun = @import("root").bun; pub const joiner = @import("./string_joiner.zig"); const log = bun.Output.scoped(.STR, true); +const js_lexer = @import("./js_lexer.zig"); pub const Encoding = enum { ascii, @@ -216,7 +217,6 @@ pub fn fmtIdentifier(name: string) FormatValidIdentifier { /// This will always allocate pub const FormatValidIdentifier = struct { name: string, - const js_lexer = @import("./js_lexer.zig"); pub fn format(self: FormatValidIdentifier, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { var iterator = strings.CodepointIterator.init(self.name); var cursor = strings.CodepointIterator.Cursor{}; @@ -4367,6 +4367,24 @@ pub fn containsNonBmpCodePoint(text: string) bool { return false; } +pub fn containsNonBmpCodePointOrIsInvalidIdentifier(text: string) bool { + var iter = CodepointIterator.init(text); + var curs = CodepointIterator.Cursor{}; + + if (!iter.next(&curs)) return true; + + if (curs.c > 0xFFFF or !js_lexer.isIdentifierStart(curs.c)) + return true; + + while (iter.next(&curs)) { + if (curs.c > 0xFFFF or !js_lexer.isIdentifierContinue(curs.c)) { + return true; + } + } + + return false; +} + // this is std.mem.trim except it doesn't forcibly change the slice to be const pub fn trim(slice: anytype, comptime values_to_strip: []const u8) @TypeOf(slice) { var begin: usize = 0; diff --git a/test/regression/issue/07261.test.ts b/test/regression/issue/07261.test.ts new file mode 100644 index 00000000000000..fb092f38e3eada --- /dev/null +++ b/test/regression/issue/07261.test.ts @@ -0,0 +1,23 @@ +import { bunEnv, bunExe } from "harness"; +import { mkdirSync, rmSync, writeFileSync, mkdtempSync } from "fs"; +import { tmpdir } from "os"; +import { join } from "path"; + +it("imports tsconfig.json with abritary keys", async () => { + const testDir = mkdtempSync(join(tmpdir(), "issue7261-")); + + // Clean up from prior runs if necessary + rmSync(testDir, { recursive: true, force: true }); + + // Create a directory with our test tsconfig.json + mkdirSync(testDir, { recursive: true }); + writeFileSync(join(testDir, "tsconfig.json"), '{ "key-with-hyphen": true }'); + + const { exitCode } = Bun.spawnSync({ + cmd: [bunExe(), "-e", `require('${join(testDir, "tsconfig.json")}')`], + env: bunEnv, + stderr: "inherit", + }); + + expect(exitCode).toBe(0); +});