feat: Wasm module support (#26668)

Support for Wasm modules.

Note this implements the standard where the default export is the
instance (not the module). The module will come later with source phase
imports.

```ts
import { add } from "./math.wasm";

console.log(add(1, 2));
```
This commit is contained in:
David Sherret 2024-11-19 18:59:23 -05:00 committed by GitHub
parent 6b478cd0a3
commit 8be2bbf074
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
60 changed files with 401 additions and 46 deletions

14
Cargo.lock generated
View File

@ -1466,9 +1466,9 @@ dependencies = [
[[package]]
name = "deno_core"
version = "0.320.0"
version = "0.321.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f285eed7b072749f9c3a9c4cf2c9ebb06462a2c22afec94892a6684c38f32696"
checksum = "cd2a54cda74cdc187d5fc2d23370a45cf09f912caf566dd1cd24a50157d809c7"
dependencies = [
"anyhow",
"bincode",
@ -1480,6 +1480,7 @@ dependencies = [
"deno_ops",
"deno_unsync",
"futures",
"indexmap 2.3.0",
"libc",
"memoffset",
"parking_lot",
@ -1494,6 +1495,7 @@ dependencies = [
"tokio",
"url",
"v8",
"wasm_dep_analyzer",
]
[[package]]
@ -1981,9 +1983,9 @@ dependencies = [
[[package]]
name = "deno_ops"
version = "0.196.0"
version = "0.197.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d35c75ae05062f37ec2ae5fd1d99b2dcdfa0aef70844d3706759b8775056c5f6"
checksum = "37a8825d92301cf445727c43f17fee2a20fcdf4370004339965156ae7c56c97e"
dependencies = [
"proc-macro-rules",
"proc-macro2",
@ -6517,9 +6519,9 @@ dependencies = [
[[package]]
name = "serde_v8"
version = "0.229.0"
version = "0.230.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e1dbbda82d67a393ea96f75d8383bc41fcd0bba43164aeaab599e1c2c2d46d7"
checksum = "b5a783242d2af51d6955cc04bf2b64adb643ab588b61e9573c908a69dabf8c2f"
dependencies = [
"num-bigint",
"serde",

View File

@ -46,7 +46,7 @@ repository = "https://github.com/denoland/deno"
[workspace.dependencies]
deno_ast = { version = "=0.43.3", features = ["transpiling"] }
deno_core = { version = "0.320.0" }
deno_core = { version = "0.321.0" }
deno_bench_util = { version = "0.171.0", path = "./bench_util" }
deno_config = { version = "=0.39.1", features = ["workspace", "sync"] }

View File

@ -1548,6 +1548,10 @@ impl CliOptions {
}) => Url::parse(&flags.module_url)
.ok()
.map(|url| vec![Cow::Owned(url)]),
DenoSubcommand::Doc(DocFlags {
source_files: DocSourceFileFlag::Paths(paths),
..
}) => Some(files_to_urls(paths)),
_ => None,
})
.unwrap_or_default();

View File

@ -38,6 +38,7 @@ fn get_module_graph_error_class(err: &ModuleGraphError) -> &'static str {
ModuleGraphError::ModuleError(err) => match err {
ModuleError::InvalidTypeAssertion { .. } => "SyntaxError",
ModuleError::ParseErr(_, diagnostic) => get_diagnostic_class(diagnostic),
ModuleError::WasmParseErr(..) => "SyntaxError",
ModuleError::UnsupportedMediaType { .. }
| ModuleError::UnsupportedImportAttributeType { .. } => "TypeError",
ModuleError::Missing(_, _) | ModuleError::MissingDynamic(_, _) => {
@ -71,7 +72,6 @@ fn get_module_graph_error_class(err: &ModuleGraphError) -> &'static str {
| JsrLoadError::UnknownExport { .. } => "NotFound",
},
},
ModuleError::WasmParseErr(_, _) => "SyntaxError",
},
}
}

View File

@ -883,8 +883,13 @@ impl FileSystemDocuments {
let doc = if specifier.scheme() == "file" {
let path = url_to_file_path(specifier).ok()?;
let bytes = fs::read(path).ok()?;
let content =
deno_graph::source::decode_owned_source(specifier, bytes, None).ok()?;
let content = bytes_to_content(
specifier,
MediaType::from_specifier(specifier),
bytes,
None,
)
.ok()?;
Document::new(
specifier.clone(),
content.into(),
@ -923,19 +928,24 @@ impl FileSystemDocuments {
specifier,
Some(&cached_file.metadata.headers),
);
let content = deno_graph::source::decode_owned_source(
let media_type = resolve_media_type(
specifier,
Some(&cached_file.metadata.headers),
None,
);
let content = bytes_to_content(
specifier,
media_type,
cached_file.content,
maybe_charset,
)
.ok()?;
let maybe_headers = Some(cached_file.metadata.headers);
Document::new(
specifier.clone(),
content.into(),
None,
None,
maybe_headers,
Some(cached_file.metadata.headers),
is_cjs_resolver,
resolver.clone(),
config.clone(),
@ -1706,6 +1716,24 @@ fn analyze_module(
}
}
fn bytes_to_content(
specifier: &ModuleSpecifier,
media_type: MediaType,
bytes: Vec<u8>,
maybe_charset: Option<&str>,
) -> Result<String, AnyError> {
if media_type == MediaType::Wasm {
// we use the dts representation for Wasm modules
Ok(deno_graph::source::wasm::wasm_module_to_dts(&bytes)?)
} else {
Ok(deno_graph::source::decode_owned_source(
specifier,
bytes,
maybe_charset,
)?)
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -5609,7 +5609,7 @@ mod tests {
let (_tx, rx) = mpsc::unbounded_channel();
let state =
State::new(state_snapshot, Default::default(), Default::default(), rx);
let mut op_state = OpState::new(None);
let mut op_state = OpState::new(None, None);
op_state.put(state);
op_state
}

View File

@ -66,6 +66,7 @@ use deno_graph::JsonModule;
use deno_graph::Module;
use deno_graph::ModuleGraph;
use deno_graph::Resolution;
use deno_graph::WasmModule;
use deno_runtime::code_cache;
use deno_runtime::deno_fs::FileSystem;
use deno_runtime::deno_node::create_host_defined_options;
@ -368,7 +369,9 @@ impl<TGraphContainer: ModuleGraphContainer>
requested_module_type: RequestedModuleType,
) -> Result<ModuleSource, AnyError> {
let code_source = self.load_code_source(specifier, maybe_referrer).await?;
let code = if self.shared.is_inspecting {
let code = if self.shared.is_inspecting
|| code_source.media_type == MediaType::Wasm
{
// we need the code with the source map in order for
// it to work with --inspect or --inspect-brk
code_source.code
@ -378,6 +381,7 @@ impl<TGraphContainer: ModuleGraphContainer>
};
let module_type = match code_source.media_type {
MediaType::Json => ModuleType::Json,
MediaType::Wasm => ModuleType::Wasm,
_ => ModuleType::JavaScript,
};
@ -545,6 +549,7 @@ impl<TGraphContainer: ModuleGraphContainer>
Some(Module::Node(module)) => module.specifier.clone(),
Some(Module::Js(module)) => module.specifier.clone(),
Some(Module::Json(module)) => module.specifier.clone(),
Some(Module::Wasm(module)) => module.specifier.clone(),
Some(Module::External(module)) => {
node::resolve_specifier_into_node_modules(
&module.specifier,
@ -552,7 +557,6 @@ impl<TGraphContainer: ModuleGraphContainer>
)
}
None => specifier.into_owned(),
Some(Module::Wasm(_)) => todo!("@dsherret"),
};
Ok(specifier)
}
@ -717,13 +721,19 @@ impl<TGraphContainer: ModuleGraphContainer>
media_type: *media_type,
})))
}
Some(deno_graph::Module::Wasm(WasmModule {
source, specifier, ..
})) => Ok(Some(CodeOrDeferredEmit::Code(ModuleCodeStringSource {
code: ModuleSourceCode::Bytes(source.clone().into()),
found_url: specifier.clone(),
media_type: MediaType::Wasm,
}))),
Some(
deno_graph::Module::External(_)
| deno_graph::Module::Node(_)
| deno_graph::Module::Npm(_),
)
| None => Ok(None),
Some(deno_graph::Module::Wasm(_)) => todo!("@dsherret"),
}
}

