mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(codegen): add option for choosing quotes; remove slow choose_quot method (#4219)
This commit is contained in:
parent
3e56b2bd16
commit
83c2c62f7b
14 changed files with 397 additions and 384 deletions
|
|
@ -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();
|
||||
|
|
@ -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() {
|
||||
|
|
@ -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("`");
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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__ */`.
|
||||
|
|
@ -51,6 +57,7 @@ pub struct CodegenReturn {
|
|||
}
|
||||
|
||||
pub struct Codegen<'a, const MINIFY: bool> {
|
||||
options: CodegenOptions,
|
||||
comment_options: CommentOptions,
|
||||
|
||||
source_text: &'a str,
|
||||
|
|
@ -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>,
|
||||
|
||||
|
|
@ -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(),
|
||||
|
|
@ -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(),
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
@ -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(
|
||||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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"}];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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}");
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -22,9 +22,9 @@ export type A = {
|
|||
[(globalThis.Symbol).unscopables]: number;
|
||||
[aliasing.isConcatSpreadable]: number;
|
||||
[1]: number;
|
||||
['2']: number;
|
||||
["2"]: number;
|
||||
[(missing2)]: number;
|
||||
[Math.random() > 0.5 ? 'f1' : 'f2']: number;
|
||||
[Math.random() > 0.5 ? "f1" : "f2"]: number;
|
||||
};
|
||||
export interface B {
|
||||
[missing]: number;
|
||||
|
|
@ -35,9 +35,9 @@ export interface B {
|
|||
[(globalThis.Symbol).unscopables]: number;
|
||||
[aliasing.isConcatSpreadable]: number;
|
||||
[1]: number;
|
||||
['2']: number;
|
||||
["2"]: number;
|
||||
[(missing2)]: number;
|
||||
[Math.random() > 0.5 ? 'f1' : 'f2']: number;
|
||||
[Math.random() > 0.5 ? "f1" : "f2"]: number;
|
||||
}
|
||||
export class C {
|
||||
[missing]: number = 1;
|
||||
|
|
@ -48,9 +48,9 @@ export class C {
|
|||
[(globalThis.Symbol).unscopables]: number = 1;
|
||||
[aliasing.isConcatSpreadable]: number = 1;
|
||||
[1]: number = 1;
|
||||
['2']: number = 1;
|
||||
["2"]: number = 1;
|
||||
[(missing2)]: number = 1;
|
||||
[Math.random() > 0.5 ? 'f1' : 'f2']: number = 1;
|
||||
[Math.random() > 0.5 ? "f1" : "f2"]: number = 1;
|
||||
}
|
||||
export const D = {
|
||||
[missing]: 1,
|
||||
|
|
@ -61,9 +61,9 @@ export const D = {
|
|||
[(globalThis.Symbol).unscopables]: 1,
|
||||
[aliasing.isConcatSpreadable]: 1,
|
||||
[1]: 1,
|
||||
['2']: 1,
|
||||
["2"]: 1,
|
||||
[(missing2)]: 1,
|
||||
[Math.random() > 0.5 ? 'f1' : 'f2']: 1
|
||||
[Math.random() > 0.5 ? "f1" : "f2"]: 1
|
||||
};
|
||||
|
||||
//// [declarationComputedPropertyNames.d.ts] ////
|
||||
|
|
@ -80,9 +80,9 @@ export type A = {
|
|||
[(globalThis.Symbol).unscopables]: number;
|
||||
[aliasing.isConcatSpreadable]: number;
|
||||
[1]: number;
|
||||
['2']: number;
|
||||
["2"]: number;
|
||||
[(missing2)]: number;
|
||||
[Math.random() > 0.5 ? 'f1' : 'f2']: number;
|
||||
[Math.random() > 0.5 ? "f1" : "f2"]: number;
|
||||
};
|
||||
export interface B {
|
||||
[missing]: number;
|
||||
|
|
@ -93,17 +93,17 @@ export interface B {
|
|||
[(globalThis.Symbol).unscopables]: number;
|
||||
[aliasing.isConcatSpreadable]: number;
|
||||
[1]: number;
|
||||
['2']: number;
|
||||
["2"]: number;
|
||||
[(missing2)]: number;
|
||||
[Math.random() > 0.5 ? 'f1' : 'f2']: number;
|
||||
[Math.random() > 0.5 ? "f1" : "f2"]: number;
|
||||
}
|
||||
export declare class C {
|
||||
[1]: number;
|
||||
['2']: number;
|
||||
["2"]: number;
|
||||
}
|
||||
export declare const D: {
|
||||
1: number;
|
||||
'2': number;
|
||||
"2": number;
|
||||
};
|
||||
export {};
|
||||
x TS9010: Variable must have an explicit type annotation with
|
||||
|
|
|
|||
Loading…
Reference in a new issue