diff --git a/src/fast_check/transform.rs b/src/fast_check/transform.rs index 7abcbef0b..0390b97d6 100644 --- a/src/fast_check/transform.rs +++ b/src/fast_check/transform.rs @@ -4,6 +4,7 @@ #![allow(clippy::disallowed_methods)] #![allow(clippy::disallowed_types)] +use std::collections::HashSet; use std::sync::Arc; use deno_ast::swc::ast::*; @@ -474,6 +475,8 @@ impl<'a> FastCheckTransformer<'a> { } } let mut insert_members = Vec::new(); + let mut had_private_constructor = false; + let mut seen_ts_private_methods = HashSet::new(); for mut member in std::mem::take(&mut n.body) { had_private = had_private || matches!( @@ -481,8 +484,48 @@ impl<'a> FastCheckTransformer<'a> { ClassMember::PrivateMethod(_) | ClassMember::PrivateProp(_) ); - let retain = - self.transform_class_member(&mut member, &mut insert_members)?; + let mut retain = true; + // do some extra checks to see whether it should be removed + if let ClassMember::Constructor(ctor) = &member { + if ctor.accessibility == Some(Accessibility::Private) { + if had_private_constructor { + retain = false; + } else { + had_private_constructor = true; + } + } + } else if let ClassMember::Method(method) = &member { + if method.accessibility == Some(Accessibility::Private) { + let key = match &method.key { + PropName::Ident(i) => Some(i.sym.to_string()), + PropName::Str(s) => Some(s.value.to_string()), + PropName::Num(n) => Some( + n.raw + .as_ref() + .map(|r| r.to_string()) + .unwrap_or_else(|| n.value.to_string()), + ), + + PropName::Computed(_) => None, + PropName::BigInt(n) => Some( + n.raw + .as_ref() + .map(|r| r.to_string()) + .unwrap_or_else(|| n.value.to_string()), + ), + }; + retain = match key { + Some(key) => seen_ts_private_methods.insert(key), + None => false, + }; + } + } + + if retain { + retain = + self.transform_class_member(&mut member, &mut insert_members)?; + } + if retain { members.push(member); } else { @@ -638,9 +681,6 @@ impl<'a> FastCheckTransformer<'a> { } if n.accessibility == Some(Accessibility::Private) { - if n.body.is_none() { - return Ok(false); - } n.params.clear(); return Ok(true); } diff --git a/tests/specs/graph/jsr/FastCheck_ClassCtors.txt b/tests/specs/graph/jsr/FastCheck_ClassCtors.txt index 616f0a345..c18dff3f9 100644 --- a/tests/specs/graph/jsr/FastCheck_ClassCtors.txt +++ b/tests/specs/graph/jsr/FastCheck_ClassCtors.txt @@ -20,6 +20,9 @@ export class ClassPrivateCtorWithOverloads { private constructor(prop: string, other: Private1) { } } +export declare class ClassDeclarePrivateCtor { + private constructor(prop: string, other: Private1); +} export class ClassProtectedCtor { protected constructor(prop: string, other = 5) { } @@ -97,7 +100,7 @@ import 'jsr:@scope/a' }, { "kind": "esm", - "size": 1279, + "size": 1382, "mediaType": "TypeScript", "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" } @@ -119,7 +122,10 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: private constructor(){} } export class ClassPrivateCtorWithOverloads { - private constructor(){} + private constructor(); + } + export declare class ClassDeclarePrivateCtor { + private constructor(); } export class ClassProtectedCtor { protected constructor(prop: string, other?: number){} @@ -171,6 +177,9 @@ Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: export declare class ClassPrivateCtorWithOverloads { private constructor(); } + export declare class ClassDeclarePrivateCtor { + private constructor(); + } export declare class ClassProtectedCtor { protected constructor(prop: string, other?: number); } diff --git a/tests/specs/graph/jsr/FastCheck_ClassMethods.txt b/tests/specs/graph/jsr/FastCheck_ClassMethods.txt new file mode 100644 index 000000000..ea85ca0b3 --- /dev/null +++ b/tests/specs/graph/jsr/FastCheck_ClassMethods.txt @@ -0,0 +1,152 @@ +# https://jsr.io/@scope/a/meta.json +{"versions": { "1.0.0": {} } } + +# https://jsr.io/@scope/a/1.0.0_meta.json +{ "exports": { ".": "./mod.ts" } } + +# https://jsr.io/@scope/a/1.0.0/mod.ts +const getEntity = Symbol(); + +export class PrivateMethodOverloadable { + private constructor(); + private constructor() { + } + + private [getEntity](): Promise; + private async [getEntity]() { + } + + private method(): string { + + } + + private methodWithOverload(): string; + private methodWithOverload(): string { + + } + + private [123](): string; + private [123](): string { + + } + + private [9876n](): string; + private [9876n](): string { + + } + + private "stringAndIdent"(): string; + private stringAndIdent(): string { + } + + private "stringOnly"(): string; + private "stringOnly"(): string { + } +} + +class Private {} + +export class AmbientPrivateMethodOverloadable { + private constructor(value: string); + private constructor(private: Private); + + private [getEntity](value: string): Promise; + private async [getEntity](); + + private method(value: string): string; + + private methodWithOverload(value: string): string; + private methodWithOverload(): string; + + private [123](value: string): string; + private [123](): string; + + private [9876n](value: string): string; + private [9876n](): string; + + private "stringAndIdent"(value: string): string; + private stringAndIdent(): string; + + private "stringOnly"(value: string): string; + private "stringOnly"(): string; +} + +# mod.ts +import 'jsr:@scope/a' + +# output +{ + "roots": [ + "file:///mod.ts" + ], + "modules": [ + { + "kind": "esm", + "dependencies": [ + { + "specifier": "jsr:@scope/a", + "code": { + "specifier": "jsr:@scope/a", + "span": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 21 + } + } + } + } + ], + "size": 22, + "mediaType": "TypeScript", + "specifier": "file:///mod.ts" + }, + { + "kind": "esm", + "size": 1289, + "mediaType": "TypeScript", + "specifier": "https://jsr.io/@scope/a/1.0.0/mod.ts" + } + ], + "redirects": { + "jsr:@scope/a": "https://jsr.io/@scope/a/1.0.0/mod.ts" + }, + "packages": { + "@scope/a": "@scope/a@1.0.0" + } +} + +Fast check https://jsr.io/@scope/a/1.0.0/mod.ts: + {} + export class PrivateMethodOverloadable { + private constructor(); + private method!: unknown; + private methodWithOverload!: unknown; + private "stringAndIdent"!: unknown; + private "stringOnly"!: unknown; + } + export class AmbientPrivateMethodOverloadable { + private constructor(); + private method!: unknown; + private methodWithOverload!: unknown; + private "stringAndIdent"!: unknown; + private "stringOnly"!: unknown; + } + --- DTS --- + export declare class PrivateMethodOverloadable { + private constructor(); + private method: unknown; + private methodWithOverload: unknown; + private "stringAndIdent": unknown; + private "stringOnly": unknown; + } + export declare class AmbientPrivateMethodOverloadable { + private constructor(); + private method: unknown; + private methodWithOverload: unknown; + private "stringAndIdent": unknown; + private "stringOnly": unknown; + }