View File

@ -675,10 +675,12 @@ impl<'a> DenoCompileBinaryWriter<'a> {
deno_graph::Module::Json(m) => {
(Some(m.source.as_bytes().to_vec()), m.media_type)
}
deno_graph::Module::Wasm(m) => {
(Some(m.source.to_vec()), MediaType::Wasm)
}
deno_graph::Module::Npm(_)
| deno_graph::Module::Node(_)
| deno_graph::Module::External(_) => (None, MediaType::Unknown),
deno_graph::Module::Wasm(_) => todo!("@dsherret"),
};
if module.specifier().scheme() == "file" {
let file_path = deno_path_util::url_to_file_path(module.specifier())?;

View File

@ -380,10 +380,14 @@ fn get_check_hash(
hasher.write_str(module.specifier.as_str());
hasher.write_str(&module.source);
}
Module::Wasm(module) => {
has_file_to_type_check = true;
hasher.write_str(module.specifier.as_str());
hasher.write_str(&module.source_dts);
}
Module::External(module) => {
hasher.write_str(module.specifier.as_str());
}
Module::Wasm(_) => todo!("@dsherret"),
}
}
@ -438,11 +442,11 @@ fn get_tsc_roots(
| MediaType::SourceMap
| MediaType::Unknown => None,
},
Module::Wasm(module) => Some((module.specifier.clone(), MediaType::Dmts)),
Module::External(_)
| Module::Node(_)
| Module::Npm(_)
| Module::Json(_) => None,
Module::Wasm(_) => todo!("@dsherret"),
}
}

