Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 4 additions & 4 deletions src/runtime/api/JSTranspiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,7 @@ impl TransformTask {
fn then(&mut self, promise: &mut JSPromise, global: &JSGlobalObject) -> JsResult<()> {
// The job drops this `TransformTask` (running its `Drop`: transpiler
// deref etc.) right after `then` returns.
if self.log.has_any() || self.err.is_some() {
if self.log.has_errors() || self.err.is_some() {
let error_value: JsResult<JSValue> = 'brk: {
if let Some(err) = &self.err {
if !self.log.has_any() {
Expand Down Expand Up @@ -1317,7 +1317,7 @@ impl JSTranspiler {
return Err(global.throw(format_args!("Failed to parse")));
};

if (log_ref.warnings + log_ref.errors) > 0 {
if log_ref.has_errors() {
return Err(global.throw_value(log_ref.to_js(global, format_args!("Parse error"))?));
}

Expand Down Expand Up @@ -1501,7 +1501,7 @@ impl JSTranspiler {
return Err(global.throw(format_args!("Failed to parse code")));
};

if (log_ref.warnings + log_ref.errors) > 0 {
if log_ref.has_errors() {
return Err(global.throw_value(log_ref.to_js(global, format_args!("Parse error"))?));
}

Expand Down Expand Up @@ -1711,7 +1711,7 @@ impl JSTranspiler {
return Err(global.throw_error(err, "Failed to scan imports"));
}

if (log.warnings + log.errors) > 0 {
if log.has_errors() {
return Err(
global.throw_value(log.to_js(global, format_args!("Failed to scan imports"))?)
);
Expand Down
84 changes: 77 additions & 7 deletions test/bundler/transpiler/transpiler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2450,6 +2450,60 @@ export default <>hi</>
).toThrow();
});

describe("a parser warning does not throw", () => {
// `-->` at the start of a line is a legacy HTML comment. The parser logs a
// warning for it and keeps going.
const warnSrc = "x = 1\n--> legacy html comment\ny = 2\n";
const warnJsx = 'import { a } from "a";\nexport default <div {...obj} key="k" />;\n';

it("transformSync", () => {
const t = new Bun.Transpiler({ loader: "js" });
expect(t.transformSync(warnSrc)).toBe("x = 1;\ny = 2;\n");
});

it("transform", async () => {
const t = new Bun.Transpiler({ loader: "js" });
expect(await t.transform(warnSrc)).toBe("x = 1;\ny = 2;\n");
});

it("scan", () => {
const t = new Bun.Transpiler({ loader: "jsx" });
expect(t.scan(warnJsx)).toEqual({
imports: [{ kind: "import-statement", path: "a" }],
exports: ["default"],
});
});

it("scanImports", () => {
const t = new Bun.Transpiler({ loader: "jsx" });
// The key-after-spread warning makes the JSX fall back to the classic
// runtime, so the scan also lists the runtime requires.
expect(t.scanImports(warnJsx)).toEqual([
{ kind: "import-statement", path: "a" },
{ kind: "require-call", path: "react/jsx-dev-runtime" },
{ kind: "require-call", path: "react" },
]);
});

it("a parse error next to a warning still throws", () => {
const t = new Bun.Transpiler({ loader: "js" });
const src = warnSrc + "bad??!?!?!";
for (const fn of ["transformSync", "scan", "scanImports"]) {
Comment thread
robobun marked this conversation as resolved.
Resolved
let thrown;
try {
t[fn](src);
} catch (e) {
thrown = e;
}
expect(thrown).toBeInstanceOf(AggregateError);
expect(thrown.errors.map(m => [m.level, m.message])).toEqual([
["warn", 'Treating "-->" as the start of a legacy HTML single-line comment'],
["error", "Unexpected ?"],
]);
}
});
});

it("define with an empty-string key is ignored without leaving uninitialized slots", async () => {
// `JSPropertyIterator` skips empty-name properties, but `names`/`values` were being
// indexed by the property position instead of a dense counter, leaving garbage in the
Expand Down Expand Up @@ -3794,15 +3848,31 @@ class Foo {
expectParseError("class Foo { static { continue } }", 'Cannot use "continue" here');
expectParseError("x: { class Foo { static { break x } } }", 'There is no containing label named "x"');
expectParseError("x: { class Foo { static { continue x } } }", 'There is no containing label named "x"');
});

it("private accessor and method misuse is a warning", async () => {
const cases = [
["class Foo { get #x() { this.#x = 1 } }", 'Writing to getter-only property "#x" will throw'],
["class Foo { get #x() { this.#x += 1 } }", 'Writing to getter-only property "#x" will throw'],
["class Foo { set #x(x) { this.#x } }", 'Reading from setter-only property "#x" will throw'],
["class Foo { set #x(x) { this.#x += 1 } }", 'Reading from setter-only property "#x" will throw'],
["class Foo { #x() { this.#x = 1 } }", 'Writing to read-only method "#x" will throw'],
["class Foo { #x() { this.#x += 1 } }", 'Writing to read-only method "#x" will throw'],
];

expectParseError("class Foo { get #x() { this.#x = 1 } }", 'Writing to getter-only property "#x" will throw');
expectParseError("class Foo { get #x() { this.#x += 1 } }", 'Writing to getter-only property "#x" will throw');
expectParseError("class Foo { set #x(x) { this.#x } }", 'Reading from setter-only property "#x" will throw');
expectParseError("class Foo { set #x(x) { this.#x += 1 } }", 'Reading from setter-only property "#x" will throw');
// The transpiler keeps going after a warning, so the code is still printed.
for (const [code] of cases) {
expect(transpiler.transformSync(code)).toContain("class Foo");
}

// Writing to method warnings
expectParseError("class Foo { #x() { this.#x = 1 } }", 'Writing to read-only method "#x" will throw');
expectParseError("class Foo { #x() { this.#x += 1 } }", 'Writing to read-only method "#x" will throw');
using dir = tempDir("private-warnings", {
"in.js": cases.map(([code]) => `{ ${code} }`).join("\n") + "\n",
});
const result = await Bun.build({ entrypoints: [join(String(dir), "in.js")], write: false });
expect(result.success).toBe(true);
expect(result.logs.map(log => [log.level, log.position.line, log.message])).toEqual(
cases.map(([, message], i) => ["warn", i + 1, message]),
);
});

it("class bodies keep `this` and the class name as written", () => {
Expand Down
Loading