mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
158 lines
5.6 KiB
Rust
158 lines
5.6 KiB
Rust
//! JavaScript harness emitter.
|
|
//!
|
|
//! 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.
|
|
//!
|
|
//! 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.
|
|
|
|
use crate::dynamic::environment::{Environment, RuntimeArtifacts};
|
|
use crate::dynamic::lang::{js_shared, ChainStepHarness, ChainStepTerminal, HarnessSource, LangEmitter};
|
|
use crate::dynamic::spec::{EntryKind, HarnessSpec};
|
|
use crate::evidence::UnsupportedReason;
|
|
|
|
pub use js_shared::{detect_shape, materialize_node, probe_shim, JsShape};
|
|
|
|
/// Zero-sized [`LangEmitter`] handle for JavaScript.
|
|
pub struct JavaScriptEmitter;
|
|
|
|
impl LangEmitter for JavaScriptEmitter {
|
|
fn emit(&self, spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|
emit(spec)
|
|
}
|
|
|
|
fn entry_kinds_supported(&self) -> &'static [EntryKind] {
|
|
js_shared::SUPPORTED
|
|
}
|
|
|
|
fn entry_kind_hint(&self, attempted: EntryKind) -> String {
|
|
format!(
|
|
"javascript emitter supports {supported:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 13 shape dispatch in `js_shared`",
|
|
supported = js_shared::SUPPORTED,
|
|
)
|
|
}
|
|
|
|
fn materialize_runtime(&self, env: &Environment) -> RuntimeArtifacts {
|
|
materialize_node(env)
|
|
}
|
|
|
|
fn compose_chain_step(
|
|
&self,
|
|
prev_output: Option<&[u8]>,
|
|
terminal: Option<&ChainStepTerminal>,
|
|
) -> ChainStepHarness {
|
|
js_shared::chain_step(prev_output, /* typescript = */ false, terminal)
|
|
}
|
|
}
|
|
|
|
/// Emit a JS harness for `spec`.
|
|
pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|
js_shared::emit(spec, false)
|
|
}
|
|
|
|
#[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(),
|
|
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
|
|
stubs_required: vec![],
|
|
framework: None,
|
|
}
|
|
}
|
|
|
|
#[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]
|
|
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");
|
|
}
|
|
|
|
#[test]
|
|
fn emit_entry_subpath_default_is_entry_js() {
|
|
let spec = make_spec(PayloadSlot::Param(0));
|
|
let harness = emit(&spec).unwrap();
|
|
assert_eq!(harness.entry_subpath, Some("entry.js".to_owned()));
|
|
}
|
|
|
|
#[test]
|
|
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));
|
|
}
|
|
|
|
#[test]
|
|
fn entry_kind_hint_names_attempted_and_phase() {
|
|
let hint = JavaScriptEmitter.entry_kind_hint(EntryKind::HttpRoute);
|
|
assert!(hint.contains("HttpRoute"));
|
|
assert!(hint.contains("Phase 13"));
|
|
}
|
|
|
|
}
|