View File

@ -446,8 +446,8 @@ impl<'a> GraphDisplayContext<'a> {
let maybe_cache_info = match root {
Module::Js(module) => module.maybe_cache_info.as_ref(),
Module::Json(module) => module.maybe_cache_info.as_ref(),
Module::Wasm(module) => module.maybe_cache_info.as_ref(),
Module::Node(_) | Module::Npm(_) | Module::External(_) => None,
Module::Wasm(_) => todo!("@dsherret"),
};
if let Some(cache_info) = maybe_cache_info {
if let Some(local) = &cache_info.local {
@ -469,8 +469,8 @@ impl<'a> GraphDisplayContext<'a> {
let size = match m {
Module::Js(module) => module.size(),
Module::Json(module) => module.size(),
Module::Wasm(module) => module.size(),
Module::Node(_) | Module::Npm(_) | Module::External(_) => 0,
Module::Wasm(_) => todo!("@dsherret"),
};
size as f64
})
@ -569,8 +569,8 @@ impl<'a> GraphDisplayContext<'a> {
Specifier(_) => match module {
Module::Js(module) => Some(module.size() as u64),
Module::Json(module) => Some(module.size() as u64),
Module::Wasm(module) => Some(module.size() as u64),
Module::Node(_) | Module::Npm(_) | Module::External(_) => None,
Module::Wasm(_) => todo!("@dsherret"),
},
};
format!("{} {}", header_text, maybe_size_to_text(maybe_size))
@ -583,8 +583,8 @@ impl<'a> GraphDisplayContext<'a> {
Package(package) => {
tree_node.children.extend(self.build_npm_deps(package));
}
Specifier(_) => {
if let Some(module) = module.js() {
Specifier(_) => match module {
Module::Js(module) => {
if let Some(types_dep) = &module.maybe_types_dependency {
if let Some(child) =
self.build_resolved_info(&types_dep.dependency, true)
@ -596,7 +596,16 @@ impl<'a> GraphDisplayContext<'a> {
tree_node.children.extend(self.build_dep_info(dep));
}
}
}
Module::Wasm(module) => {
for dep in module.dependencies.values() {
tree_node.children.extend(self.build_dep_info(dep));
}
}
Module::Json(_)
| Module::Npm(_)
| Module::Node(_)
| Module::External(_) => {}
},
}
}
tree_node
@ -661,7 +670,7 @@ impl<'a> GraphDisplayContext<'a> {
};
self.build_error_msg(specifier, message.as_ref())
}
ModuleError::ParseErr(_, _) => {
ModuleError::ParseErr(_, _) | ModuleError::WasmParseErr(_, _) => {
self.build_error_msg(specifier, "(parsing error)")
}
ModuleError::UnsupportedImportAttributeType { .. } => {
@ -673,7 +682,6 @@ impl<'a> GraphDisplayContext<'a> {
ModuleError::Missing(_, _) | ModuleError::MissingDynamic(_, _) => {
self.build_error_msg(specifier, "(missing)")
}
ModuleError::WasmParseErr(_, _) => todo!("@dsherret"),
}
}

View File

@ -26,7 +26,6 @@ use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
use deno_core::url::Url;
use deno_graph::Module;
use deno_terminal::colors;
use http_body_util::BodyExt;
use serde::Deserialize;
@ -1108,13 +1107,12 @@ fn collect_excluded_module_diagnostics(
let graph_specifiers = graph
.modules()
.filter_map(|m| match m {
deno_graph::Module::Js(_) | deno_graph::Module::Json(_) => {
Some(m.specifier())
}
deno_graph::Module::Js(_)
| deno_graph::Module::Json(_)
| deno_graph::Module::Wasm(_) => Some(m.specifier()),
deno_graph::Module::Npm(_)
| deno_graph::Module::Node(_)
| deno_graph::Module::External(_) => None,
Module::Wasm(_) => todo!("@dsherret"),
})
.filter(|s| s.as_str().starts_with(root.as_str()));
for specifier in graph_specifiers {

View File

@ -450,6 +450,12 @@ delete Object.prototype.__proto__;
// We specify the resolution mode to be CommonJS for some npm files and this
// diagnostic gets generated even though we're using custom module resolution.
1452,
// Module '...' cannot be imported using this construct. The specifier only resolves to an
// ES module, which cannot be imported with 'require'.
1471,
// TS1479: The current file is a CommonJS module whose imports will produce 'require' calls;
// however, the referenced file is an ECMAScript module and cannot be imported with 'require'.
1479,
// TS2306: File '.../index.d.ts' is not a module.
// We get this for `x-typescript-types` declaration files which don't export
// anything. We prefer to treat these as modules with no exports.

View File

@ -650,6 +650,10 @@ fn op_load_inner(
media_type = MediaType::Json;
Some(Cow::Borrowed(&*module.source))
}
Module::Wasm(module) => {
media_type = MediaType::Dts;
Some(Cow::Borrowed(&*module.source_dts))
}
Module::Npm(_) | Module::Node(_) => None,
Module::External(module) => {
// means it's Deno code importing an npm module
@ -664,7 +668,6 @@ fn op_load_inner(
&mut is_cjs,
)?))
}
Module::Wasm(_) => todo!("@dsherret"),
}
} else if let Some(npm) = state
.maybe_npm
@ -890,6 +893,9 @@ fn resolve_graph_specifier_types(
Some(Module::Json(module)) => {
Ok(Some((module.specifier.clone(), module.media_type)))
}
Some(Module::Wasm(module)) => {
Ok(Some((module.specifier.clone(), MediaType::Dmts)))
}
Some(Module::Npm(module)) => {
if let Some(npm) = &state.maybe_npm.as_ref() {
let package_folder = npm
@ -929,7 +935,6 @@ fn resolve_graph_specifier_types(
}))
}
Some(Module::Node(_)) | None => Ok(None),
Some(Module::Wasm(_)) => todo!("@dsherret"),
}
}
@ -1198,7 +1203,7 @@ mod tests {
.context("Unable to get CWD")
.unwrap(),
);
let mut op_state = OpState::new(None);
let mut op_state = OpState::new(None, None);
op_state.put(state);
op_state
}

