From 62850a4bb96d06587cf2be6e375050a38b4c7cdc Mon Sep 17 00:00:00 2001
From: Alex Crichton <alex@alexcrichton.com>
Date: Fri, 15 Mar 2019 08:04:25 -0700
Subject: [PATCH] Add a `raw_module` attribute to `#[wasm_bindgen]`

This allows subverting the checks and resolution performed by the
`module` attribute added as part of [RFC 6] and has been discussed in #1343.

Closes #1343

[RFC 6]: https://github.com/rustwasm/rfcs/pull/6
---
 crates/backend/src/ast.rs                     |  5 +++
 crates/backend/src/encode.rs                  |  3 ++
 crates/macro-support/src/parser.rs            | 37 +++++++++++--------
 guide/src/SUMMARY.md                          |  1 +
 .../attributes/on-js-imports/module.md        |  5 +++
 .../attributes/on-js-imports/raw_module.md    | 19 ++++++++++
 6 files changed, 54 insertions(+), 16 deletions(-)
 create mode 100644 guide/src/reference/attributes/on-js-imports/raw_module.md

diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs
index 4c317cea44fb..75c73860275c 100644
--- a/crates/backend/src/ast.rs
+++ b/crates/backend/src/ast.rs
@@ -79,6 +79,7 @@ pub struct Import {
 pub enum ImportModule {
     None,
     Named(String, Span),
+    RawNamed(String, Span),
     Inline(usize, Span),
 }
 
@@ -96,6 +97,10 @@ impl Hash for ImportModule {
                 2u8.hash(h);
                 idx.hash(h);
             }
+            ImportModule::RawNamed(name, _) => {
+                3u8.hash(h);
+                name.hash(h);
+            }
         }
     }
 }
diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs
index abc8c613762f..7a6c9df5770a 100644
--- a/crates/backend/src/encode.rs
+++ b/crates/backend/src/encode.rs
@@ -208,6 +208,9 @@ fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result<Import<
             ast::ImportModule::Named(m, span) => {
                 ImportModule::Named(intern.resolve_import_module(m, *span)?)
             }
+            ast::ImportModule::RawNamed(m, _span) => {
+                ImportModule::Named(intern.intern_str(m))
+            }
             ast::ImportModule::Inline(idx, _) => ImportModule::Inline(*idx as u32),
             ast::ImportModule::None => ImportModule::None,
         },
diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs
index 574af3c27edd..d94a468070ce 100644
--- a/crates/macro-support/src/parser.rs
+++ b/crates/macro-support/src/parser.rs
@@ -33,6 +33,7 @@ macro_rules! attrgen {
             (static_method_of, StaticMethodOf(Span, Ident)),
             (js_namespace, JsNamespace(Span, Ident)),
             (module, Module(Span, String, Span)),
+            (raw_module, RawModule(Span, String, Span)),
             (inline_js, InlineJs(Span, String, Span)),
             (getter, Getter(Span, Option<Ident>)),
             (setter, Setter(Span, Option<Ident>)),
@@ -1085,24 +1086,28 @@ impl MacroParse<BindgenAttrs> for syn::ItemForeignMod {
                 ));
             }
         }
-        let module = match opts.module() {
-            Some((name, span)) => {
-                if opts.inline_js().is_some() {
-                    let msg = "cannot specify both `module` and `inline_js`";
-                    errors.push(Diagnostic::span_error(span, msg));
-                }
-                ast::ImportModule::Named(name.to_string(), span)
+        let module = if let Some((name, span)) = opts.module() {
+            if opts.inline_js().is_some() {
+                let msg = "cannot specify both `module` and `inline_js`";
+                errors.push(Diagnostic::span_error(span, msg));
             }
-            None => {
-                match opts.inline_js() {
-                    Some((js, span)) => {
-                        let i = program.inline_js.len();
-                        program.inline_js.push(js.to_string());
-                        ast::ImportModule::Inline(i, span)
-                    }
-                    None => ast::ImportModule::None
-                }
+            if opts.raw_module().is_some() {
+                let msg = "cannot specify both `module` and `raw_module`";
+                errors.push(Diagnostic::span_error(span, msg));
             }
+            ast::ImportModule::Named(name.to_string(), span)
+        } else if let Some((name, span)) = opts.raw_module() {
+            if opts.inline_js().is_some() {
+                let msg = "cannot specify both `raw_module` and `inline_js`";
+                errors.push(Diagnostic::span_error(span, msg));
+            }
+            ast::ImportModule::RawNamed(name.to_string(), span)
+        } else if let Some((js, span)) = opts.inline_js() {
+            let i = program.inline_js.len();
+            program.inline_js.push(js.to_string());
+            ast::ImportModule::Inline(i, span)
+        } else {
+            ast::ImportModule::None
         };
         for item in self.items.into_iter() {
             if let Err(e) = item.macro_parse(program, module.clone()) {
diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md
index 077c50cbe3b2..530b1286b7e2 100644
--- a/guide/src/SUMMARY.md
+++ b/guide/src/SUMMARY.md
@@ -66,6 +66,7 @@
       - [`js_namespace`](./reference/attributes/on-js-imports/js_namespace.md)
       - [`method`](./reference/attributes/on-js-imports/method.md)
       - [`module = "blah"`](./reference/attributes/on-js-imports/module.md)
+      - [`raw_module = "blah"`](./reference/attributes/on-js-imports/raw_module.md)
       - [`static_method_of = Blah`](./reference/attributes/on-js-imports/static_method_of.md)
       - [`structural`](./reference/attributes/on-js-imports/structural.md)
       - [`variadic`](./reference/attributes/on-js-imports/variadic.md)
diff --git a/guide/src/reference/attributes/on-js-imports/module.md b/guide/src/reference/attributes/on-js-imports/module.md
index 4f6b4408e7c0..24643e42c731 100644
--- a/guide/src/reference/attributes/on-js-imports/module.md
+++ b/guide/src/reference/attributes/on-js-imports/module.md
@@ -31,3 +31,8 @@ generates JavaScript import glue like:
 ```js
 let illmatic = this.illmatic;
 ```
+
+Note that if the string specified with `module` starts with `./`, `../`, or `/`
+then it's interpreted as a path to a [local JS snippet](../../js-snippets.html).
+If this doesn't work for your use case you might be interested in the
+[`raw_module` attribute](raw_module.html)
diff --git a/guide/src/reference/attributes/on-js-imports/raw_module.md b/guide/src/reference/attributes/on-js-imports/raw_module.md
new file mode 100644
index 000000000000..1f8da2f3dfb2
--- /dev/null
+++ b/guide/src/reference/attributes/on-js-imports/raw_module.md
@@ -0,0 +1,19 @@
+# `raw_module = "blah"`
+
+This attribute performs exactly the same purpose as the [`module`
+attribute](module.html) on JS imports, but it does not attempt to interpret
+paths starting with `./`, `../`, or `/` as JS snippets. For example:
+
+```rust
+#[wasm_bindgen(raw_module = "./some/js/file.js")]
+extern "C" {
+    fn the_function();
+}
+```
+
+Note that if you use this attribute with a relative or absolute path, it's
+likely up to the final bundler or project to assign meaning to that path. This
+typically means that the JS file or module will be resolved relative to the
+final location of the wasm file itself. That means that `raw_module` is likely
+unsuitable for libraries on crates.io, but may be usable within end-user
+applications.