Bug 1342478: Support loading modules from relative paths in shell module loader. r=jonco
authorAndré Bargull <andre.bargull@gmail.com>
Thu, 02 Mar 2017 02:35:15 -0800
changeset 374655 2cd83ad751203f5f91b1a1411fb930be9bff18c1
parent 374654 9b990c5890a81b46e4ffd8ae28c230effe9fba40
child 374656 08df0e07f0ba02a27e532b25a0d3415166a6bba1
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1342478
milestone54.0a1
Bug 1342478: Support loading modules from relative paths in shell module loader. r=jonco
js/src/shell/ModuleLoader.js
--- a/js/src/shell/ModuleLoader.js
+++ b/js/src/shell/ModuleLoader.js
@@ -9,29 +9,59 @@
 // Save standard built-ins before scripts can modify them.
 const ArrayPrototypeJoin = Array.prototype.join;
 const MapPrototypeGet = Map.prototype.get;
 const MapPrototypeHas = Map.prototype.has;
 const MapPrototypeSet = Map.prototype.set;
 const ObjectDefineProperty = Object.defineProperty;
 const ReflectApply = Reflect.apply;
 const StringPrototypeIndexOf = String.prototype.indexOf;
+const StringPrototypeLastIndexOf = String.prototype.lastIndexOf;
+const StringPrototypeStartsWith = String.prototype.startsWith;
 const StringPrototypeSubstring = String.prototype.substring;
 
 const ReflectLoader = new class {
     constructor() {
         this.registry = new Map();
+        this.modulePaths = new Map();
         this.loadPath = getModuleLoadPath();
     }
 
-    resolve(name) {
+    resolve(name, module) {
         if (os.path.isAbsolute(name))
             return name;
 
-        return os.path.join(this.loadPath, name);
+        let loadPath = this.loadPath;
+        if (module) {
+            // Treat |name| as a relative path if it starts with either "./"
+            // or "../".
+            let isRelative = ReflectApply(StringPrototypeStartsWith, name, ["./"])
+                          || ReflectApply(StringPrototypeStartsWith, name, ["../"])
+#ifdef XP_WIN
+                          || ReflectApply(StringPrototypeStartsWith, name, [".\\"])
+                          || ReflectApply(StringPrototypeStartsWith, name, ["..\\"])
+#endif
+                             ;
+
+            // If |name| is a relative path and |module|'s path is available,
+            // load |name| relative to the referring module.
+            if (isRelative && ReflectApply(MapPrototypeHas, this.modulePaths, [module])) {
+                let modulePath = ReflectApply(MapPrototypeGet, this.modulePaths, [module]);
+                let sepIndex = ReflectApply(StringPrototypeLastIndexOf, modulePath, ["/"]);
+#ifdef XP_WIN
+                let otherSepIndex = ReflectApply(StringPrototypeLastIndexOf, modulePath, ["\\"]);
+                if (otherSepIndex > sepIndex)
+                    sepIndex = otherSepIndex;
+#endif
+                if (sepIndex >= 0)
+                    loadPath = ReflectApply(StringPrototypeSubstring, modulePath, [0, sepIndex]);
+            }
+        }
+
+        return os.path.join(loadPath, name);
     }
 
     normalize(path) {
 #ifdef XP_WIN
         // Replace all forward slashes with backward slashes.
         // NB: It may be tempting to replace this loop with a call to
         // String.prototype.replace, but user scripts may have modified
         // String.prototype or RegExp.prototype built-in functions, which makes
@@ -119,34 +149,35 @@ const ReflectLoader = new class {
     loadAndParse(path) {
         let normalized = this.normalize(path);
         if (ReflectApply(MapPrototypeHas, this.registry, [normalized]))
             return ReflectApply(MapPrototypeGet, this.registry, [normalized]);
 
         let source = this.fetch(path);
         let module = parseModule(source, path);
         ReflectApply(MapPrototypeSet, this.registry, [normalized, module]);
+        ReflectApply(MapPrototypeSet, this.modulePaths, [module, path]);
         return module;
     }
 
     loadAndExecute(path) {
         let module = this.loadAndParse(path);
         module.declarationInstantiation();
         return module.evaluation();
     }
 
     importRoot(path) {
         return this.loadAndExecute(path);
     }
 
     ["import"](name, referrer) {
-        let path = this.resolve(name);
+        let path = this.resolve(name, null);
         return this.loadAndExecute(path);
     }
 };
 
 setModuleResolveHook((module, requestName) => {
-    let path = ReflectLoader.resolve(requestName);
+    let path = ReflectLoader.resolve(requestName, module);
     return ReflectLoader.loadAndParse(path)
 });
 
 Reflect.Loader = ReflectLoader;
 }