Skip to content
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
14 changes: 7 additions & 7 deletions crates/oxc_codegen/src/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for Directive<'a> {
// A Use Strict Directive may not contain an EscapeSequence or LineContinuation.
// So here should print original `directive` value, the `expression` value is escaped str.
// See https://github.com/babel/babel/blob/main/packages/babel-generator/src/generators/base.ts#L64
p.wrap_quote(self.directive.as_str(), |p, _| {
p.wrap_quote(|p, _| {
p.print_str(self.directive.as_str());
});
p.print_semicolon_after_statement();
Expand Down Expand Up @@ -1268,7 +1268,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for RegExpLiteral<'a> {
}
}

fn print_unquoted_str<const MINIFY: bool>(s: &str, quote: char, p: &mut Codegen<{ MINIFY }>) {
fn print_unquoted_str<const MINIFY: bool>(s: &str, quote: u8, p: &mut Codegen<{ MINIFY }>) {
let mut chars = s.chars().peekable();

while let Some(c) = chars.next() {
Expand Down Expand Up @@ -1308,21 +1308,21 @@ fn print_unquoted_str<const MINIFY: bool>(s: &str, quote: char, p: &mut Codegen<
p.print_str("\\\\");
}
'\'' => {
if quote == '\'' {
if quote == b'\'' {
p.print_str("\\'");
} else {
p.print_str("'");
}
}
'\"' => {
if quote == '"' {
if quote == b'"' {
p.print_str("\\\"");
} else {
p.print_str("\"");
}
}
'`' => {
if quote == '`' {
if quote == b'`' {
p.print_str("\\`");
} else {
p.print_str("`");
Expand Down Expand Up @@ -1354,7 +1354,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for StringLiteral<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, _ctx: Context) {
p.add_source_mapping(self.span.start);
let s = self.value.as_str();
p.wrap_quote(s, |p, quote| {
p.wrap_quote(|p, quote| {
print_unquoted_str(s, quote, p);
});
}
Expand Down Expand Up @@ -2239,7 +2239,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for JSXAttributeValue<'a> {
Self::Element(el) => el.gen(p, ctx),
Self::StringLiteral(lit) => {
p.print_char(b'"');
print_unquoted_str(&lit.value, '"', p);
print_unquoted_str(&lit.value, b'"', p);
p.print_char(b'"');
}
Self::ExpressionContainer(expr_container) => expr_container.gen(p, ctx),
Expand Down
46 changes: 23 additions & 23 deletions crates/oxc_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ pub type CodeGenerator<'a> = Codegen<'a, false>;
/// Code generator with whitespace removal.
pub type WhitespaceRemover<'a> = Codegen<'a, true>;

#[derive(Default, Clone, Copy)]
pub struct CodegenOptions {
/// Use single quotes instead of double quotes.
pub single_quote: bool,
}

#[derive(Default, Clone, Copy)]
pub struct CommentOptions {
/// Enable preserve annotate comments, like `/* #__PURE__ */` and `/* #__NO_SIDE_EFFECTS__ */`.
Expand All @@ -51,6 +57,7 @@ pub struct CodegenReturn {
}

pub struct Codegen<'a, const MINIFY: bool> {
options: CodegenOptions,
comment_options: CommentOptions,

source_text: &'a str,
Expand Down Expand Up @@ -80,6 +87,9 @@ pub struct Codegen<'a, const MINIFY: bool> {
/// Track the current indentation level
indent: u32,

/// Fast path for [CodegenOptions::single_quote]
quote: u8,

// Builders
sourcemap_builder: Option<SourcemapBuilder>,

Expand Down Expand Up @@ -112,6 +122,7 @@ impl<'a, const MINIFY: bool> Codegen<'a, MINIFY> {
#[must_use]
pub fn new() -> Self {
Self {
options: CodegenOptions::default(),
comment_options: CommentOptions::default(),
source_text: "",
trivias: Trivias::default(),
Expand All @@ -127,6 +138,7 @@ impl<'a, const MINIFY: bool> Codegen<'a, MINIFY> {
start_of_arrow_expr: 0,
start_of_default_export: 0,
indent: 0,
quote: b'"',
sourcemap_builder: None,
move_comment_map: MoveCommentMap::default(),
}
Expand All @@ -141,6 +153,13 @@ impl<'a, const MINIFY: bool> Codegen<'a, MINIFY> {
self
}

#[must_use]
pub fn with_options(mut self, options: CodegenOptions) -> Self {
self.options = options;
self.quote = if options.single_quote { b'\'' } else { b'"' };
self
}

#[must_use]
pub fn enable_comment(
mut self,
Expand Down Expand Up @@ -467,11 +486,10 @@ impl<'a, const MINIFY: bool> Codegen<'a, MINIFY> {
}

#[inline]
fn wrap_quote<F: FnMut(&mut Self, char)>(&mut self, s: &str, mut f: F) {
let quote = Self::choose_quote(s);
self.print_char(quote as u8);
f(self, quote);
self.print_char(quote as u8);
fn wrap_quote<F: FnMut(&mut Self, u8)>(&mut self, mut f: F) {
self.print_char(self.quote);
f(self, self.quote);
self.print_char(self.quote);
}

fn print_directives_and_statements(
Expand Down Expand Up @@ -512,24 +530,6 @@ impl<'a, const MINIFY: bool> Codegen<'a, MINIFY> {
sourcemap_builder.add_source_mapping_for_name(&self.code, span, name);
}
}

fn choose_quote(s: &str) -> char {
let mut single_cost = 0;
let mut double_cost = 0;
for c in s.chars() {
match c {
'\'' => single_cost += 1,
'"' => double_cost += 1,
_ => {}
}
}

if single_cost > double_cost {
'"'
} else {
'\''
}
}
}

pub(crate) type MoveCommentMap = FxHashMap<u32, Comment>;
Expand Down
22 changes: 13 additions & 9 deletions crates/oxc_codegen/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
use oxc_allocator::Allocator;
use oxc_codegen::{CodeGenerator, CommentOptions};
use oxc_codegen::{CodeGenerator, CodegenOptions, CommentOptions};
use oxc_parser::Parser;
use oxc_span::SourceType;

fn test(source_text: &str, expected: &str) {
fn check(source_text: &str, expected: &str, source_type: SourceType) {
let allocator = Allocator::default();
let source_type = SourceType::default().with_module(true);
let ret = Parser::new(&allocator, source_text, source_type).parse();
let result = CodeGenerator::new().build(&ret.program).source_text;
let result = CodeGenerator::new()
.with_options(CodegenOptions { single_quote: true })
.build(&ret.program)
.source_text;
assert_eq!(expected, result, "for source {source_text}, expect {expected}, got {result}");
}

fn test(source_text: &str, expected: &str) {
let source_type = SourceType::default().with_module(true);
check(source_text, expected, source_type);
}

fn test_ts(source_text: &str, expected: &str, is_typescript_definition: bool) {
let allocator = Allocator::default();
let source_type = SourceType::default()
.with_typescript(true)
.with_typescript_definition(is_typescript_definition)
.with_module(true);
let ret = Parser::new(&allocator, source_text, source_type).parse();
let result = CodeGenerator::new().build(&ret.program).source_text;
assert_eq!(expected, result, "for source {source_text}, expect {expected}, got {result}");
check(source_text, expected, source_type);
}

#[test]
Expand All @@ -30,7 +34,7 @@ fn string() {
test("let x = '\t'", "let x = '\t';\n");
test(r"let x = '\v'", "let x = '\\v';\n");
test("let x = '\\n'", "let x = '\\n';\n");
test("let x = '\\''", "let x = \"'\";\n");
test("let x = '\\''", "let x = '\\'';\n");
test("let x = '\\\"'", "let x = '\"';\n");
// test( "let x = '\\'''", "let x = `''`;\n");
test("let x = '\\\\'", "let x = '\\\\';\n");
Expand Down
10 changes: 5 additions & 5 deletions crates/oxc_isolated_declarations/tests/snapshots/as-const.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/as-const.ts
==================== .D.TS ====================

declare const F: {
readonly string: 'string';
readonly templateLiteral: 'templateLiteral';
readonly string: "string";
readonly templateLiteral: "templateLiteral";
readonly number: 1.23;
readonly bigint: -1_2_3n;
readonly boolean: true;
Expand All @@ -15,8 +15,8 @@ declare const F: {
readonly function: (a: string) => void;
readonly arrow: (a: string) => void;
readonly object: {
readonly a: 'a';
readonly b: 'b';
readonly a: "a";
readonly b: "b";
};
readonly array: readonly ['a', undefined, { readonly b: '\n'}];
readonly array: readonly ["a", undefined, { readonly b: "\n"}];
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export declare abstract class Qux {
baz(): void;
}
export declare class Baz {
readonly prop1 = 'some string';
readonly prop1 = "some string";
prop2: string;
private prop3;
private prop4;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/eliminate-imports.ts
---
==================== .D.TS ====================

import { AExtend, BExtend, Type, CImplements1, CImplements2, CType, ThisType1, ThisType2 } from 'mod';
import { AExtend, BExtend, Type, CImplements1, CImplements2, CType, ThisType1, ThisType2 } from "mod";
export interface A extends AExtend<Type> {}
export declare class B extends BExtend<Type> {}
export declare class C implements CImplements1<CType>, CImplements2<CType> {}
export declare function foo(this: ThisType1 ): void;
export declare const bar: (this: ThisType2 ) => void;
import { type InferType1, type InferType2 } from 'infer';
import { type InferType1, type InferType2 } from "infer";
export type F<X extends InferType1> = X extends infer U extends InferType2 ? U : never;
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/infer-template-liter
---
==================== .D.TS ====================

export declare const CSS_VARS_HELPER = 'useCssVars';
export declare const CSS_VARS_HELPER = "useCssVars";
export declare function g(func?: string): void;
export declare const F: {
readonly a: 'a';
readonly b: readonly ['b'];
readonly a: "a";
readonly b: readonly ["b"];
};
export declare let GOOD: string;
export declare const BAD: unknown;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ input_file: crates/oxc_isolated_declarations/tests/fixtures/mapped-types.ts
---
==================== .D.TS ====================

import { K } from 'foo';
import { T } from 'bar';
import { K } from "foo";
import { T } from "bar";
export interface I {
prop: { [key in K] : T};
}
6 changes: 3 additions & 3 deletions crates/oxc_linter/src/fixer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::borrow::Cow;

use oxc_codegen::Codegen;
use oxc_codegen::{CodeGenerator, CodegenOptions};
use oxc_diagnostics::OxcDiagnostic;
use oxc_span::{GetSpan, Span, SPAN};

Expand Down Expand Up @@ -219,8 +219,8 @@ impl<'c, 'a: 'c> RuleFixer<'c, 'a> {
}

#[allow(clippy::unused_self)]
pub fn codegen(self) -> Codegen<'a, false> {
Codegen::<false>::new()
pub fn codegen(self) -> CodeGenerator<'a> {
CodeGenerator::new().with_options(CodegenOptions { single_quote: true })
}

#[allow(clippy::unused_self)]
Expand Down
7 changes: 5 additions & 2 deletions crates/oxc_minifier/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mod oxc;
// mod terser;

use oxc_allocator::Allocator;
use oxc_codegen::WhitespaceRemover;
use oxc_codegen::{CodegenOptions, WhitespaceRemover};
use oxc_minifier::{CompressOptions, Minifier, MinifierOptions};
use oxc_parser::Parser;
use oxc_span::SourceType;
Expand All @@ -20,7 +20,10 @@ pub(crate) fn minify(
let ret = Parser::new(&allocator, source_text, source_type).parse();
let program = allocator.alloc(ret.program);
Minifier::new(options).build(&allocator, program);
WhitespaceRemover::new().build(program).source_text
WhitespaceRemover::new()
.with_options(CodegenOptions { single_quote: true })
.build(program)
.source_text
}

pub(crate) fn test(source_text: &str, expected: &str) {
Expand Down
7 changes: 5 additions & 2 deletions crates/oxc_minifier/tests/oxc/remove_dead_code.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use oxc_allocator::Allocator;
use oxc_codegen::CodeGenerator;
use oxc_codegen::{CodeGenerator, CodegenOptions};
use oxc_minifier::RemoveDeadCode;
use oxc_parser::Parser;
use oxc_span::SourceType;
Expand All @@ -12,7 +12,10 @@ fn print(source_text: &str, remove_dead_code: bool) -> String {
if remove_dead_code {
RemoveDeadCode::new(&allocator).build(program);
}
CodeGenerator::new().build(program).source_text
CodeGenerator::new()
.with_options(CodegenOptions { single_quote: true })
.build(program)
.source_text
}

pub(crate) fn test(source_text: &str, expected: &str) {
Expand Down
7 changes: 5 additions & 2 deletions crates/oxc_minifier/tests/oxc/replace_global_defines.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use oxc_allocator::Allocator;
use oxc_codegen::WhitespaceRemover;
use oxc_codegen::{CodegenOptions, WhitespaceRemover};
use oxc_minifier::{ReplaceGlobalDefines, ReplaceGlobalDefinesConfig};
use oxc_parser::Parser;
use oxc_span::SourceType;
Expand All @@ -11,7 +11,10 @@ pub(crate) fn test(source_text: &str, expected: &str, config: ReplaceGlobalDefin
let ret = Parser::new(&allocator, source_text, source_type).parse();
let program = allocator.alloc(ret.program);
ReplaceGlobalDefines::new(&allocator, config).build(program);
WhitespaceRemover::new().build(program).source_text
WhitespaceRemover::new()
.with_options(CodegenOptions { single_quote: true })
.build(program)
.source_text
};
assert_eq!(minified, expected, "for source {source_text}");
}
Expand Down
Loading