View File

@ -1086,12 +1086,10 @@ function loadESMFromCJS(module, filename, code) {
module.exports = namespace;
}
Module._extensions[".mjs"] = Module._extensions[".mts"] = function (
module,
filename,
) {
loadESMFromCJS(module, filename);
};
Module._extensions[".mjs"] =
Module._extensions[".mts"] =
Module._extensions[".wasm"] =
loadESMFromCJS;
function stripBOM(content) {
if (StringPrototypeCharCodeAt(content, 0) === 0xfeff) {

View File

@ -78,7 +78,7 @@ pub fn validate_import_attributes_callback(
for (key, value) in attributes {
let msg = if key != "type" {
Some(format!("\"{key}\" attribute is not supported."))
} else if value != "json" {
} else if value != "json" && value != "$$deno-core-internal-wasm-module" {
Some(format!("\"{value}\" is not a valid module type."))
} else {
None

View File

@ -5427,7 +5427,8 @@ fn lsp_code_actions_deno_cache() {
let res =
client
.write_request( "textDocument/codeAction",
.write_request(
"textDocument/codeAction",
json!({
"textDocument": {
"uri": "file:///a/file.ts"
@ -5453,8 +5454,7 @@ fn lsp_code_actions_deno_cache() {
"only": ["quickfix"]
}
}),
)
;
);
assert_eq!(
res,
json!([{
@ -16516,3 +16516,47 @@ fn lsp_jsdoc_named_example() {
}),
);
}
#[test]
fn lsp_wasm_module() {
let context = TestContextBuilder::new()
.use_temp_cwd()
.use_http_server()
.build();
let mut client = context.new_lsp_command().build();
client.initialize_default();
client.did_open(json!({
"textDocument": {
"uri": "file:///a/file.ts",
"languageId": "typescript",
"version": 1,
"text": "import { add } from \"http://localhost:4545/wasm/math.wasm\";\nadd(1, '');\n"
}
}));
client.write_request(
"workspace/executeCommand",
json!({
"command": "deno.cache",
"arguments": [[], "file:///a/file.ts"],
}),
);
let diagnostics = client.read_diagnostics();
assert_eq!(
json!(diagnostics.all()),
json!([
{
"range": {
"start": { "line": 1, "character": 7 },
"end": { "line": 1, "character": 9 }
},
"severity": 1,
"code": 2345,
"source": "deno-ts",
"message": "Argument of type 'string' is not assignable to parameter of type 'number'."
}
])
);
client.shutdown();
}

View File

@ -0,0 +1,5 @@
{
"args": "check --allow-import main.ts",
"output": "check.out",
"exitCode": 1
}

View File

@ -0,0 +1,6 @@
Download http://localhost:4545/wasm/math.wasm
Check file:///[WILDLINE]/main.ts
error: TS2345 [ERROR]: Argument of type 'string' is not assignable to parameter of type 'number'.
console.log(add(1, ""));
~~
at file:///[WILDLINE]/main.ts:3:20

View File

@ -0,0 +1,3 @@
import { add } from "http://localhost:4545/wasm/math.wasm";
console.log(add(1, ""));

View File

@ -0,0 +1,4 @@
{
"args": "doc http://localhost:4545/wasm/math.wasm",
"output": "doc.out"
}

View File

@ -0,0 +1,21 @@
Download http://localhost:4545/wasm/math.wasm
Defined in http://localhost:4545/wasm/math.wasm:2:1
function add(arg0: number, arg1: number): number
Defined in http://localhost:4545/wasm/math.wasm:3:1
function subtract(arg0: number, arg1: number): number
Defined in http://localhost:4545/wasm/math.wasm:4:22
const __data_end: number
Defined in http://localhost:4545/wasm/math.wasm:5:22
const __heap_base: number
Defined in http://localhost:4545/wasm/math.wasm:1:22
const memory: WebAssembly.Memory

View File

@ -0,0 +1,4 @@
{
"args": "info --allow-import main.js",
"output": "main.out"
}

View File

@ -0,0 +1,7 @@
import {
add,
subtract,
} from "http://localhost:4545/wasm/math_with_import.wasm";
console.log(add(1, 2));
console.log(subtract(100, 50));

View File

@ -0,0 +1,10 @@
Download http://localhost:4545/wasm/math_with_import.wasm
Download http://localhost:4545/wasm/math.ts
local: [WILDLINE]main.js
type: JavaScript
dependencies: 2 unique
size: [WILDLINE]
file:///[WILDLINE]/main.js ([WILDLINE])
└─┬ http://localhost:4545/wasm/math_with_import.wasm ([WILDLINE])
└── http://localhost:4545/wasm/math.ts ([WILDLINE])

View File

@ -0,0 +1,12 @@
{
"steps": [{
"args": "run -A setup.ts",
"output": "[WILDCARD]"
}, {
"args": "run -A --check main.cts",
"output": "main.out"
}, {
"args": "run -A --check main.cjs",
"output": "main.out"
}]
}

View File

@ -0,0 +1,5 @@
// @ts-check
const { add, subtract } = require("./math.wasm");
console.log(add(1, 2));
console.log(subtract(9, 3));

View File

@ -0,0 +1,4 @@
import WasmModule = require("./math.wasm");
console.log(WasmModule.add(1, 2));
console.log(WasmModule.subtract(9, 3));

View File

@ -0,0 +1,3 @@
Check file:///[WILDLINE]
3
6

Binary file not shown.

View File

@ -0,0 +1,4 @@
{
"args": "run -A main.cjs",
"output": "main.out"
}

View File

@ -0,0 +1,7 @@
fetch("http://localhost:4545/wasm/math.wasm").then(async (response) => {
if (!response.ok) {
throw new Error(`Failed to fetch WASM module: ${response.statusText}`);
}
using file = Deno.openSync("math.wasm", { write: true, create: true });
await response.body!.pipeTo(file.writable);
});

View File

@ -0,0 +1,5 @@
{
"args": "--allow-import main.js",
"output": "main.out",
"exitCode": 1
}

View File

@ -0,0 +1,9 @@
{
"lock": false,
"scopes": {
"http://localhost:4545/wasm/": {
// file won't exist
"http://localhost:4545/wasm/math.ts": "./local_math.ts"
}
}
}

View File

@ -0,0 +1,7 @@
import {
add,
subtract,
} from "http://localhost:4545/wasm/math_with_import.wasm";
console.log(add(1, 2));
console.log(subtract(100, 50));

View File

@ -0,0 +1,3 @@
Download http://localhost:4545/wasm/math_with_import.wasm
error: Module not found "file:///[WILDLINE]/local_math.ts".
at http://localhost:4545/wasm/math_with_import.wasm:1:8

View File

@ -0,0 +1,5 @@
{
"args": "--allow-import main.js",
"output": "main.out",
"exitCode": 1
}

View File

@ -0,0 +1,8 @@
{
"lock": false,
"scopes": {
"http://localhost:4545/wasm/": {
"http://localhost:4545/wasm/math.ts": "./local_math.ts"
}
}
}

View File

@ -0,0 +1,7 @@
export function addTest(a: number, b: number) {
return (a + b) * 2;
}
export function subtractTest(a: number, b: number) {
return (a - b) / 2;
}

View File

@ -0,0 +1,7 @@
import {
add,
subtract,
} from "http://localhost:4545/wasm/math_with_import.wasm";
console.log(add(1, 2));
console.log(subtract(100, 50));

View File

@ -0,0 +1,3 @@
Download http://localhost:4545/wasm/math_with_import.wasm
error: Uncaught SyntaxError: The requested module './math.ts' does not provide an export named 'add'
at <anonymous> (http://localhost:4545/wasm/math_with_import.wasm:[WILDLINE])

View File

@ -0,0 +1,4 @@
{
"args": "--allow-import main.js",
"output": "main.out"
}

View File

@ -0,0 +1,7 @@
import {
add,
subtract,
} from "http://localhost:4545/wasm/math_with_import.wasm";
console.log(add(1, 2));
console.log(subtract(100, 50));

View File

@ -0,0 +1,4 @@
Download http://localhost:4545/wasm/math_with_import.wasm
Download http://localhost:4545/wasm/math.ts
3
50

View File

@ -0,0 +1,5 @@
{
"args": "--allow-import main.js",
"output": "main.out",
"exitCode": 10
}

View File

@ -0,0 +1,2 @@
{
}

View File

@ -0,0 +1,6 @@
{
"version": "4",
"remote": {
"http://localhost:4545/wasm/math.wasm": "c4fdd49432f1517835b93274447890007947f9d30674ab7f1474091860113d95"
}
}

View File

@ -0,0 +1,4 @@
import { add, subtract } from "http://localhost:4545/wasm/math.wasm";
console.log(add(1, 2));
console.log(subtract(100, 50));

View File

@ -0,0 +1,12 @@
Download http://localhost:4545/wasm/math.wasm
error: Integrity check failed for remote specifier. The source code is invalid, as it does not match the expected hash in the lock file.
Specifier: http://localhost:4545/wasm/math.wasm
Actual: d1643d9d4ba8f34ee5198717860cbc629013179addba6d4e347b68eb721c73b4
Expected: c4fdd49432f1517835b93274447890007947f9d30674ab7f1474091860113d95
This could be caused by:
* the lock file may be corrupt
* the source itself may be corrupt
Investigate the lockfile; delete it to regenerate the lockfile or --reload to reload the source code from the server.

View File

@ -0,0 +1,4 @@
{
"args": "--allow-import main.js",
"output": "main.out"
}

View File

@ -0,0 +1,8 @@
{
"lock": false,
"scopes": {
"http://localhost:4545/wasm/": {
"http://localhost:4545/wasm/math.ts": "./local_math.ts"
}
}
}

View File

@ -0,0 +1,7 @@
export function add(a: number, b: number) {
return (a + b) * 2;
}
export function subtract(a: number, b: number) {
return (a - b) / 2;
}

View File

@ -0,0 +1,7 @@
import {
add,
subtract,
} from "http://localhost:4545/wasm/math_with_import.wasm";
console.log(add(1, 2));
console.log(subtract(100, 50));

View File

@ -0,0 +1,3 @@
Download http://localhost:4545/wasm/math_with_import.wasm
6
25

View File

@ -0,0 +1,4 @@
{
"args": "run --allow-import main.ts",
"output": "main.out"
}

View File

@ -0,0 +1,3 @@
Download http://localhost:4545/wasm/math.wasm
3
6

View File

@ -0,0 +1,4 @@
import { add, subtract } from "http://localhost:4545/wasm/math.wasm";
console.log(add(1, 2));
console.log(subtract(8, 2));

8
tests/testdata/wasm/math.ts vendored Normal file
View File

@ -0,0 +1,8 @@
// this file is imported by math_with_import.wasm
export function add(a: number, b: number) {
return a + b;
}
export function subtract(a: number, b: number) {
return a - b;
}

BIN
tests/testdata/wasm/math.wasm vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@ -919,6 +919,11 @@ pub fn wildcard_match_detailed(
if was_last_wildcard || was_last_wildline || current_text.is_empty() {
WildcardMatchResult::Success
} else if current_text == "\n" {
WildcardMatchResult::Fail(
"<matched everything>\n!!!! PROBLEM: Missing final newline at end of expected output !!!!"
.to_string(),
)
} else {
output_lines.push("==== HAD TEXT AT END OF FILE ====".to_string());
output_lines.push(colors::red(annotate_whitespace(current_text)));