mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Compare commits
5 Commits
bun-v1.3.5
...
claude/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70bc217b26 | ||
|
|
dd27427aac | ||
|
|
94dcf1e87a | ||
|
|
24bb41e078 | ||
|
|
7387dc26b0 |
@@ -31,6 +31,7 @@ pub const JSBundler = struct {
|
||||
banner: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
footer: OwnedString = OwnedString.initEmpty(bun.default_allocator),
|
||||
css_chunking: bool = false,
|
||||
legal_comments: options.LegalComments = .eof,
|
||||
drop: bun.StringSet = bun.StringSet.init(bun.default_allocator),
|
||||
has_any_on_before_parse: bool = false,
|
||||
throw_on_error: bool = true,
|
||||
@@ -162,6 +163,16 @@ pub const JSBundler = struct {
|
||||
try this.footer.appendSliceExact(slice.slice());
|
||||
}
|
||||
|
||||
if (try config.get(globalThis, "legalComments")) |legal_comments| {
|
||||
if (!legal_comments.isUndefined()) {
|
||||
this.legal_comments = try legal_comments.toEnum(
|
||||
globalThis,
|
||||
"legalComments",
|
||||
options.LegalComments,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (try config.getTruthy(globalThis, "sourcemap")) |source_map_js| {
|
||||
if (source_map_js.isBoolean()) {
|
||||
if (source_map_js == .true) {
|
||||
|
||||
@@ -1751,6 +1751,7 @@ pub const BundleV2 = struct {
|
||||
transpiler.options.emit_dce_annotations = config.emit_dce_annotations orelse !config.minify.whitespace;
|
||||
transpiler.options.ignore_dce_annotations = config.ignore_dce_annotations;
|
||||
transpiler.options.css_chunking = config.css_chunking;
|
||||
transpiler.options.legal_comments = config.legal_comments;
|
||||
transpiler.options.banner = config.banner.slice();
|
||||
transpiler.options.footer = config.footer.slice();
|
||||
|
||||
|
||||
@@ -407,6 +407,7 @@ pub const Command = struct {
|
||||
banner: []const u8 = "",
|
||||
footer: []const u8 = "",
|
||||
css_chunking: bool = false,
|
||||
legal_comments: options.LegalComments = .eof,
|
||||
|
||||
bake: bool = false,
|
||||
bake_debug_dump_server: bool = false,
|
||||
|
||||
@@ -163,6 +163,7 @@ pub const build_only_params = [_]ParamType{
|
||||
clap.parseParam("--minify-syntax Minify syntax and inline data") catch unreachable,
|
||||
clap.parseParam("--minify-whitespace Minify whitespace") catch unreachable,
|
||||
clap.parseParam("--minify-identifiers Minify identifiers") catch unreachable,
|
||||
clap.parseParam("--legal-comments <STR>? Where to place legal comments. \"none\", \"inline\", \"eof\", \"linked\", \"external\" (default: \"eof\" when bundling, \"inline\" otherwise)") catch unreachable,
|
||||
clap.parseParam("--css-chunking Chunk CSS files together to reduce duplicated CSS loaded in a browser. Only has an effect when multiple entrypoints import CSS") catch unreachable,
|
||||
clap.parseParam("--dump-environment-variables") catch unreachable,
|
||||
clap.parseParam("--conditions <STR>... Pass custom conditions to resolve") catch unreachable,
|
||||
@@ -789,6 +790,15 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
|
||||
|
||||
ctx.bundler_options.css_chunking = args.flag("--css-chunking");
|
||||
|
||||
// Set legal comments - default to "eof" when bundling, "inline" otherwise
|
||||
if (args.option("--legal-comments")) |legal_comments_str| {
|
||||
ctx.bundler_options.legal_comments = options.LegalComments.fromString(legal_comments_str);
|
||||
} else {
|
||||
// esbuild's default: "eof" when bundling, "inline" otherwise
|
||||
// For now, we'll default to "eof" since this is in a bundling context
|
||||
ctx.bundler_options.legal_comments = .eof;
|
||||
}
|
||||
|
||||
ctx.bundler_options.emit_dce_annotations = args.flag("--emit-dce-annotations") or
|
||||
!ctx.bundler_options.minify_whitespace;
|
||||
|
||||
|
||||
@@ -1859,9 +1859,29 @@ fn NewLexer_(
|
||||
}
|
||||
}
|
||||
|
||||
fn isLegalComment(text: []const u8) bool {
|
||||
// Already have legal annotation (/*! or //!)
|
||||
if (text.len > 2 and text[2] == '!') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for JSDoc legal comment patterns like esbuild
|
||||
if (text.len > 3) {
|
||||
// Check for @license, @preserve, @copyright
|
||||
if (std.mem.indexOf(u8, text, "@license") != null or
|
||||
std.mem.indexOf(u8, text, "@preserve") != null or
|
||||
std.mem.indexOf(u8, text, "@copyright") != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn scanCommentText(noalias lexer: *LexerType, for_pragma: bool) void {
|
||||
const text = lexer.source.contents[lexer.start..lexer.end];
|
||||
const has_legal_annotation = text.len > 2 and text[2] == '!';
|
||||
const has_legal_annotation = isLegalComment(text);
|
||||
const is_multiline_comment = text.len > 1 and text[1] == '*';
|
||||
|
||||
if (lexer.track_comments)
|
||||
|
||||
@@ -1648,6 +1648,32 @@ pub const SourceMapOption = enum {
|
||||
});
|
||||
};
|
||||
|
||||
pub const LegalComments = enum {
|
||||
none,
|
||||
@"inline",
|
||||
eof,
|
||||
linked,
|
||||
external,
|
||||
|
||||
pub fn fromString(str: ?[]const u8) LegalComments {
|
||||
const s = str orelse return .eof; // Default to eof when bundling, inline otherwise (handled in Arguments.zig)
|
||||
if (strings.eqlComptime(s, "none")) return .none;
|
||||
if (strings.eqlComptime(s, "inline")) return .@"inline";
|
||||
if (strings.eqlComptime(s, "eof")) return .eof;
|
||||
if (strings.eqlComptime(s, "linked")) return .linked;
|
||||
if (strings.eqlComptime(s, "external")) return .external;
|
||||
return .eof; // Default fallback
|
||||
}
|
||||
|
||||
pub const Map = bun.ComptimeStringMap(LegalComments, .{
|
||||
.{ "none", .none },
|
||||
.{ "inline", .@"inline" },
|
||||
.{ "eof", .eof },
|
||||
.{ "linked", .linked },
|
||||
.{ "external", .external },
|
||||
});
|
||||
};
|
||||
|
||||
pub const PackagesOption = enum {
|
||||
bundle,
|
||||
external,
|
||||
@@ -1705,6 +1731,7 @@ pub const BundleOptions = struct {
|
||||
preserve_symlinks: bool = false,
|
||||
preserve_extensions: bool = false,
|
||||
production: bool = false,
|
||||
legal_comments: LegalComments = .eof,
|
||||
|
||||
// only used by bundle_v2
|
||||
output_format: Format = .esm,
|
||||
|
||||
@@ -956,4 +956,90 @@ export { greeting };`,
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
|
||||
test("legalComments option", async () => {
|
||||
const dir = tempDirWithFiles("bun-build-api-legal-comments", {
|
||||
"entry.js": `/*!
|
||||
* Legal comment with ! - should be preserved
|
||||
* Copyright 2024 Test Corp
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license MIT
|
||||
* This should be preserved as it contains @license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @preserve
|
||||
* This should be preserved as it contains @preserve
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a regular JSDoc comment - should be removed
|
||||
*/
|
||||
|
||||
//! Legal line comment - should be preserved
|
||||
|
||||
// Regular line comment - should be removed
|
||||
|
||||
console.log("hello world");`,
|
||||
});
|
||||
|
||||
// Test default behavior (should preserve legal comments)
|
||||
const build1 = await Bun.build({
|
||||
entrypoints: [join(dir, "entry.js")],
|
||||
});
|
||||
|
||||
expect(build1.success).toBe(true);
|
||||
expect(build1.outputs).toHaveLength(1);
|
||||
const output1 = await build1.outputs[0].text();
|
||||
|
||||
// Should preserve legal comments
|
||||
expect(output1).toContain("Legal comment with ! - should be preserved");
|
||||
expect(output1).toContain("@license MIT");
|
||||
expect(output1).toContain("@preserve");
|
||||
expect(output1).toContain("//! Legal line comment - should be preserved");
|
||||
|
||||
// Should remove regular comments
|
||||
expect(output1).not.toContain("This is a regular JSDoc comment - should be removed");
|
||||
expect(output1).not.toContain("Regular line comment - should be removed");
|
||||
|
||||
// Test legalComments: "eof" (explicit)
|
||||
const build2 = await Bun.build({
|
||||
entrypoints: [join(dir, "entry.js")],
|
||||
legalComments: "eof",
|
||||
});
|
||||
|
||||
expect(build2.success).toBe(true);
|
||||
expect(build2.outputs).toHaveLength(1);
|
||||
const output2 = await build2.outputs[0].text();
|
||||
|
||||
// Should still preserve legal comments
|
||||
expect(output2).toContain("Legal comment with ! - should be preserved");
|
||||
expect(output2).toContain("@license MIT");
|
||||
expect(output2).toContain("@preserve");
|
||||
|
||||
// Test legalComments: "inline"
|
||||
const build3 = await Bun.build({
|
||||
entrypoints: [join(dir, "entry.js")],
|
||||
legalComments: "inline",
|
||||
});
|
||||
|
||||
expect(build3.success).toBe(true);
|
||||
expect(build3.outputs).toHaveLength(1);
|
||||
const output3 = await build3.outputs[0].text();
|
||||
|
||||
// Should still preserve legal comments
|
||||
expect(output3).toContain("Legal comment with ! - should be preserved");
|
||||
expect(output3).toContain("@license MIT");
|
||||
expect(output3).toContain("@preserve");
|
||||
|
||||
// Test invalid legalComments value (should throw)
|
||||
await expect(async () => {
|
||||
await Bun.build({
|
||||
entrypoints: [join(dir, "entry.js")],
|
||||
legalComments: "invalid" as any,
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -351,3 +351,213 @@ describe("single-line comments", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("legal comments", () => {
|
||||
itBundled("preserve /*! style comments", {
|
||||
files: {
|
||||
"/entry.js": `/*!
|
||||
* This is a legal comment with ! - should be preserved
|
||||
* Copyright 2024 Test Corp
|
||||
*/
|
||||
console.log("hello");`,
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const output = api.readFile("/out.js");
|
||||
api.expectFile("/out.js").toContain("This is a legal comment with ! - should be preserved");
|
||||
api.expectFile("/out.js").toContain("Copyright 2024 Test Corp");
|
||||
api.expectFile("/out.js").toContain("hello");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("preserve //! style comments", {
|
||||
files: {
|
||||
"/entry.js": `//! This is a line comment with ! - should be preserved
|
||||
console.log("hello");`,
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const output = api.readFile("/out.js");
|
||||
api.expectFile("/out.js").toContain("//! This is a line comment with ! - should be preserved");
|
||||
api.expectFile("/out.js").toContain("hello");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("preserve @license comments", {
|
||||
files: {
|
||||
"/entry.js": `/**
|
||||
* @license MIT
|
||||
* This should be preserved as it contains @license
|
||||
*/
|
||||
console.log("hello");`,
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const output = api.readFile("/out.js");
|
||||
api.expectFile("/out.js").toContain("@license MIT");
|
||||
api.expectFile("/out.js").toContain("This should be preserved as it contains @license");
|
||||
api.expectFile("/out.js").toContain("hello");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("preserve @preserve comments", {
|
||||
files: {
|
||||
"/entry.js": `/**
|
||||
* @preserve
|
||||
* This should be preserved as it contains @preserve
|
||||
*/
|
||||
console.log("hello");`,
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const output = api.readFile("/out.js");
|
||||
api.expectFile("/out.js").toContain("@preserve");
|
||||
api.expectFile("/out.js").toContain("This should be preserved as it contains @preserve");
|
||||
api.expectFile("/out.js").toContain("hello");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("preserve @copyright comments", {
|
||||
files: {
|
||||
"/entry.js": `/**
|
||||
* @copyright
|
||||
* This should be preserved as it contains @copyright
|
||||
*/
|
||||
console.log("hello");`,
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const output = api.readFile("/out.js");
|
||||
api.expectFile("/out.js").toContain("@copyright");
|
||||
api.expectFile("/out.js").toContain("This should be preserved as it contains @copyright");
|
||||
api.expectFile("/out.js").toContain("hello");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("remove regular JSDoc comments", {
|
||||
files: {
|
||||
"/entry.js": `/**
|
||||
* This is a regular JSDoc comment - should be removed
|
||||
*/
|
||||
console.log("hello");`,
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const output = api.readFile("/out.js");
|
||||
api.expectFile("/out.js").not.toContain("This is a regular JSDoc comment - should be removed");
|
||||
api.expectFile("/out.js").toContain("hello");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("remove regular line comments", {
|
||||
files: {
|
||||
"/entry.js": `// This is a regular line comment - should be removed
|
||||
console.log("hello");`,
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const output = api.readFile("/out.js");
|
||||
api.expectFile("/out.js").not.toContain("This is a regular line comment - should be removed");
|
||||
api.expectFile("/out.js").toContain("hello");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("mixed legal and regular comments", {
|
||||
files: {
|
||||
"/entry.js": `/*!
|
||||
* Legal comment with ! - preserved
|
||||
*/
|
||||
/**
|
||||
* @license MIT
|
||||
* Legal @license comment - preserved
|
||||
*/
|
||||
/**
|
||||
* Regular JSDoc comment - removed
|
||||
*/
|
||||
// Regular line comment - removed
|
||||
//! Legal line comment - preserved
|
||||
console.log("hello");`,
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const output = api.readFile("/out.js");
|
||||
// Should preserve legal comments
|
||||
api.expectFile("/out.js").toContain("Legal comment with ! - preserved");
|
||||
api.expectFile("/out.js").toContain("@license MIT");
|
||||
api.expectFile("/out.js").toContain("Legal @license comment - preserved");
|
||||
api.expectFile("/out.js").toContain("//! Legal line comment - preserved");
|
||||
|
||||
// Should remove regular comments
|
||||
api.expectFile("/out.js").not.toContain("Regular JSDoc comment - removed");
|
||||
api.expectFile("/out.js").not.toContain("Regular line comment - removed");
|
||||
|
||||
api.expectFile("/out.js").toContain("hello");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("legal comments with minification", {
|
||||
files: {
|
||||
"/entry.js": `/*!
|
||||
* Legal comment - should be preserved even with minification
|
||||
*/
|
||||
/**
|
||||
* @license MIT
|
||||
* License comment - preserved
|
||||
*/
|
||||
/**
|
||||
* Regular comment - should be removed
|
||||
*/
|
||||
console.log("hello");`,
|
||||
},
|
||||
minifyWhitespace: true,
|
||||
minifySyntax: true,
|
||||
onAfterBundle(api) {
|
||||
const output = api.readFile("/out.js");
|
||||
// Legal comments should still be preserved
|
||||
api.expectFile("/out.js").toContain("Legal comment - should be preserved even with minification");
|
||||
api.expectFile("/out.js").toContain("@license MIT");
|
||||
api.expectFile("/out.js").toContain("License comment - preserved");
|
||||
|
||||
// Regular comments should be removed
|
||||
api.expectFile("/out.js").not.toContain("Regular comment - should be removed");
|
||||
|
||||
api.expectFile("/out.js").toContain("hello");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("case sensitivity in legal comments", {
|
||||
files: {
|
||||
"/entry.js": `/**
|
||||
* @LICENSE MIT (uppercase)
|
||||
* @PRESERVE (uppercase)
|
||||
* @COPYRIGHT (uppercase)
|
||||
*/
|
||||
/**
|
||||
* @License MIT (mixed case)
|
||||
* @Preserve (mixed case)
|
||||
* @Copyright (mixed case)
|
||||
*/
|
||||
console.log("hello");`,
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const output = api.readFile("/out.js");
|
||||
// Should preserve exact case and not do case-insensitive matching
|
||||
api.expectFile("/out.js").not.toContain("@LICENSE MIT (uppercase)");
|
||||
api.expectFile("/out.js").not.toContain("@PRESERVE (uppercase)");
|
||||
api.expectFile("/out.js").not.toContain("@COPYRIGHT (uppercase)");
|
||||
api.expectFile("/out.js").not.toContain("@License MIT (mixed case)");
|
||||
api.expectFile("/out.js").not.toContain("@Preserve (mixed case)");
|
||||
api.expectFile("/out.js").not.toContain("@Copyright (mixed case)");
|
||||
api.expectFile("/out.js").toContain("hello");
|
||||
},
|
||||
});
|
||||
|
||||
itBundled("legal comments in nested positions", {
|
||||
files: {
|
||||
"/entry.js": `function test() {
|
||||
/*!
|
||||
* Legal comment inside function - should be preserved
|
||||
*/
|
||||
return "hello";
|
||||
}
|
||||
console.log(test());`,
|
||||
},
|
||||
onAfterBundle(api) {
|
||||
const output = api.readFile("/out.js");
|
||||
api.expectFile("/out.js").toContain("Legal comment inside function - should be preserved");
|
||||
api.expectFile("/out.js").toContain("hello");
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user