char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
f90c0ce
main
1use super :: * ; 2 3# [ test ] 4fn erases_types_without_removing_value_imports_or_leaking_private_modules () { 5let module ="avril:quoted\"name" ; 6let compiled =compile ( 7Path :: new ( "src/api.ts" ), 8r#" 9import type { Internal } from "ext:private/types.js"; 10import { unused } from "ext:private/value.js"; 11const internal: Internal | null = null; 12export function identity(value: unknown): unknown { return value; } 13"# , 14Some ( module), 15) 16. unwrap (); 17let allocator =Allocator :: default (); 18let javascript =Parser :: new ( & allocator, & compiled. javascript , SourceType :: mjs ()). parse (); 19assert! ( 20 javascript. diagnostics . is_empty (), 21"{:?}" , 22 javascript. diagnostics 23); 24let imports: Vec < _ > = javascript 25. program 26. body 27. iter () 28. filter_map ( |statement|match statement{ 29Statement :: ImportDeclaration ( import) =>Some ( import. source . value . as_str ()), 30 _ =>None , 31}) 32. collect (); 33assert_eq! ( imports, [ "ext:private/value.js" ]); 34 35let declaration = compiled. declaration . unwrap (); 36let declarations =Parser :: new ( & allocator, & declaration, SourceType :: d_ts ()). parse (); 37assert! ( 38 declarations. diagnostics . is_empty (), 39"{:?}" , 40 declarations. diagnostics 41); 42assert_eq! ( declarations. program . body . len (), 1 ); 43let Statement :: TSExternalModuleDeclaration ( public) =& declarations. program . body [ 0 ] else { 44panic! ( "Expected an ambient public module" ); 45}; 46assert! ( public. declare ); 47assert_eq! ( public. id . value . as_str (), module); 48let body =& public. body . as_ref (). unwrap (). body ; 49assert_eq! ( body. len (), 1 ); 50let Statement :: ExportDeclaration ( export) =& body[ 0 ] else { 51panic! ( "Expected the public function" ); 52}; 53let Declaration :: FunctionDeclaration ( function) =& export. declaration else { 54panic! ( "Expected a function declaration" ); 55}; 56assert_eq! ( function. id . as_ref (). unwrap (). name . as_str (), "identity" ); 57assert! ( function. body . is_none ()); 58assert! ( !function. declare ); 59} 60 61# [ test ] 62fn internal_modules_do_not_require_isolated_declarations_or_function_only_exports () { 63let compiled =compile ( 64Path :: new ( "src/internal.ts" ), 65"import 'ext:private/setup.js'; export class State { #value: number = 42; value() { return this.#value; } }" , 66None , 67) 68. unwrap (); 69assert! ( compiled. declaration . is_none ()); 70let allocator =Allocator :: default (); 71let parsed =Parser :: new ( & allocator, & compiled. javascript , SourceType :: mjs ()). parse (); 72assert! ( parsed. diagnostics . is_empty (), "{:?}" , parsed. diagnostics ); 73let Statement :: ImportDeclaration ( import) =& parsed. program . body [ 0 ] else { 74panic! ( "Expected the side-effect import" ); 75}; 76assert_eq! ( import. source . value . as_str (), "ext:private/setup.js" ); 77let Statement :: ExportDeclaration ( export) =& parsed. program . body [ 1 ] else { 78panic! ( "Expected the native class export" ); 79}; 80assert! ( matches! ( 81 export. declaration , 82Declaration :: ClassDeclaration ( _) 83)); 84} 85 86# [ test ] 87fn isolated_declaration_diagnostics_are_propagated () { 88assert! ( 89compile ( 90Path :: new ( "src/api.ts" ), 91"export function value() { return 42; }" , 92Some ( "avril:fixture" ), 93) 94. is_ok () 95); 96let error =compile ( 97Path :: new ( "src/api.ts" ), 98"export function value() { return Math.random(); }" , 99Some ( "avril:fixture" ), 100) 101. err () 102. expect ( "Nontrivial return expressions require an annotation" ); 103assert! ( error. to_string (). contains ( "return type" ), "{error}" ); 104} 105 106# [ test ] 107fn unsupported_public_declarations_fail_instead_of_being_dropped () { 108for sourcein [ 109"export enum Value { One }" , 110"export namespace Value { export type Inner = number; }" , 111"export class Value {}" , 112"export { value } from 'ext:private/main.js';" , 113"export default function value(): number { return 42; }" , 114"import type { Value } from 'ext:private/types.js'; export function value(v: Value): Value { return v; }" , 115] { 116let error =compile ( Path :: new ( "src/api.ts" ), source, Some ( "avril:fixture" )) 117. err () 118. expect ( "Unsupported public declarations must fail" ); 119assert! ( 120 error. to_string (). contains ( "Unsupported public declaration" ), 121"{source}: {error}" 122); 123} 124} 125 126# [ test ] 127fn packages_callable_intersections_and_their_exported_types () { 128let compiled =compile ( 129Path :: new ( "src/public.ts" ), 130r#" 131import { registration } from "ext:private/main.js"; 132export interface Options { name: string; } 133export type Register = ((name: string) => void) & ((options: Options) => void); 134export const register: Register & { only: Register } = registration; 135"# , 136Some ( "avril:fixture" ), 137) 138. unwrap (); 139let declaration = compiled. declaration . unwrap (); 140assert! ( !declaration. contains ( "ext:private" )); 141let allocator =Allocator :: default (); 142let parsed =Parser :: new ( & allocator, & declaration, SourceType :: d_ts ()). parse (); 143assert! ( parsed. diagnostics . is_empty (), "{:?}" , parsed. diagnostics ); 144let Statement :: TSExternalModuleDeclaration ( public) =& parsed. program . body [ 0 ] else { 145panic! ( "Expected an ambient public module" ); 146}; 147let exports: Vec < _ > = public 148. body 149. as_ref () 150. unwrap () 151. body 152. iter () 153. map ( |statement|{ 154let Statement :: ExportDeclaration ( export) = statementelse { 155panic! ( "Expected a public export" ); 156}; 157match & export. declaration { 158Declaration :: TSInterfaceDeclaration ( interface) => interface. id . name . as_str (), 159Declaration :: TSTypeAliasDeclaration ( alias) => alias. id . name . as_str (), 160Declaration :: VariableDeclaration ( variable) =>{ 161assert! ( !variable. declare ); 162assert_eq! ( variable. declarations . len (), 1 ); 163assert! ( variable. declarations [ 0 ]. init . is_none ()); 164let oxc:: ast:: ast:: BindingPattern :: BindingIdentifier ( id) = 165& variable. declarations [ 0 ]. id 166else { 167panic! ( "Expected the exported binding" ); 168}; 169 id. name . as_str () 170} 171 _ =>panic! ( "Unexpected public declaration" ), 172} 173}) 174. collect (); 175assert_eq! ( exports, [ "Options" , "Register" , "register" ]); 176} 177 178# [ test ] 179fn private_types_are_kept_without_being_exported () { 180let compiled =compile ( 181Path :: new ( "src/public.ts" ), 182r#" 183type Hidden = { value: number }; 184interface AlsoHidden { text: string; } 185import { convert as implementation } from "./internal.js"; 186export const convert: (input: Hidden) => AlsoHidden = implementation; 187"# , 188Some ( "avril:fixture" ), 189) 190. unwrap (); 191let declaration = compiled. declaration . unwrap (); 192assert! ( !declaration. contains ( "./internal.js" )); 193let allocator =Allocator :: default (); 194let parsed =Parser :: new ( & allocator, & declaration, SourceType :: d_ts ()). parse (); 195assert! ( parsed. diagnostics . is_empty (), "{:?}" , parsed. diagnostics ); 196let Statement :: TSExternalModuleDeclaration ( public) =& parsed. program . body [ 0 ] else { 197panic! ( "Expected an ambient public module" ); 198}; 199let mut exported =0 ; 200let mut markers =0 ; 201let mut private =Vec :: new (); 202for statementin public. body . as_ref (). unwrap (). body . iter () { 203match statement{ 204Statement :: ExportDeclaration ( _) => exported +=1 , 205Statement :: ExportNamedDeclaration ( export) =>{ 206assert! ( export. specifiers . is_empty ()); 207 markers +=1 ; 208} 209Statement :: TSInterfaceDeclaration ( interface) =>{ 210 private. push ( interface. id . name . as_str ()); 211} 212Statement :: TSTypeAliasDeclaration ( alias) => private. push ( alias. id . name . as_str ()), 213 statement =>panic! ( "Unexpected public statement: {statement:?}" ), 214} 215} 216assert_eq! ( exported, 1 ); 217// The marker is required even though the body has exports: without it 218// the private declarations would be importable. 219assert_eq! ( markers, 1 ); 220assert_eq! ( private, [ "Hidden" , "AlsoHidden" ]); 221} 222 223# [ test ] 224fn public_type_imports_and_reexports_are_preserved_verbatim () { 225let compiled =compile ( 226Path :: new ( "src/public/sync.ts" ), 227r#" 228import type { CopyOptions } from "avril:fs"; 229export type { CopyOptions } from "avril:fs"; 230import { copy as implementation } from "../fs_sync.js"; 231export const copy: (source: string, options?: CopyOptions) => void = implementation; 232"# , 233Some ( "avril:fixture/sync" ), 234) 235. unwrap (); 236let declaration = compiled. declaration . unwrap (); 237assert! ( declaration. contains ( "from \"avril:fs\"" )); 238assert! ( !declaration. contains ( "../fs_sync.js" )); 239let allocator =Allocator :: default (); 240let parsed =Parser :: new ( & allocator, & declaration, SourceType :: d_ts ()). parse (); 241assert! ( parsed. diagnostics . is_empty (), "{:?}" , parsed. diagnostics ); 242let Statement :: TSExternalModuleDeclaration ( public) =& parsed. program . body [ 0 ] else { 243panic! ( "Expected an ambient public module" ); 244}; 245let mut imports =0 ; 246let mut reexports =0 ; 247let mut exports =0 ; 248for statementin public. body . as_ref (). unwrap (). body . iter () { 249match statement{ 250Statement :: ImportDeclaration ( import) =>{ 251assert_eq! ( import. source . value . as_str (), "avril:fs" ); 252 imports +=1 ; 253} 254Statement :: ExportFromDeclaration ( export) =>{ 255assert_eq! ( export. source . value . as_str (), "avril:fs" ); 256 reexports +=1 ; 257} 258Statement :: ExportDeclaration ( _) => exports +=1 , 259 statement =>panic! ( "Unexpected public statement: {statement:?}" ), 260} 261} 262assert_eq! (( imports, reexports, exports), ( 1 , 1 , 1 )); 263} 264 265# [ test ] 266fn private_module_references_fail_instead_of_being_bundled () { 267for sourcein [ 268"import type { Value } from './types.js'; export function value(v: Value): Value { return v; }" , 269"export type { Value } from './types.js';" , 270"import { Value } from 'avril:fixture'; import { value as implementation } from './internal.js'; export const value: (v: Value) => Value = implementation;" , 271"export { Value } from 'avril:fixture';" , 272] { 273let error =compile ( Path :: new ( "src/api.ts" ), source, Some ( "avril:fixture" )) 274. err () 275. expect ( "Private module references must fail" ); 276assert! ( 277 error. to_string (). contains ( "Unsupported public declaration" ), 278"{source}: {error}" 279); 280} 281} 282 283# [ test ] 284fn outputs_mirror_source_paths_under_src () { 285use std:: path:: PathBuf ; 286let out =Path :: new ( "target" ); 287assert_eq! ( 288output_path ( out, Path :: new ( "src/main.ts" )). unwrap (), 289PathBuf :: from ( "target/main.ts" ) 290); 291assert_eq! ( 292output_path ( out, Path :: new ( "src/public.ts" )). unwrap (), 293PathBuf :: from ( "target/public.ts" ) 294); 295assert_eq! ( 296output_path ( out, Path :: new ( "src/public/posix.ts" )). unwrap (), 297PathBuf :: from ( "target/public/posix.ts" ) 298); 299output_path ( out, Path :: new ( "other.ts" )). expect_err ( "Sources outside src/ must fail" ); 300} 301 302# [ test ] 303fn invalid_syntax_is_rejected_with_source_diagnostics () { 304let error =compile ( Path :: new ( "src/broken.ts" ), "export function broken(" , None ) 305. err () 306. expect ( "Invalid syntax must fail" ); 307assert! ( 308 error. to_string (). contains ( "export function broken(" ), 309"{error}" 310); 311} 312 313# [ test ] 314fn implementation_only_declarations_are_pruned_from_the_public_declaration () { 315let compiled =compile ( 316Path :: new ( "src/public.ts" ), 317r#" 318import { factory } from "ext:core/ops"; 319import type { Unused } from "./internal.js"; 320interface State { entries: Map<string, string>; } 321const state = new Map<string, State>(); 322class Impl { run(): void { factory(); } } 323export interface Surface { readonly value: number; } 324export function read(): Surface { return null as unknown as Surface; } 325"# , 326Some ( "avril:fixture" ), 327) 328. unwrap (); 329let declaration = compiled. declaration . unwrap (); 330assert! ( !declaration. contains ( "ext:core/ops" ), "{declaration}" ); 331assert! ( !declaration. contains ( "./internal.js" ), "{declaration}" ); 332assert! ( !declaration. contains ( "State" ), "{declaration}" ); 333assert! ( !declaration. contains ( "Impl" ), "{declaration}" ); 334assert! ( 335 declaration. contains ( "export interface Surface" ), 336"{declaration}" 337); 338} 339 340# [ test ] 341fn side_effect_imports_do_not_reach_the_public_declaration () { 342let compiled =compile ( 343Path :: new ( "src/public.ts" ), 344"import 'ext:private/setup.js'; export function value(): number { return 42; }" , 345Some ( "avril:fixture" ), 346) 347. unwrap (); 348let declaration = compiled. declaration . unwrap (); 349assert! ( !declaration. contains ( "ext:private" ), "{declaration}" ); 350assert! ( 351 declaration. contains ( "export function value" ), 352"{declaration}" 353); 354}