Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(lexer): do not treat '#bun' in a url as a pragma #15888

Merged
merged 7 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 2 additions & 21 deletions src/js_lexer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,6 @@ fn NewLexer_(
code_point: CodePoint = -1,
identifier: []const u8 = "",
jsx_pragma: JSXPragma = .{},
bun_pragma: enum {
none,
bun,
bun_cjs,
bytecode,
bytecode_cjs,
} = .none,
source_mapping_url: ?js_ast.Span = null,
number: f64 = 0.0,
rescan_close_brace_as_template_token: bool = false,
Expand Down Expand Up @@ -1957,9 +1950,7 @@ fn NewLexer_(
// }
}

if (lexer.bun_pragma == .none and strings.hasPrefixWithWordBoundary(chunk, "bun")) {
lexer.bun_pragma = .bun;
} else if (strings.hasPrefixWithWordBoundary(chunk, "jsx")) {
if (strings.hasPrefixWithWordBoundary(chunk, "jsx")) {
if (PragmaArg.scan(.skip_space_first, lexer.start + i + 1, "jsx", chunk)) |span| {
lexer.jsx_pragma._jsx = span;
}
Expand All @@ -1979,10 +1970,6 @@ fn NewLexer_(
if (PragmaArg.scan(.no_space_first, lexer.start + i + 1, " sourceMappingURL=", chunk)) |span| {
lexer.source_mapping_url = span;
}
} else if ((lexer.bun_pragma == .bun or lexer.bun_pragma == .bun_cjs) and strings.hasPrefixWithWordBoundary(chunk, "bytecode")) {
lexer.bun_pragma = if (lexer.bun_pragma == .bun) .bytecode else .bytecode_cjs;
} else if ((lexer.bun_pragma == .bytecode or lexer.bun_pragma == .bun) and strings.hasPrefixWithWordBoundary(chunk, "bun-cjs")) {
lexer.bun_pragma = if (lexer.bun_pragma == .bytecode) .bytecode_cjs else .bun_cjs;
}
},
else => {},
Expand Down Expand Up @@ -2012,9 +1999,7 @@ fn NewLexer_(
}
}

if (lexer.bun_pragma == .none and strings.hasPrefixWithWordBoundary(chunk, "bun")) {
lexer.bun_pragma = .bun;
} else if (strings.hasPrefixWithWordBoundary(chunk, "jsx")) {
if (strings.hasPrefixWithWordBoundary(chunk, "jsx")) {
if (PragmaArg.scan(.skip_space_first, lexer.start + i + 1, "jsx", chunk)) |span| {
lexer.jsx_pragma._jsx = span;
}
Expand All @@ -2034,10 +2019,6 @@ fn NewLexer_(
if (PragmaArg.scan(.no_space_first, lexer.start + i + 1, " sourceMappingURL=", chunk)) |span| {
lexer.source_mapping_url = span;
}
} else if ((lexer.bun_pragma == .bun or lexer.bun_pragma == .bun_cjs) and strings.hasPrefixWithWordBoundary(chunk, "bytecode")) {
lexer.bun_pragma = if (lexer.bun_pragma == .bun) .bytecode else .bytecode_cjs;
} else if ((lexer.bun_pragma == .bytecode or lexer.bun_pragma == .bun) and strings.hasPrefixWithWordBoundary(chunk, "bun-cjs")) {
lexer.bun_pragma = if (lexer.bun_pragma == .bytecode) .bytecode_cjs else .bun_cjs;
}
},
else => {},
Expand Down
57 changes: 46 additions & 11 deletions src/js_parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/// ** you must also increment the `expected_version` in RuntimeTranspilerCache.zig **
/// ** IMPORTANT **
pub const std = @import("std");
const bun = @import("root").bun;
pub const logger = bun.logger;
pub const js_lexer = bun.js_lexer;
pub const importRecord = @import("./import_record.zig");
Expand All @@ -15,7 +16,6 @@ pub const RuntimeImports = _runtime.Runtime.Imports;
pub const RuntimeFeatures = _runtime.Runtime.Features;
pub const RuntimeNames = _runtime.Runtime.Names;
pub const fs = @import("./fs.zig");
const bun = @import("root").bun;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;
Expand Down Expand Up @@ -3218,16 +3218,12 @@ pub const Parser = struct {
}

// Detect a leading "// @bun" pragma
if (p.lexer.bun_pragma != .none and p.options.features.dont_bundle_twice) {
return js_ast.Result{
.already_bundled = switch (p.lexer.bun_pragma) {
.bun => .bun,
.bytecode => .bytecode,
.bytecode_cjs => .bytecode_cjs,
.bun_cjs => .bun_cjs,
else => unreachable,
},
};
if (self.options.features.dont_bundle_twice) {
if (self.hasBunPragma()) |pragma| {
return js_ast.Result{
.already_bundled = pragma,
};
}
}

// We must check the cache only after we've consumed the hashbang and leading // @bun pragma
Expand Down Expand Up @@ -4274,6 +4270,45 @@ pub const Parser = struct {
.log = log,
};
}

const PragmaState = packed struct { seen_cjs: bool = false, seen_bytecode: bool = false };

fn hasBunPragma(self: *const Parser) ?js_ast.Result.AlreadyBundled {
const BUN_PRAGMA = "// @bun";
var cursor: usize = 0;

const contents = self.lexer.source.contents;
if (!bun.strings.startsWith(contents[cursor..], BUN_PRAGMA)) return null;
cursor += BUN_PRAGMA.len;

var state: PragmaState = .{};

while (cursor < self.lexer.end) : (cursor += 1) {
switch (contents[cursor]) {
'\n' => break,
'@' => {
cursor += 1;
if (cursor >= contents.len) break;
if (contents[cursor] != 'b') continue;
const slice = contents[cursor..];
if (bun.strings.startsWith(slice, "bun-cjs")) {
state.seen_cjs = true;
cursor += "bun-cjs".len;
} else if (bun.strings.startsWith(slice, "bytecode")) {
state.seen_bytecode = true;
cursor += "bytecode".len;
}
},
else => {},
}
}

if (state.seen_cjs) {
return if (state.seen_bytecode) .bytecode_cjs else .bun_cjs;
} else {
return if (state.seen_bytecode) .bytecode else .bun;
}
}
};

const FindLabelSymbolResult = struct { ref: Ref, is_loop: bool, found: bool = false };
Expand Down
15 changes: 15 additions & 0 deletions test/bundler/transpiler/bun-pragma.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import path from "path";
import { bunExe, bunEnv } from "harness";

const fixturePath = (name: string): string => path.join(import.meta.dirname, "fixtures", name);

describe("@bun pragma", () => {
it("is not detected when embedded in a URL", async () => {
const res = Bun.spawn({
cmd: [bunExe(), "run", fixturePath("bun-in-url.ts")],
stdio: ["ignore", "ignore", "ignore"],
});
await res.exited;
expect(res.exitCode).toBe(0);
});
});
5 changes: 5 additions & 0 deletions test/bundler/transpiler/fixtures/bun-in-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// https://bun.sh/docs/api/http#bun-serve
const a: string = "hello";
console.log(a);

// '#bun' spotted in first comment but it's not a valid bun pragma
Loading