Skip to content

Commit

Permalink
feat(codegen): add option for choosing quotes; remove slow `choose_qu…
Browse files Browse the repository at this point in the history
…ot` method
  • Loading branch information
Boshen committed Jul 12, 2024
1 parent cb15303 commit 04155aa
Show file tree
Hide file tree
Showing 14 changed files with 397 additions and 384 deletions.
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

0 comments on commit 04155aa

Please sign in to comment.