diff --git a/README.md b/README.md index 40b072f..0e395e6 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,15 @@ initializes its WebAssembly module on first use. ```ts import { createEvaluator } from "nix-eval"; -const evaluator = await createEvaluator(); +const evaluator = await createEvaluator({ strict: true }); const result = await evaluator.eval("6 * 7"); console.log(result.output); // "42" ``` +Pass `{ strict: true }` to force the final value before rendering. This resolves +lazy values in attribute sets and lists instead of displaying ``. + Pass a location as the second argument to associate diagnostics with a source name. It defaults to `""`. diff --git a/demo/src/evaluator.ts b/demo/src/evaluator.ts index eb0f136..457b8c8 100644 --- a/demo/src/evaluator.ts +++ b/demo/src/evaluator.ts @@ -19,7 +19,7 @@ export interface EvalErrorEvent { export type NixEval = (source: string) => Promise; export async function createNixEvaluator(el: EventTarget): Promise { - const ev = await createEvaluator(); + const ev = await createEvaluator({ strict: true }); return async (source: string): Promise => { const start = performance.now(); diff --git a/demo/vite.config.ts b/demo/vite.config.ts new file mode 100644 index 0000000..43030f9 --- /dev/null +++ b/demo/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + optimizeDeps: { + exclude: ["nix-eval"], + }, +}); diff --git a/rust/src/lib.rs b/rust/src/lib.rs index da919cf..4d94a58 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -13,7 +13,7 @@ struct Output { ast: String, } -fn eval(source: &str, location: &str) -> Output { +fn eval(source: &str, location: &str, strict: bool) -> Output { let mut output = Output::default(); let mut builder = tvix_eval::Evaluation::builder_pure(); let source_map = builder.source_map().clone(); @@ -26,7 +26,14 @@ fn eval(source: &str, location: &str) -> Output { let mut runtime_observer = TracingObserver::new(&mut output.trace); builder.set_runtime_observer(Some(&mut runtime_observer)); - builder.build().evaluate(source, Some(location.into())) + builder + .mode(if strict { + tvix_eval::EvalMode::Strict + } else { + tvix_eval::EvalMode::Lazy + }) + .build() + .evaluate(source, Some(location.into())) }; if let Some(expr) = &result.expr { @@ -55,8 +62,8 @@ fn eval(source: &str, location: &str) -> Output { } #[wasm_bindgen(js_name = evaluate)] -pub fn eval_wasm(source: &str, location: &str) -> String { - let output = eval(source, location); +pub fn eval_wasm(source: &str, location: &str, strict: bool) -> String { + let output = eval(source, location, strict); serde_json::json!({ "errors": output.errors, "warnings": output.warnings, @@ -74,7 +81,7 @@ mod tests { #[test] fn evaluates_a_pure_expression() { - let result = eval("6 * 7", "/input.nix"); + let result = eval("6 * 7", "/input.nix", false); assert_eq!(result.output, "42"); assert!(result.warnings.is_empty()); @@ -86,7 +93,7 @@ mod tests { #[test] fn returns_parse_errors_as_data() { - let result = eval("let", "/input.nix"); + let result = eval("let", "/input.nix", false); assert!(result.output.is_empty()); assert!(result.warnings.is_empty()); @@ -97,8 +104,20 @@ mod tests { #[test] fn serializes_output_for_wasm() { let output: serde_json::Value = - serde_json::from_str(&eval_wasm("6 * 7", "/input.nix")).unwrap(); + serde_json::from_str(&eval_wasm("6 * 7", "/input.nix", false)).unwrap(); assert_eq!(output["output"], "42"); } + + #[test] + fn strict_evaluation_forces_nested_values() { + let source = "{ value = builtins.concatStringsSep \"\" [ \"hello\" \" world\" ]; }"; + + assert!(eval(source, "/input.nix", false).output.contains("")); + let strict_output = eval(source, "/input.nix", true).output; + assert!( + strict_output.contains("hello world"), + "unexpected strict output: {strict_output}" + ); + } } diff --git a/src/common.ts b/src/common.ts index d58d77f..d1f3388 100644 --- a/src/common.ts +++ b/src/common.ts @@ -7,6 +7,10 @@ export type Output = { ast: string; }; +export type EvaluatorOptions = { + strict?: boolean; +}; + export interface Evaluator { eval(source: string, location?: string): Promise; } diff --git a/src/index.ts b/src/index.ts index f110f12..bdcdbb5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ -export type { Evaluator, Output } from "#src/common.js"; +export type { Evaluator, EvaluatorOptions, Output } from "#src/common.js"; export { createEvaluator } from "#src/sync.js"; diff --git a/src/sync.test.ts b/src/sync.test.ts index 3f98976..1e5b7d7 100644 --- a/src/sync.test.ts +++ b/src/sync.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; vi.mock("#wasm/nix_eval.js", () => ({ default: vi.fn().mockResolvedValue(undefined), - evaluate: vi.fn((source: string, _location: string) => + evaluate: vi.fn((source: string, _location: string, _strict: boolean) => JSON.stringify({ errors: "", warnings: "", @@ -40,4 +40,14 @@ describe("createEvaluator", () => { expect(result.output).toBe("42"); expect(result.errors).toBe(""); }); + + it("passes the strict option to the WASM binding", async () => { + const mod = await import("#wasm/nix_eval.js"); + const evaluate = mod.evaluate as ReturnType; + const evaluator = await createEvaluator({ strict: true }); + + await evaluator.eval("6 * 7"); + + expect(evaluate).toHaveBeenLastCalledWith("6 * 7", "/input.nix", true); + }); }); diff --git a/src/sync.ts b/src/sync.ts index 0b3c36f..c7cbe45 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -1,10 +1,10 @@ import * as wasm from "#wasm/nix_eval.js"; import { parseOutput } from "#src/common.js"; -import type { Evaluator } from "#src/common.js"; +import type { Evaluator, EvaluatorOptions } from "#src/common.js"; let initialized: Promise | undefined; -export async function createEvaluator(): Promise { +export async function createEvaluator({ strict = false }: EvaluatorOptions = {}): Promise { initialized ??= Promise.resolve( typeof wasm.default === "function" ? wasm.default() : undefined, ).then(() => undefined); @@ -12,7 +12,7 @@ export async function createEvaluator(): Promise { return { async eval(source, location = "/input.nix") { - return parseOutput(wasm.evaluate(source, location)); + return parseOutput(wasm.evaluate(source, location, strict)); }, }; }