mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-15 22:59:17 +00:00
fix(codegen): order accessibility before abstract on accessors (#26392)
Codegen emits invalid TypeScript when an abstract accessor has an explicit accessibility modifier. Both normal and minified output put `abstract` before `public` or `protected`, causing TS1029 because accessibility must precede `abstract`.
For this valid input:
```ts
abstract class C {
public abstract accessor x: number;
protected abstract accessor y: string;
}
```
The previous output reordered the members to:
```ts
abstract public accessor x: number;
abstract protected accessor y: string;
```
`AccessorProperty::gen` now prints accessibility before `abstract`, matching the neighboring method and property emitters and preserving the input's valid modifier order. The same ordering applies in minified output:
```ts
abstract class C{public abstract accessor x:number;protected abstract accessor y:string}
```
The change is confined to accessor modifier ordering; subsequent `static`, `override`, and `accessor` emission remains unchanged.
This commit is contained in:
1 parent
34a242e134
commit
b156333a23
3 files changed
+65
-8
No files matched your search
@@ -3021,16 +3021,16 @@ impl Gen for AccessorProperty<'_> {
|
||||
fn r#gen(&self, p: &mut Codegen, ctx: Context) {
|
||||
p.add_source_mapping(self.span);
|
||||
p.print_decorators(&self.decorators, ctx);
|
||||
if self.r#type.is_abstract() {
|
||||
p.print_space_before_identifier();
|
||||
p.print_str("abstract");
|
||||
p.print_soft_space();
|
||||
}
|
||||
if let Some(accessibility) = self.accessibility {
|
||||
p.print_space_before_identifier();
|
||||
p.print_str(accessibility.as_str());
|
||||
p.print_soft_space();
|
||||
}
|
||||
if self.r#type.is_abstract() {
|
||||
p.print_space_before_identifier();
|
||||
p.print_str("abstract");
|
||||
p.print_soft_space();
|
||||
}
|
||||
if self.r#static {
|
||||
p.print_space_before_identifier();
|
||||
p.print_str("static");
|
||||
|
||||
@@ -39,6 +39,11 @@ pub fn test_tsx(source_text: &str, expected: &str) {
|
||||
test_options_with_source_type(source_text, expected, SourceType::tsx(), default_options());
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn test_ts(source_text: &str, expected: &str) {
|
||||
test_options_with_source_type(source_text, expected, SourceType::ts(), default_options());
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn test_options_with_source_type(
|
||||
source_text: &str,
|
||||
|
||||
@@ -3,13 +3,65 @@ use oxc_parser::ParseOptions;
|
||||
use oxc_span::SourceType;
|
||||
|
||||
use crate::{
|
||||
snapshot, snapshot_options,
|
||||
snapshot, snapshot_options, test_ts,
|
||||
tester::{
|
||||
default_options, test, test_idempotency, test_options_with_source_type, test_same,
|
||||
test_tsx, test_with_parse_options,
|
||||
default_options, test, test_idempotency, test_idempotency_options,
|
||||
test_options_with_source_type, test_same, test_tsx, test_with_parse_options,
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn abstract_accessor_modifiers() {
|
||||
test_ts(
|
||||
"abstract class C { public abstract accessor x: number; protected abstract accessor y: string; }",
|
||||
"abstract class C {\n\tpublic abstract accessor x: number;\n\tprotected abstract accessor y: string;\n}\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minify_abstract_accessor_modifiers() {
|
||||
test_options_with_source_type(
|
||||
"abstract class C { public abstract accessor x: number; protected abstract accessor y: string; }",
|
||||
"abstract class C{public abstract accessor x:number;protected abstract accessor y:string}",
|
||||
SourceType::ts(),
|
||||
CodegenOptions::minify(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accessor_modifier_combinations() {
|
||||
let check = |source: &str, minified: &str| {
|
||||
test_ts(source, source);
|
||||
test_idempotency(source);
|
||||
test_options_with_source_type(source, minified, SourceType::ts(), CodegenOptions::minify());
|
||||
test_idempotency_options(source, &CodegenOptions::minify());
|
||||
};
|
||||
check(
|
||||
"abstract class C {\n\tabstract accessor x: number;\n}\n",
|
||||
"abstract class C{abstract accessor x:number}",
|
||||
);
|
||||
check(
|
||||
"abstract class C extends B {\n\tpublic abstract override accessor x: number;\n}\n",
|
||||
"abstract class C extends B{public abstract override accessor x:number}",
|
||||
);
|
||||
check(
|
||||
"abstract class C extends B {\n\tprotected abstract override accessor x: number;\n}\n",
|
||||
"abstract class C extends B{protected abstract override accessor x:number}",
|
||||
);
|
||||
check("class C {\n\tpublic static accessor x = 1;\n}\n", "class C{public static accessor x=1}");
|
||||
check(
|
||||
"class C {\n\tprotected static accessor x = 1;\n}\n",
|
||||
"class C{protected static accessor x=1}",
|
||||
);
|
||||
check("class C {\n\tprivate accessor x = 1;\n}\n", "class C{private accessor x=1}");
|
||||
check(
|
||||
"class C extends B {\n\tpublic override accessor x = 1;\n}\n",
|
||||
"class C extends B{public override accessor x=1}",
|
||||
);
|
||||
check("class C {\n\taccessor #x = 1;\n}\n", "class C{accessor#x=1}");
|
||||
check("class C {\n\tpublic accessor x!: number;\n}\n", "class C{public accessor x!:number}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cases() {
|
||||
test_same("({ foo(): string {} });\n");
|
||||
|
||||
Reference in new issue
Block a user