mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
feat(codegen): better whitespace minification for import / export statements (#7650)
part of #7638
This commit is contained in:
parent
c793d71a78
commit
c523ccb7ef
7 changed files with 362 additions and 32 deletions
|
|
@ -1,5 +1,5 @@
|
|||
#![allow(clippy::print_stdout)]
|
||||
use std::{env, path::Path};
|
||||
use std::path::Path;
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_codegen::{CodeGenerator, CodegenOptions};
|
||||
|
|
@ -8,14 +8,16 @@ use oxc_span::SourceType;
|
|||
use pico_args::Arguments;
|
||||
|
||||
// Instruction:
|
||||
// 1. create a `test.js`
|
||||
// 2. run `cargo run -p oxc_codegen --example codegen` or `just example codegen`
|
||||
// create a `test.js`,
|
||||
// run `cargo run -p oxc_codegen --example codegen`
|
||||
// or `cargo watch -x "run -p oxc_codegen --example codegen"`
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let mut args = Arguments::from_env();
|
||||
let name = env::args().nth(1).unwrap_or_else(|| "test.js".to_string());
|
||||
|
||||
let twice = args.contains("--twice");
|
||||
let minify = args.contains("--minify");
|
||||
let name = args.free_from_str().unwrap_or_else(|_| "test.js".to_string());
|
||||
|
||||
let path = Path::new(&name);
|
||||
let source_text = std::fs::read_to_string(path)?;
|
||||
|
|
|
|||
|
|
@ -726,12 +726,14 @@ impl Gen for ImportDeclaration<'_> {
|
|||
fn gen(&self, p: &mut Codegen, ctx: Context) {
|
||||
p.add_source_mapping(self.span);
|
||||
p.print_indent();
|
||||
p.print_str("import ");
|
||||
p.print_space_before_identifier();
|
||||
p.print_str("import");
|
||||
if self.import_kind.is_type() {
|
||||
p.print_str("type ");
|
||||
p.print_str(" type");
|
||||
}
|
||||
if let Some(specifiers) = &self.specifiers {
|
||||
if specifiers.is_empty() {
|
||||
p.print_soft_space();
|
||||
p.print_str("{}");
|
||||
p.print_soft_space();
|
||||
p.print_str("from");
|
||||
|
|
@ -755,23 +757,33 @@ impl Gen for ImportDeclaration<'_> {
|
|||
p.print_soft_space();
|
||||
p.print_str("},");
|
||||
in_block = false;
|
||||
} else if index != 0 {
|
||||
} else if index == 0 {
|
||||
p.print_hard_space();
|
||||
} else {
|
||||
p.print_comma();
|
||||
p.print_soft_space();
|
||||
}
|
||||
spec.local.print(p, ctx);
|
||||
if index == specifiers.len() - 1 {
|
||||
p.print_hard_space();
|
||||
}
|
||||
}
|
||||
ImportDeclarationSpecifier::ImportNamespaceSpecifier(spec) => {
|
||||
if in_block {
|
||||
p.print_soft_space();
|
||||
p.print_str("},");
|
||||
in_block = false;
|
||||
} else if index != 0 {
|
||||
} else if index == 0 {
|
||||
p.print_soft_space();
|
||||
} else {
|
||||
p.print_comma();
|
||||
p.print_soft_space();
|
||||
}
|
||||
p.print_str("* as ");
|
||||
p.print_ascii_byte(b'*');
|
||||
p.print_soft_space();
|
||||
p.print_str("as ");
|
||||
spec.local.print(p, ctx);
|
||||
p.print_hard_space();
|
||||
}
|
||||
ImportDeclarationSpecifier::ImportSpecifier(spec) => {
|
||||
if in_block {
|
||||
|
|
@ -780,9 +792,9 @@ impl Gen for ImportDeclaration<'_> {
|
|||
} else {
|
||||
if index != 0 {
|
||||
p.print_comma();
|
||||
p.print_soft_space();
|
||||
}
|
||||
in_block = true;
|
||||
p.print_soft_space();
|
||||
p.print_ascii_byte(b'{');
|
||||
p.print_soft_space();
|
||||
}
|
||||
|
|
@ -804,12 +816,14 @@ impl Gen for ImportDeclaration<'_> {
|
|||
if in_block {
|
||||
p.print_soft_space();
|
||||
p.print_ascii_byte(b'}');
|
||||
p.print_soft_space();
|
||||
}
|
||||
p.print_str(" from ");
|
||||
p.print_str("from");
|
||||
}
|
||||
p.print_soft_space();
|
||||
self.source.print(p, ctx);
|
||||
if let Some(with_clause) = &self.with_clause {
|
||||
p.print_hard_space();
|
||||
p.print_soft_space();
|
||||
with_clause.print(p, ctx);
|
||||
}
|
||||
p.add_source_mapping_end(self.span);
|
||||
|
|
@ -822,9 +836,15 @@ impl Gen for WithClause<'_> {
|
|||
p.add_source_mapping(self.span);
|
||||
self.attributes_keyword.print(p, ctx);
|
||||
p.print_soft_space();
|
||||
p.print_block_start(self.span);
|
||||
p.print_sequence(&self.with_entries, ctx);
|
||||
p.print_block_end(self.span);
|
||||
p.add_source_mapping(self.span);
|
||||
p.print_ascii_byte(b'{');
|
||||
if !self.with_entries.is_empty() {
|
||||
p.print_soft_space();
|
||||
p.print_list(&self.with_entries, ctx);
|
||||
p.print_soft_space();
|
||||
}
|
||||
p.add_source_mapping_end(self.span);
|
||||
p.print_ascii_byte(b'}');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -864,11 +884,12 @@ impl Gen for ExportNamedDeclaration<'_> {
|
|||
_ => {}
|
||||
};
|
||||
}
|
||||
p.print_str("export ");
|
||||
p.print_str("export");
|
||||
if self.export_kind.is_type() {
|
||||
p.print_str("type ");
|
||||
p.print_str(" type ");
|
||||
}
|
||||
if let Some(decl) = &self.declaration {
|
||||
p.print_hard_space();
|
||||
match decl {
|
||||
Declaration::VariableDeclaration(decl) => decl.print(p, ctx),
|
||||
Declaration::FunctionDeclaration(decl) => decl.print(p, ctx),
|
||||
|
|
@ -891,6 +912,7 @@ impl Gen for ExportNamedDeclaration<'_> {
|
|||
p.needs_semicolon = false;
|
||||
}
|
||||
} else {
|
||||
p.print_soft_space();
|
||||
p.print_ascii_byte(b'{');
|
||||
if !self.specifiers.is_empty() {
|
||||
p.print_soft_space();
|
||||
|
|
@ -969,18 +991,25 @@ impl Gen for ExportAllDeclaration<'_> {
|
|||
fn gen(&self, p: &mut Codegen, ctx: Context) {
|
||||
p.add_source_mapping(self.span);
|
||||
p.print_indent();
|
||||
p.print_str("export ");
|
||||
p.print_str("export");
|
||||
if self.export_kind.is_type() {
|
||||
p.print_str("type ");
|
||||
p.print_str(" type ");
|
||||
} else {
|
||||
p.print_soft_space();
|
||||
}
|
||||
p.print_ascii_byte(b'*');
|
||||
|
||||
if let Some(exported) = &self.exported {
|
||||
p.print_str(" as ");
|
||||
p.print_soft_space();
|
||||
p.print_str("as ");
|
||||
exported.print(p, ctx);
|
||||
p.print_hard_space();
|
||||
} else {
|
||||
p.print_soft_space();
|
||||
}
|
||||
|
||||
p.print_str(" from ");
|
||||
p.print_str("from");
|
||||
p.print_soft_space();
|
||||
self.source.print(p, ctx);
|
||||
if let Some(with_clause) = &self.with_clause {
|
||||
p.print_hard_space();
|
||||
|
|
|
|||
|
|
@ -382,13 +382,6 @@ impl<'a> Codegen<'a> {
|
|||
self.print_ascii_byte(b'=');
|
||||
}
|
||||
|
||||
fn print_sequence<T: Gen>(&mut self, items: &[T], ctx: Context) {
|
||||
for item in items {
|
||||
item.print(self, ctx);
|
||||
self.print_comma();
|
||||
}
|
||||
}
|
||||
|
||||
fn print_curly_braces<F: FnOnce(&mut Self)>(&mut self, span: Span, single_line: bool, op: F) {
|
||||
self.add_source_mapping(span);
|
||||
self.print_ascii_byte(b'{');
|
||||
|
|
|
|||
199
crates/oxc_codegen/tests/integration/snapshots/minify.snap
Normal file
199
crates/oxc_codegen/tests/integration/snapshots/minify.snap
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
---
|
||||
source: crates/oxc_codegen/tests/integration/main.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
########## 0
|
||||
let x: string = `\x01`;
|
||||
----------
|
||||
let x:string=`\x01`;
|
||||
########## 1
|
||||
function foo<T extends string>(x: T, y: string, ...restOfParams: Omit<T, 'x'>): T {
|
||||
return x;
|
||||
}
|
||||
----------
|
||||
function foo<T extends string>(x:T,y:string,...restOfParams:Omit<T,'x'>): T{return x}
|
||||
########## 2
|
||||
let x: string[] = ['abc', 'def', 'ghi'];
|
||||
----------
|
||||
let x:string[]=['abc','def','ghi'];
|
||||
########## 3
|
||||
let x: Array<string> = ['abc', 'def', 'ghi',];
|
||||
----------
|
||||
let x:Array<string>=['abc','def','ghi'];
|
||||
########## 4
|
||||
let x: [string, number] = ['abc', 123];
|
||||
----------
|
||||
let x:[string,number]=['abc',123];
|
||||
########## 5
|
||||
let x: string | number = 'abc';
|
||||
----------
|
||||
let x:string|number='abc';
|
||||
########## 6
|
||||
let x: string & number = 'abc';
|
||||
----------
|
||||
let x:string&number='abc';
|
||||
########## 7
|
||||
let x: typeof String = 'string';
|
||||
----------
|
||||
let x:typeof String='string';
|
||||
########## 8
|
||||
let x: keyof string = 'length';
|
||||
----------
|
||||
let x:keyof string='length';
|
||||
########## 9
|
||||
let x: keyof typeof String = 'length';
|
||||
----------
|
||||
let x:keyof typeof String='length';
|
||||
########## 10
|
||||
let x: string['length'] = 123;
|
||||
----------
|
||||
let x:string['length']=123;
|
||||
########## 11
|
||||
function isString(value: unknown): asserts value is string {
|
||||
if (typeof value !== 'string') {
|
||||
throw new Error('Not a string');
|
||||
}
|
||||
}
|
||||
----------
|
||||
function isString(value:unknown): asserts value is string{if(typeof value!=='string'){throw new Error('Not a string')}}
|
||||
########## 12
|
||||
import type { Foo } from 'foo';
|
||||
----------
|
||||
import type{Foo}from'foo';
|
||||
########## 13
|
||||
import { Foo, type Bar } from 'foo';
|
||||
----------
|
||||
import{Foo,type Bar}from'foo';
|
||||
########## 14
|
||||
export { Foo, type Bar } from 'foo';
|
||||
----------
|
||||
export{Foo,type Bar}from'foo';
|
||||
########## 15
|
||||
type A<T> = { [K in keyof T as K extends string ? B<K> : K ]: T[K] }
|
||||
----------
|
||||
type A<T>={[K in keyof T as K extends string ? B<K> : K]:T[K]};
|
||||
########## 16
|
||||
class A {readonly type = 'frame'}
|
||||
----------
|
||||
class A{readonly type='frame'}
|
||||
########## 17
|
||||
let foo: { <T>(t: T): void }
|
||||
----------
|
||||
let foo:{<T>(t:T):void};
|
||||
########## 18
|
||||
let foo: { new <T>(t: T): void }
|
||||
----------
|
||||
let foo:{new <T>(t:T):void};
|
||||
########## 19
|
||||
function <const T>(){}
|
||||
----------
|
||||
function<const T>(){}
|
||||
########## 20
|
||||
class A {m?(): void}
|
||||
----------
|
||||
class A{m?():void;}
|
||||
########## 21
|
||||
class A {constructor(public readonly a: number) {}}
|
||||
----------
|
||||
class A{constructor(public readonly a:number){}}
|
||||
########## 22
|
||||
abstract class A {private abstract static m() {}}
|
||||
----------
|
||||
abstract class A{private abstract static m(){}}
|
||||
########## 23
|
||||
abstract class A {private abstract static readonly prop: string}
|
||||
----------
|
||||
abstract class A{private abstract static readonly prop:string}
|
||||
########## 24
|
||||
a = x!;
|
||||
----------
|
||||
a=x! ;
|
||||
########## 25
|
||||
b = (x as y);
|
||||
----------
|
||||
b=x as y;
|
||||
########## 26
|
||||
c = foo<string>;
|
||||
----------
|
||||
c=foo<string> ;
|
||||
########## 27
|
||||
d = x satisfies y;
|
||||
----------
|
||||
d=((x) satisfies y);
|
||||
########## 28
|
||||
export @x declare abstract class C {}
|
||||
----------
|
||||
export @x declare abstract class C{}
|
||||
########## 29
|
||||
div<T>``
|
||||
----------
|
||||
div<T>``;
|
||||
########## 30
|
||||
export type Component<Props = any> = Foo;
|
||||
----------
|
||||
export type Component<Props = any>=Foo;
|
||||
########## 31
|
||||
|
||||
export type Component<
|
||||
Props = any,
|
||||
RawBindings = any,
|
||||
D = any,
|
||||
C extends ComputedOptions = ComputedOptions,
|
||||
M extends MethodOptions = MethodOptions,
|
||||
E extends EmitsOptions | Record<string, any[]> = {},
|
||||
S extends Record<string, any> = any,
|
||||
> =
|
||||
| ConcreteComponent<Props, RawBindings, D, C, M, E, S>
|
||||
| ComponentPublicInstanceConstructor<Props>
|
||||
|
||||
----------
|
||||
export type Component<Props = any,RawBindings = any,D = any,C extends ComputedOptions = ComputedOptions,M extends MethodOptions = MethodOptions,E extends EmitsOptions|Record<string,any[]> = {},S extends Record<string,any> = any>=ConcreteComponent<Props,RawBindings,D,C,M,E,S>|ComponentPublicInstanceConstructor<Props>;
|
||||
########## 32
|
||||
(a || b) as any
|
||||
----------
|
||||
(a||b) as any;
|
||||
########## 33
|
||||
(a ** b) as any
|
||||
----------
|
||||
(a**b) as any;
|
||||
########## 34
|
||||
(function g() {}) as any
|
||||
----------
|
||||
(function g(){}) as any;
|
||||
########## 35
|
||||
|
||||
import defaultExport from "module-name";
|
||||
import * as name from "module-name";
|
||||
import { export1 } from "module-name";
|
||||
import { export1 as alias1 } from "module-name";
|
||||
import { default as alias } from "module-name";
|
||||
import { export1, export2 } from "module-name";
|
||||
import { export1, export2 as alias2, /* … */ } from "module-name";
|
||||
import { "string name" as alias } from "module-name";
|
||||
import defaultExport, { export1, /* … */ } from "module-name";
|
||||
import defaultExport, * as name from "module-name";
|
||||
import "module-name";
|
||||
import {} from 'mod';
|
||||
|
||||
export let name1, name2/*, … */; // also var
|
||||
export const name3 = 1, name4 = 2/*, … */; // also var, let
|
||||
export function functionName() { /* … */ }
|
||||
export class ClassName { /* … */ }
|
||||
export function* generatorFunctionName() { /* … */ }
|
||||
export const { name5, name2: bar } = o;
|
||||
export const [ name6, name7 ] = array;
|
||||
|
||||
export { name8, /* …, */ name81 };
|
||||
export { variable1 as name9, variable2 as name10, /* …, */ name82 };
|
||||
export { variable1 as "string name" };
|
||||
export { name1 as default1 /*, … */ };
|
||||
|
||||
export * from "module-name";
|
||||
export * as name11 from "module-name";
|
||||
export { name12, /* …, */ nameN } from "module-name";
|
||||
export { import1 as name13, import2 as name14, /* …, */ name15 } from "module-name";
|
||||
export { default, /* …, */ } from "module-name";
|
||||
export { default as name16 } from "module-name";
|
||||
|
||||
----------
|
||||
import defaultExport from'module-name';import*as name from'module-name';import{export1}from'module-name';import{export1 as alias1}from'module-name';import{default as alias}from'module-name';import{export1,export2}from'module-name';import{export1,export2 as alias2}from'module-name';import{'string name' as alias}from'module-name';import defaultExport,{export1}from'module-name';import defaultExport,*as name from'module-name';import'module-name';import{}from"mod";export let name1,name2;export const name3=1,name4=2;export function functionName(){}export class ClassName{}export function*generatorFunctionName(){}export const {name5,name2:bar}=o;export const [name6,name7]=array;export{name8,name81};export{variable1 as name9,variable2 as name10,name82};export{variable1 as 'string name'};export{name1 as default1};export*from'module-name';export*as name11 from'module-name';export{name12,nameN}from'module-name';export{import1 as name13,import2 as name14,name15}from'module-name';export{default}from'module-name';export{default as name16}from'module-name';
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
source: crates/oxc_codegen/tests/integration/main.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
########## 0
|
||||
let x: string = `\x01`;
|
||||
|
|
@ -225,3 +226,69 @@ export type Component<
|
|||
(function g() {}) as any
|
||||
----------
|
||||
(function g() {}) as any;
|
||||
|
||||
########## 35
|
||||
|
||||
import defaultExport from "module-name";
|
||||
import * as name from "module-name";
|
||||
import { export1 } from "module-name";
|
||||
import { export1 as alias1 } from "module-name";
|
||||
import { default as alias } from "module-name";
|
||||
import { export1, export2 } from "module-name";
|
||||
import { export1, export2 as alias2, /* … */ } from "module-name";
|
||||
import { "string name" as alias } from "module-name";
|
||||
import defaultExport, { export1, /* … */ } from "module-name";
|
||||
import defaultExport, * as name from "module-name";
|
||||
import "module-name";
|
||||
import {} from 'mod';
|
||||
|
||||
export let name1, name2/*, … */; // also var
|
||||
export const name3 = 1, name4 = 2/*, … */; // also var, let
|
||||
export function functionName() { /* … */ }
|
||||
export class ClassName { /* … */ }
|
||||
export function* generatorFunctionName() { /* … */ }
|
||||
export const { name5, name2: bar } = o;
|
||||
export const [ name6, name7 ] = array;
|
||||
|
||||
export { name8, /* …, */ name81 };
|
||||
export { variable1 as name9, variable2 as name10, /* …, */ name82 };
|
||||
export { variable1 as "string name" };
|
||||
export { name1 as default1 /*, … */ };
|
||||
|
||||
export * from "module-name";
|
||||
export * as name11 from "module-name";
|
||||
export { name12, /* …, */ nameN } from "module-name";
|
||||
export { import1 as name13, import2 as name14, /* …, */ name15 } from "module-name";
|
||||
export { default, /* …, */ } from "module-name";
|
||||
export { default as name16 } from "module-name";
|
||||
|
||||
----------
|
||||
import defaultExport from 'module-name';
|
||||
import * as name from 'module-name';
|
||||
import { export1 } from 'module-name';
|
||||
import { export1 as alias1 } from 'module-name';
|
||||
import { default as alias } from 'module-name';
|
||||
import { export1, export2 } from 'module-name';
|
||||
import { export1, export2 as alias2 } from 'module-name';
|
||||
import { 'string name' as alias } from 'module-name';
|
||||
import defaultExport, { export1 } from 'module-name';
|
||||
import defaultExport, * as name from 'module-name';
|
||||
import 'module-name';
|
||||
import {} from "mod";
|
||||
export let name1, name2;
|
||||
export const name3 = 1, name4 = 2;
|
||||
export function functionName() {}
|
||||
export class ClassName {}
|
||||
export function* generatorFunctionName() {}
|
||||
export const { name5, name2: bar } = o;
|
||||
export const [name6, name7] = array;
|
||||
export { name8, name81 };
|
||||
export { variable1 as name9, variable2 as name10, name82 };
|
||||
export { variable1 as 'string name' };
|
||||
export { name1 as default1 };
|
||||
export * from 'module-name';
|
||||
export * as name11 from 'module-name';
|
||||
export { name12, nameN } from 'module-name';
|
||||
export { import1 as name13, import2 as name14, name15 } from 'module-name';
|
||||
export { default } from 'module-name';
|
||||
export { default as name16 } from 'module-name';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::snapshot;
|
||||
use crate::{snapshot, snapshot_options};
|
||||
use oxc_codegen::CodegenOptions;
|
||||
|
||||
#[test]
|
||||
fn ts() {
|
||||
|
|
@ -54,7 +55,46 @@ export type Component<
|
|||
"(a || b) as any",
|
||||
"(a ** b) as any",
|
||||
"(function g() {}) as any",
|
||||
r#"
|
||||
import defaultExport from "module-name";
|
||||
import * as name from "module-name";
|
||||
import { export1 } from "module-name";
|
||||
import { export1 as alias1 } from "module-name";
|
||||
import { default as alias } from "module-name";
|
||||
import { export1, export2 } from "module-name";
|
||||
import { export1, export2 as alias2, /* … */ } from "module-name";
|
||||
import { "string name" as alias } from "module-name";
|
||||
import defaultExport, { export1, /* … */ } from "module-name";
|
||||
import defaultExport, * as name from "module-name";
|
||||
import "module-name";
|
||||
import {} from 'mod';
|
||||
|
||||
export let name1, name2/*, … */; // also var
|
||||
export const name3 = 1, name4 = 2/*, … */; // also var, let
|
||||
export function functionName() { /* … */ }
|
||||
export class ClassName { /* … */ }
|
||||
export function* generatorFunctionName() { /* … */ }
|
||||
export const { name5, name2: bar } = o;
|
||||
export const [ name6, name7 ] = array;
|
||||
|
||||
export { name8, /* …, */ name81 };
|
||||
export { variable1 as name9, variable2 as name10, /* …, */ name82 };
|
||||
export { variable1 as "string name" };
|
||||
export { name1 as default1 /*, … */ };
|
||||
|
||||
export * from "module-name";
|
||||
export * as name11 from "module-name";
|
||||
export { name12, /* …, */ nameN } from "module-name";
|
||||
export { import1 as name13, import2 as name14, /* …, */ name15 } from "module-name";
|
||||
export { default, /* …, */ } from "module-name";
|
||||
export { default as name16 } from "module-name";
|
||||
"#
|
||||
];
|
||||
|
||||
snapshot("ts", &cases);
|
||||
snapshot_options(
|
||||
"minify",
|
||||
&cases,
|
||||
&CodegenOptions { minify: true, ..CodegenOptions::default() },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ use crate::tester::{test, test_minify, test_without_source};
|
|||
#[test]
|
||||
fn module_decl() {
|
||||
test("export * as foo from 'foo'", "export * as foo from \"foo\";\n");
|
||||
test("import x from './foo.js' with {}", "import x from \"./foo.js\" with {\n};\n");
|
||||
test("import {} from './foo.js' with {}", "import {} from \"./foo.js\" with {\n};\n");
|
||||
test("export * from './foo.js' with {}", "export * from \"./foo.js\" with {\n};\n");
|
||||
test("import x from './foo.js' with {}", "import x from \"./foo.js\" with {};\n");
|
||||
test("import {} from './foo.js' with {}", "import {} from \"./foo.js\" with {};\n");
|
||||
test("export * from './foo.js' with {}", "export * from \"./foo.js\" with {};\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue