text: (function(mod) {
    if (typeof exports == "object" && typeof module == "object") // CommonJS
      return mod(require("../lib/infer"), require("../lib/tern"), require("../lib/signal"), require)
    if (typeof define == "function" && define.amd) // AMD
      return define(["../lib/infer", "../lib/tern", "../lib/signal"], mod)
    mod(tern, tern, tern.signal)
  })(function(infer, tern, signal, require) {
    "use strict"
  
    function Modules(server, options) {
      this.server = server
      this.options = options || {}
      this.modules = Object.create(null)
      this.nonRelative = Object.create(null)
      this.knownModules = Object.create(null)
      this.resolvers = []
      this.modNameTests = []
      this.importTests = []
      this.completableTypes = Object.create(null)
    }
  
    Modules.prototype = signal.mixin({
      buildWrappingScope: function(parent, origin, node) {
        var scope = new infer.Scope(parent, node)
        scope.origin = origin
        this.signal("wrapScope", scope)
        return scope
      },
  
      maybeOverride: function(name) {
        if (!this.options.modules || !this.options.modules.hasOwnProperty(name))
          return false
        if (this.modules[name]) return this.modules[name]
  
        var override = this.options.modules[name]
        if (typeof(override) == "string" && override.charAt(0) == "=")
          return infer.def.parsePath(override.slice(1))
  
        var scope = this.buildWrappingScope(infer.cx().topScope, name)
        infer.def.load(override, scope)
        return this.modules[name] = scope.exports
      },
  
      resolveModule: function(name, parentFile) {
        var over = this.maybeOverride(name)
        if (over) return over
        var known = this.knownModules[name]
        if (known) return known
        if (this.options.dontLoad == true ||
            this.options.dontLoad && new RegExp(this.options.dontLoad).test(name) ||
            this.options.load && !new RegExp(this.options.load).test(name))
          return infer.ANull
  
        var resolved, relative = isRelative(name)
        for (var i = 0; !resolved && i < this.resolvers.length; i++)
          resolved = this.resolvers[i](name, parentFile)
        if (!resolved) resolved = defaultResolver(name, parentFile)
        if (!resolved) return infer.ANull
        if (typeof resolved != "string") {
          if (!relative) this.nonRelative[name] = true
          return resolved
        }
  
        var known = this.modules[resolved]
        if (known) return known
  
        if (/\.js$|(?:^\/)[^\.]+$/.test(resolved))
          this.server.addFile(resolved, null, parentFile)
        if (!relative) this.nonRelative[name] = resolved
        return this.modules[resolved] = new infer.AVal
      },
  
      findIn: function(array, node, pos) {
        for (var i = 0; i < array.length; i++) {
          var name = array[i](node, pos)
          if (name != null) return name
        }
      },
  
      isModName: function(node, pos) { return this.findIn(this.modNameTests, node, pos) },
      isImport: function(node, pos) { return this.findIn(this.importTests, node, pos) },
  
      get: function(name) {
        return this.modules[name] || (this.modules[name] = new infer.AVal)
      },
  
      completeModuleName: function(completions, query, word) {
        function fromObj(obj, useVal) {
          for (var name in obj)
            if (filter(word, name, query))
              tern.addCompletion(query, completions, name, useVal && obj[name])
        }
  
        fromObj(this.knownModules, true)
        if (this.options.modules) fromObj(this.options.modules, false)
  
        var pathsSeen = Object.create(null)
        for (var prop in this.nonRelative) {
          var val = this.nonRelative[prop]
          if (val == true || prop.indexOf("/") == -1) {
            if (filter(word, prop, query)) tern.addCompletion(query, completions, prop)
          } else if (prop.indexOf(word) == 0 && word.indexOf("/") > -1) {
            var afterSlash = /.*?\/(.*)/.exec(prop)[1]
            var found = val.indexOf(afterSlash)
            if (found > -1) {
              var dir = val.slice(0, found) + (/.*?\/(.*\/)?/.exec(word)[1] || "")
              if (dir in pathsSeen) continue
              pathsSeen[dir] = true
              this.completeFileName(completions, query, null, word, dir)
            }
          }
        }
      },
  
      completeFileName: function(completions, query, parentFile, word, _dir) {
        var path = parentFile ? resolvePath(dirName(parentFile), word) : baseName(word)
        for (var prop in this.modules) {
          if (prop != parentFile && filter(path, prop, query)) {
            if (/\.js$/.test(prop)) prop = prop.slice(0, prop.length - 3)
            var added = prop.slice(path.length)
            tern.addCompletion(query, completions, word + added, this.modules[prop])
          }
        }
      },
  
      getModType: function(node) {
        var modName = this.isModName(node), imp, prop
        if (modName == null && (imp = this.isImport(node))) {
          modName = imp.name
          prop = imp.prop
        }
        if (modName == null) return
  
        var type = this.resolveModule(modName, node.sourceFile.name)
        if (prop) {
          var obj = type.getObjType()
          type = obj && obj.hasProp(prop) || type.getProp(prop)
        }
        return type
      }
    })
  
    function resolvePath(parent, sub) {
      if (/^https?:|^\//.test(sub)) return sub
      if (parent && !/\/$/.test(parent)) parent = parent + "/"
      var m
      while (m = /^\.(\.)?\//.exec(sub)) {
        if (m[1] && parent.length > 1) {
          var lastSlash = parent.lastIndexOf("/", parent.length - 2)
          parent = lastSlash == -1 ? "" : parent.slice(0, lastSlash + 1)
        }
        sub = sub.slice(m[0].length)
      }
      return parent + sub
    }
  
    function dirName(path) {
      var lastSlash = path.lastIndexOf("/")
      if (lastSlash == -1) return ""
      return path.slice(0, lastSlash + 1)
    }
    function baseName(path) {
      var lastSlash = path.lastIndexOf("/")
      if (lastSlash == -1) return path
      else return path.slice(lastSlash + 1)
    }
  
    function defaultResolver(name, parentFile) {
      if (!/^\.\.?\//.test(name)) return
      var path = resolvePath(dirName(parentFile), name)
      var server = infer.cx().parent
      if (server.findFile(path)) return path
      if (server.findFile(path + ".js")) return path + ".js"
    }
  
    // Under node, replace completeFileName with a version that actually
    // queries the file system
    if (require) (function() {
      var fs = require("fs"), path = require("path")
  
      Modules.prototype.completeFileName = function(completions, query, parentFile, word, dir) {
        var pDir = this.server.projectDir
        var endSlash = /\/$/.test(word)
        if (parentFile) {
          var pt = path.resolve(pDir, path.dirname(parentFile), word)
          dir = endSlash ? pt : path.dirname(pt)
        }
        var base = endSlash ? word : path.dirname(word) + "/"
        var filePart = endSlash ? "" : path.basename(word)
  
        var me = this
        fs.readdirSync(dir).forEach(function(file) {
          if (/^\./.test(file)) return
          if (filter(filePart, file, query)) {
            var projectPath = me.server.normalizeFilename(path.relative(pDir, path.resolve(dir, file)))
            if (projectPath == parentFile) return
            var value = me.modules[projectPath]
            if (/\.js$/.test(file)) file = file.slice(0, file.length - 3)
            tern.addCompletion(query, completions, base + file, value)
          }
        })
      }
    }())
  
    function isRelative(path) {
      return /^\.\.?\//.test(path)
    }
  
    function filter(word, string, query) {
      return query.filter === false || !word ||
        (query.caseInsensitive ? string.toLowerCase() : string).indexOf(word) == 0
    }
  
    function preCondenseReach(state) {
      var mods = infer.cx().parent.mod.modules.modules
      var node = state.roots["!modules"] = new infer.Obj(null)
      for (var name in mods) {
        var mod = mods[name]
        var id = mod.origin || name
        var prop = node.defProp(id.replace(/\./g, "`"))
        prop.origin = mod.origin
        mod.propagate(prop)
      }
    }
  
    function postLoadDef(data) {
      var cx = infer.cx(), me = cx.parent.mod.modules
      var mods = cx.definitions[data["!name"]]["!modules"]
      if (mods) for (var name in mods.props) {
        var origin = name.replace(/`/g, ".")
        var mod = me.get(origin)
        mod.origin = origin
        mods.props[name].propagate(mod)
      }
      var known = cx.definitions[data["!name"]]["!known_modules"]
      if (known) for (var name in known.props)
        me.knownModules[name.replace(/`/g, ".")] = known.props[name]
    }
  
    function findTypeAt(_file, _pos, expr, type) {
      if (!expr) return type
      var me = infer.cx().parent.mod.modules
      var modType = me.getModType(expr.node)
      if (!modType) return type
  
      // The `type` is a value shared for all string literals.
      // We must create a copy before modifying `origin` and `originNode`.
      // Otherwise all string literals would point to the last jump location
      var inner = modType.getType(false) || {}
      type = Object.create(type)
      type.origin = modType.origin || inner.origin
      type.originNode = modType.originNode || inner.originNode
      if (modType.doc) type.doc = modType.doc
      else if (inner.doc) type.doc = inner.doc
      if (modType.url) type.url = modType.url
      else if (inner.url) type.url = inner.url
      return type
    }
  
    // Complete previously seen module names when completing in strings passed to require
    function findCompletions(file, query) {
      var me = infer.cx().parent.mod.modules
      var wordEnd = tern.resolvePos(file, query.end), types = me.completableTypes
      var expr = infer.findExpressionAround(file.ast, null, wordEnd, file.scope,
                                            function(type) { return type in types })
      if (!expr) return null
  
      if (me.isModName(expr.node, wordEnd) != null)
        return findModuleCompletions(me, file, query, expr.node, wordEnd)
  
      var imp = me.isImport(expr.node, wordEnd)
      if (imp && imp.name && imp.prop != null)
        return findImportCompletions(me, file, query, expr.node, imp, wordEnd)
    }
  
    function findImportCompletions(me, file, query, node, imp, wordEnd) {
      var completions = []
      var word = node.name ? node.name.slice(0, wordEnd - node.start) : ""
      if (query.caseInsensitive) word = word.toLowerCase()
  
      var modType = me.resolveModule(imp.name, node.sourceFile.name).getType()
      if (!modType) return null
      infer.forAllPropertiesOf(modType, function(prop, obj, depth) {
        if (obj == infer.cx().protos.Object) return
        if (query.filter !== false && word &&
            (query.caseInsensitive ? prop.toLowerCase() : prop).indexOf(word) !== 0) return
        tern.addCompletion(query, completions, prop, obj && obj.props[prop], depth)
      })
      return {
        start: tern.outputPos(query, file, node.name ? node.start : wordEnd),
        end: tern.outputPos(query, file, wordEnd),
        completions: completions,
        isSpecifier: true
      }
    }
  
    function findModuleCompletions(me, file, query, argNode, wordEnd) {
      if (argNode.type != "Literal" || typeof argNode.value != "string" ||
          argNode.start > wordEnd || argNode.end < wordEnd) return
  
      var word = argNode.raw.slice(1, wordEnd - argNode.start), quote = argNode.raw.charAt(0)
      if (word && word.charAt(word.length - 1) == quote)
        word = word.slice(0, word.length - 1)
      if (query.caseInsensitive) word = word.toLowerCase()
  
      var completions = []
      if (isRelative(word)) me.completeFileName(completions, query, file.name, word)
      else me.completeModuleName(completions, query, word)
  
      if (argNode.end == wordEnd + 1 && file.text.charAt(wordEnd) == quote)
        ++wordEnd
      return {
        start: tern.outputPos(query, file, argNode.start),
        end: tern.outputPos(query, file, wordEnd),
        isProperty: false,
        completions: completions.map(function(rec) {
          var name = typeof rec == "string" ? rec : rec.name
          var string = JSON.stringify(name)
          if (quote == "'") string = quote + string.slice(1, string.length -1).replace(/'/g, "\\'") + quote
          if (typeof rec == "string") return string
          rec.displayName = name
          rec.name = string
          return rec
        })
      }
    }
  
    tern.registerPlugin("modules", function(server, options) {
      server.mod.modules = new Modules(server, options)
  
      server.on("beforeLoad", function(file) {
        file.scope = this.mod.modules.buildWrappingScope(file.scope, file.name, file.ast)
      })
  
      server.on("afterLoad", function(file) {
        var mod = this.mod.modules.get(file.name)
        mod.origin = file.name
        this.mod.modules.signal("getExports", file, mod)
      })
  
      server.on("reset", function() {
        this.mod.modules.modules = Object.create(null)
      })
  
      server.on("preCondenseReach", preCondenseReach)
      server.on("postLoadDef", postLoadDef)
      server.on("typeAt", findTypeAt)
      server.on("completion", findCompletions)
    })
  
    tern.defineQueryType("exports", {
      takesFile: true,
      run: function(server, query, file) {
        function describe(aval) {
          var target = {}, type = aval.getType(false)
          target.type = infer.toString(type, 3)
          var doc = aval.doc || (type && type.doc), url = aval.url || (type && type.url)
          if (doc) target.doc = doc
          if (url) target.url = url
          var span = tern.getSpan(aval) || (type && tern.getSpan(type))
          if (span) tern.storeSpan(server, query, span, target)
          return target
        }
  
        var mod = server.mod.modules, known = mod && mod.modules[file.name]
        if (!known) return {}
        var resp = describe(known);
        var type = known.getType(false)
        if (type instanceof infer.Obj) {
          var props = resp.props = {}
          for (var prop in type.props)
            props[prop] = describe(type.props[prop])
        }
        return resp
      }
    })
  })