2026-05-14 16:12:11 -05:00
//! JavaScript harness emitter.
2026-05-12 02:20:55 -04:00
//!
2026-05-14 16:12:11 -05:00
//! After Phase 13 (Track B JS + TS vertical) the per-shape dispatch lives in
//! [`crate::dynamic::lang::js_shared`]. This module is the typed surface for
//! `Lang::JavaScript`: registers the [`JavaScriptEmitter`] in the dispatch
//! table, advertises the supported [`EntryKind`] set, and forwards
//! `emit` / `materialize_runtime` calls to the shared module.
2026-05-12 02:20:55 -04:00
//!
2026-05-14 16:12:11 -05:00
//! Payload slot support (handled by `js_shared::emit`):
//! - [`PayloadSlot::Param`] — n-th positional argument.
//! - [`PayloadSlot::EnvVar`] — set env var before calling.
//! - [`PayloadSlot::Stdin`] — pipe payload to `process.stdin`.
//! - [`PayloadSlot::QueryParam`] — HTTP-shaped query param (Express / Koa / Next).
//! - [`PayloadSlot::HttpBody`] — HTTP body (Express / Koa / Next).
//! - [`PayloadSlot::Argv`] — coerced to positional `Param(0)` by build_call.
2026-05-12 02:20:55 -04:00
2026-05-14 13:40:47 -05:00
use crate ::dynamic ::environment ::{ Environment , RuntimeArtifacts } ;
2026-05-17 06:20:10 -05:00
use crate ::dynamic ::lang ::{ js_shared , ChainStepHarness , ChainStepTerminal , HarnessSource , LangEmitter } ;
2026-05-14 16:12:11 -05:00
use crate ::dynamic ::spec ::{ EntryKind , HarnessSpec } ;
2026-05-12 02:20:55 -04:00
use crate ::evidence ::UnsupportedReason ;
2026-05-14 16:12:11 -05:00
pub use js_shared ::{ detect_shape , materialize_node , probe_shim , JsShape } ;
2026-05-14 03:22:30 -05:00
2026-05-14 16:12:11 -05:00
/// Zero-sized [`LangEmitter`] handle for JavaScript.
pub struct JavaScriptEmitter ;
2026-05-14 03:22:30 -05:00
impl LangEmitter for JavaScriptEmitter {
fn emit ( & self , spec : & HarnessSpec ) -> Result < HarnessSource , UnsupportedReason > {
emit ( spec )
}
fn entry_kinds_supported ( & self ) -> & 'static [ EntryKind ] {
2026-05-14 16:12:11 -05:00
js_shared ::SUPPORTED
2026-05-14 03:22:30 -05:00
}
fn entry_kind_hint ( & self , attempted : EntryKind ) -> String {
format! (
2026-05-14 16:12:11 -05:00
" javascript emitter supports {supported:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 13 shape dispatch in `js_shared` " ,
supported = js_shared ::SUPPORTED ,
2026-05-14 03:22:30 -05:00
)
}
2026-05-14 13:40:47 -05:00
fn materialize_runtime ( & self , env : & Environment ) -> RuntimeArtifacts {
materialize_node ( env )
}
2026-05-15 17:22:46 -05:00
2026-05-17 06:20:10 -05:00
fn compose_chain_step (
& self ,
prev_output : Option < & [ u8 ] > ,
terminal : Option < & ChainStepTerminal > ,
) -> ChainStepHarness {
js_shared ::chain_step ( prev_output , /* typescript = */ false , terminal )
2026-05-15 17:22:46 -05:00
}
2026-05-14 13:40:47 -05:00
}
2026-05-14 16:12:11 -05:00
/// Emit a JS harness for `spec`.
2026-05-12 02:20:55 -04:00
pub fn emit ( spec : & HarnessSpec ) -> Result < HarnessSource , UnsupportedReason > {
2026-05-14 16:12:11 -05:00
js_shared ::emit ( spec , false )
2026-05-12 02:20:55 -04:00
}
#[ cfg(test) ]
mod tests {
use super ::* ;
use crate ::dynamic ::spec ::{ EntryKind , HarnessSpec , PayloadSlot } ;
use crate ::labels ::Cap ;
use crate ::symbol ::Lang ;
fn make_spec ( payload_slot : PayloadSlot ) -> HarnessSpec {
HarnessSpec {
finding_id : " js000000000001 " . into ( ) ,
entry_file : " src/app.js " . into ( ) ,
entry_name : " login " . into ( ) ,
entry_kind : EntryKind ::Function ,
lang : Lang ::JavaScript ,
toolchain_id : " node-20 " . into ( ) ,
payload_slot ,
expected_cap : Cap ::SQL_QUERY ,
constraint_hints : vec ! [ ] ,
sink_file : " src/app.js " . into ( ) ,
sink_line : 15 ,
spec_hash : " js000000000001 " . into ( ) ,
2026-05-13 13:03:44 -04:00
derivation : crate ::dynamic ::spec ::SpecDerivationStrategy ::FromFlowSteps ,
2026-05-14 14:18:09 -05:00
stubs_required : vec ! [ ] ,
2026-05-17 14:29:14 -05:00
framework : None ,
2026-05-12 02:20:55 -04:00
}
}
#[ test ]
fn emit_produces_source ( ) {
let spec = make_spec ( PayloadSlot ::Param ( 0 ) ) ;
let harness = emit ( & spec ) . unwrap ( ) ;
assert! ( harness . source . contains ( " NYX_PAYLOAD " ) ) ;
assert! ( harness . source . contains ( " require " ) ) ;
assert! ( harness . source . contains ( " login " ) ) ;
assert_eq! ( harness . filename , " harness.js " ) ;
assert_eq! ( harness . command , vec! [ " node " , " harness.js " ] ) ;
}
#[ test ]
fn emit_param_index_0 ( ) {
let spec = make_spec ( PayloadSlot ::Param ( 0 ) ) ;
let harness = emit ( & spec ) . unwrap ( ) ;
assert! ( harness . source . contains ( " _entry.login(payload) " ) ) ;
}
#[ test ]
fn emit_param_index_1 ( ) {
let spec = make_spec ( PayloadSlot ::Param ( 1 ) ) ;
let harness = emit ( & spec ) . unwrap ( ) ;
assert! ( harness . source . contains ( " _entry.login('', payload) " ) ) ;
}
#[ test ]
fn emit_env_var_slot ( ) {
let spec = make_spec ( PayloadSlot ::EnvVar ( " DB_HOST " . into ( ) ) ) ;
let harness = emit ( & spec ) . unwrap ( ) ;
assert! ( harness . source . contains ( " process.env[ \" DB_HOST \" ] = payload " ) ) ;
}
#[ test ]
fn emit_stdin_slot ( ) {
let spec = make_spec ( PayloadSlot ::Stdin ) ;
let harness = emit ( & spec ) . unwrap ( ) ;
assert! ( harness . source . contains ( " Readable " ) ) ;
assert! ( harness . source . contains ( " process.stdin " ) ) ;
}
#[ test ]
2026-05-14 16:12:11 -05:00
fn emit_http_body_now_supported_for_express_shape ( ) {
let mut spec = make_spec ( PayloadSlot ::HttpBody ) ;
spec . entry_kind = EntryKind ::HttpRoute ;
let h = emit ( & spec ) . unwrap ( ) ;
assert_eq! ( h . filename , " harness.js " ) ;
2026-05-12 02:20:55 -04:00
}
#[ test ]
2026-05-14 16:12:11 -05:00
fn emit_entry_subpath_default_is_entry_js ( ) {
2026-05-12 02:20:55 -04:00
let spec = make_spec ( PayloadSlot ::Param ( 0 ) ) ;
let harness = emit ( & spec ) . unwrap ( ) ;
assert_eq! ( harness . entry_subpath , Some ( " entry.js " . to_owned ( ) ) ) ;
}
2026-05-14 03:22:30 -05:00
#[ test ]
2026-05-14 16:12:11 -05:00
fn entry_kinds_supported_includes_http_and_cli_after_phase_13 ( ) {
let kinds = JavaScriptEmitter . entry_kinds_supported ( ) ;
assert! ( kinds . contains ( & EntryKind ::Function ) ) ;
assert! ( kinds . contains ( & EntryKind ::HttpRoute ) ) ;
assert! ( kinds . contains ( & EntryKind ::CliSubcommand ) ) ;
2026-05-14 03:22:30 -05:00
}
#[ test ]
fn entry_kind_hint_names_attempted_and_phase ( ) {
let hint = JavaScriptEmitter . entry_kind_hint ( EntryKind ::HttpRoute ) ;
assert! ( hint . contains ( " HttpRoute " ) ) ;
2026-05-14 16:12:11 -05:00
assert! ( hint . contains ( " Phase 13 " ) ) ;
2026-05-14 03:22:30 -05:00
}
2026-05-12 02:20:55 -04:00
}