<|start_filename|>lib/field_ref.js<|end_filename|>
lunr.FieldRef = function (docRef, fieldName, stringValue) {
  this.docRef = docRef
  this.fieldName = fieldName
  this._stringValue = stringValue
}

lunr.FieldRef.joiner = "/"

lunr.FieldRef.fromString = function (s) {
  var n = s.indexOf(lunr.FieldRef.joiner)

  if (n === -1) {
    throw "malformed field ref string"
  }

  var fieldRef = s.slice(0, n),
      docRef = s.slice(n + 1)

  return new lunr.FieldRef (docRef, fieldRef, s)
}

lunr.FieldRef.prototype.toString = function () {
  if (this._stringValue == undefined) {
    this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef
  }

  return this._stringValue
}


<|start_filename|>test/trimmer_test.js<|end_filename|>
suite('lunr.trimmer', function () {
  test('latin characters', function () {
    var token = new lunr.Token ('hello')
    assert.equal(lunr.trimmer(token).toString(), token.toString())
  })

  suite('punctuation', function () {
    var trimmerTest = function (description, str, expected) {
      test(description, function () {
        var token = new lunr.Token(str),
            trimmed = lunr.trimmer(token).toString()

        assert.equal(expected, trimmed)
      })
    }

    trimmerTest('full stop', 'hello.', 'hello')
    trimmerTest('inner apostrophe', "it's", "it's")
    trimmerTest('trailing apostrophe', "james'", 'james')
    trimmerTest('exclamation mark', 'stop!', 'stop')
    trimmerTest('comma', 'first,', 'first')
    trimmerTest('brackets', '[tag]', 'tag')
  })

  test('is a registered pipeline function', function () {
    assert.equal(lunr.trimmer.label, 'trimmer')
    assert.equal(lunr.Pipeline.registeredFunctions['trimmer'], lunr.trimmer)
  })
})


<|start_filename|>perf/pipeline_perf.js<|end_filename|>
suite('lunr.Pipeline', function () {  
  var tokenToToken = function(token) {
    return token
  }

  var tokenToTokenArray = function(token) {
    return [token, token]
  }

  var buildTokens = function(count) {
    return words.slice(0, count).map(function(word) { 
      return new lunr.Token(word) 
    })
  }

  lunr.Pipeline.registerFunction(tokenToToken, 'tokenToToken')
  lunr.Pipeline.registerFunction(tokenToTokenArray, 'tokenToTokenArray')

  var fewTokens = buildTokens(50);
  var manyTokens = buildTokens(1000)
  
  var tokenToTokenPipeline = new lunr.Pipeline
  tokenToTokenPipeline.add(tokenToToken)

  var tokenToTokenArrayPipeline = new lunr.Pipeline
  tokenToTokenArrayPipeline.add(tokenToTokenArray)

  this.add('few tokens, token -> token', function () {
    tokenToTokenPipeline.run(fewTokens)
  })

  this.add('many tokens, token -> token', function () {
    tokenToTokenPipeline.run(manyTokens)
  })

  this.add('few tokens, token -> token array', function () {
    tokenToTokenArrayPipeline.run(fewTokens)
  })

  this.add('many tokens, token -> token array', function () {
    tokenToTokenArrayPipeline.run(manyTokens)
  })
})


<|start_filename|>test/stemmer_test.js<|end_filename|>
suite('lunr.stemmer', function () {
  test('reduces words to their stem', function (done) {
    withFixture('stemming_vocab.json', function (err, fixture) {
      if (err != null) {
        throw err
      }

      var testData = JSON.parse(fixture)

      Object.keys(testData).forEach(function (word) {
        var expected = testData[word],
            token = new lunr.Token(word),
            result = lunr.stemmer(token).toString()

        assert.equal(expected, result)
      })

      done()
    })
  })

  test('is a registered pipeline function', function () {
    assert.equal('stemmer', lunr.stemmer.label)
    assert.equal(lunr.stemmer, lunr.Pipeline.registeredFunctions['stemmer'])
  })
})


<|start_filename|>lib/lunr.js<|end_filename|>
/**
 * A convenience function for configuring and constructing
 * a new lunr Index.
 *
 * A lunr.Builder instance is created and the pipeline setup
 * with a trimmer, stop word filter and stemmer.
 *
 * This builder object is yielded to the configuration function
 * that is passed as a parameter, allowing the list of fields
 * and other builder parameters to be customised.
 *
 * All documents _must_ be added within the passed config function.
 *
 * @example
 * var idx = lunr(function () {
 *   this.field('title')
 *   this.field('body')
 *   this.ref('id')
 *
 *   documents.forEach(function (doc) {
 *     this.add(doc)
 *   }, this)
 * })
 *
 * @see {@link lunr.Builder}
 * @see {@link lunr.Pipeline}
 * @see {@link lunr.trimmer}
 * @see {@link lunr.stopWordFilter}
 * @see {@link lunr.stemmer}
 * @namespace {function} lunr
 */
var lunr = function (config) {
  var builder = new lunr.Builder

  builder.pipeline.add(
    lunr.trimmer,
    lunr.stopWordFilter,
    lunr.stemmer
  )

  builder.searchPipeline.add(
    lunr.stemmer
  )

  config.call(builder, builder)
  return builder.build()
}

lunr.version = "@VERSION"


<|start_filename|>test/match_data_test.js<|end_filename|>
suite('lunr.MatchData', function () {
  suite('#combine', function () {
    setup(function () {
      this.match = new lunr.MatchData('foo', 'title', {
        position: [1]
      })

      this.match.combine(new lunr.MatchData('bar', 'title', {
        position: [2]
      }))

      this.match.combine(new lunr.MatchData('baz', 'body', {
        position: [3]
      }))

      this.match.combine(new lunr.MatchData('baz', 'body', {
        position: [4]
      }))
    })

    test('#terms', function () {
      assert.sameMembers(['foo', 'bar', 'baz'], Object.keys(this.match.metadata))
    })

    test('#metadata', function () {
      assert.deepEqual(this.match.metadata.foo.title.position, [1])
      assert.deepEqual(this.match.metadata.bar.title.position, [2])
      assert.deepEqual(this.match.metadata.baz.body.position, [3, 4])
    })

    test('does not mutate source data', function () {
      var metadata = { foo: [1] },
          matchData1 = new lunr.MatchData('foo', 'title', metadata),
          matchData2 = new lunr.MatchData('foo', 'title', metadata)

      matchData1.combine(matchData2)

      assert.deepEqual(metadata.foo, [1])
    })
  })
})


<|start_filename|>test/search_test.js<|end_filename|>
suite('search', function () {
  setup(function () {
    this.documents = [{
      id: 'a',
      title: '<NAME>ills <NAME>',
      body: 'Mr. Green killed <NAME> in the study with the candlestick. Mr. Green is not a very nice fellow.',
      wordCount: 19
    },{
      id: 'b',
      title: 'Plumb waters plant',
      body: 'Professor Plumb has a green plant in his study',
      wordCount: 9
    },{
      id: 'c',
      title: 'Scarlett helps Professor',
      body: '<NAME> watered <NAME>s green plant while he was away from his office last week.',
      wordCount: 16
    }]
  })

  suite('with build-time field boosts', function () {
    setup(function () {
      var self = this

      this.idx = lunr(function () {
        this.ref('id')
        this.field('title')
        this.field('body', { boost: 10 })

        self.documents.forEach(function (document) {
          this.add(document)
        }, this)
      })
    })

    suite('no query boosts', function () {
      var assertions = function () {
        test('document b ranks highest', function () {
          assert.equal('b', this.results[0].ref)
        })
      }

      suite('#search', function () {
        setup(function () {
          this.results = this.idx.search('professor')
        })

        assertions()
      })

      suite('#query', function () {
        setup(function () {
          this.results = this.idx.query(function (q) {
            q.term('professor')
          })
        })

        assertions()
      })
    })
  })

  suite('with build-time document boost', function () {
    setup(function () {
      var self = this

      this.idx = lunr(function () {
        this.ref('id')
        this.field('title')
        this.field('body')

        self.documents.forEach(function (document) {
          var boost = document.id == 'c' ? 10 : 1
          this.add(document, { boost: boost })
        }, this)
      })
    })

    suite('no query boost', function () {
      var assertions = function () {
        test('document c ranks highest', function () {
          assert.equal('c', this.results[0].ref)
        })
      }

      suite('#search', function () {
        setup(function () {
          this.results = this.idx.search('plumb')
        })

        assertions()
      })

      suite('#query', function () {
        setup(function () {
          this.results = this.idx.query(function (q) {
            q.term('plumb')
          })
        })

        assertions()
      })
    })

    suite('with query boost', function () {
      var assertions = function () {
        test('document b ranks highest', function () {
          assert.equal('b', this.results[0].ref)
        })
      }

      suite('#search', function () {
        setup(function () {
          this.results = this.idx.search('green study^10')
        })

        assertions()
      })

      suite('#query', function () {
        setup(function () {
          this.results = this.idx.query(function (q) {
            q.term('green')
            q.term('study', { boost: 10 })
          })
        })

        assertions()
      })
    })
  })

  suite('without build-time boosts', function () {
    setup(function () {
      var self = this

      this.idx = lunr(function () {
        this.ref('id')
        this.field('title')
        this.field('body')

        self.documents.forEach(function (document) {
          this.add(document)
        }, this)
      })
    })

    suite('single term search', function () {
      suite('one match', function () {
        var assertions = function () {
          test('one result returned', function () {
            assert.lengthOf(this.results, 1)
          })

          test('document c matches', function () {
            assert.equal('c', this.results[0].ref)
          })

          test('matching term', function () {
            assert.sameMembers(['scarlett'], Object.keys(this.results[0].matchData.metadata))
          })
        }

        suite('#seach', function () {
          setup(function () {
            this.results = this.idx.search('scarlett')
          })

          assertions()
        })

        suite('#query', function () {
          setup(function () {
            this.results = this.idx.query(function (q) {
              q.term('scarlett')
            })
          })

          assertions()
        })
      })

      suite('no match', function () {
        setup(function () {
          this.results = this.idx.search('foo')
        })

        test('no matches', function () {
          assert.lengthOf(this.results, 0)
        })
      })

      suite('multiple matches', function () {
        setup(function () {
          this.results = this.idx.search('plant')
        })

        test('has two matches', function () {
          assert.lengthOf(this.results, 2)
        })

        test('sorted by relevance', function () {
          assert.equal('b', this.results[0].ref)
          assert.equal('c', this.results[1].ref)
        })
      })

      suite('pipeline processing', function () {
        // study would be stemmed to studi, tokens
        // are stemmed by default on index and must
        // also be stemmed on search to match
        suite('enabled (default)', function () {
          setup(function () {
            this.results = this.idx.query(function (q) {
              q.clause({term: 'study', usePipeline: true})
            })
          })

          test('has two matches', function () {
            assert.lengthOf(this.results, 2)
          })

          test('sorted by relevance', function () {
            assert.equal('b', this.results[0].ref)
            assert.equal('a', this.results[1].ref)
          })
        })

        suite('disabled', function () {
          setup(function () {
            this.results = this.idx.query(function (q) {
              q.clause({term: 'study', usePipeline: false})
            })
          })

          test('no matches', function () {
            assert.lengthOf(this.results, 0)
          })
        })
      })
    })

    suite('multiple terms', function () {
      suite('all terms match', function () {
        setup(function () {
          this.results = this.idx.search('fellow candlestick')
        })

        test('has one match', function () {
          assert.lengthOf(this.results, 1)
        })

        test('correct document returned', function () {
          assert.equal('a', this.results[0].ref)
        })

        test('matched terms returned', function () {
          assert.sameMembers(['fellow', 'candlestick'], Object.keys(this.results[0].matchData.metadata))
          assert.sameMembers(['body'], Object.keys(this.results[0].matchData.metadata['fellow']));
          assert.sameMembers(['body'], Object.keys(this.results[0].matchData.metadata['candlestick']));
        })
      })

      suite('one term matches', function () {
        setup(function () {
          this.results = this.idx.search('week foo')
        })

        test('has one match', function () {
          assert.lengthOf(this.results, 1)
        })

        test('correct document returned', function () {
          assert.equal('c', this.results[0].ref)
        })

        test('only matching terms returned', function () {
          assert.sameMembers(['week'], Object.keys(this.results[0].matchData.metadata))
        })
      })

      suite('duplicate query terms', function () {
        // https://github.com/olivernn/lunr.js/issues/256
        // previously this would throw a duplicate index error
        // because the query vector already contained an entry
        // for the term 'fellow'
        test('no errors', function () {
          var idx = this.idx
          assert.doesNotThrow(function () {
            idx.search('fellow candlestick foo bar green plant fellow')
          })
        })
      })

      suite('documents with all terms score higher', function () {
        setup(function () {
          this.results = this.idx.search('candlestick green')
        })

        test('has three matches', function () {
          assert.lengthOf(this.results, 3)
        })

        test('correct documents returned', function () {
          var matchingDocuments = this.results.map(function (r) {
            return r.ref
          })
          assert.sameMembers(['a', 'b', 'c'], matchingDocuments)
        })

        test('documents with all terms score highest', function () {
          assert.equal('a', this.results[0].ref)
        })

        test('matching terms are returned', function () {
          assert.sameMembers(['candlestick', 'green'], Object.keys(this.results[0].matchData.metadata))
          assert.sameMembers(['green'], Object.keys(this.results[1].matchData.metadata))
          assert.sameMembers(['green'], Object.keys(this.results[2].matchData.metadata))
        })
      })

      suite('no terms match', function () {
        setup(function () {
          this.results = this.idx.search('foo bar')
        })

        test('no matches', function () {
          assert.lengthOf(this.results, 0)
        })
      })

      suite('corpus terms are stemmed', function () {
        setup(function () {
          this.results = this.idx.search('water')
        })

        test('matches two documents', function () {
          assert.lengthOf(this.results, 2)
        })

        test('matches correct documents', function () {
          var matchingDocuments = this.results.map(function (r) {
            return r.ref
          })
          assert.sameMembers(['b', 'c'], matchingDocuments)
        })
      })

      suite('field scoped terms', function () {
        suite('only matches on scoped field', function () {
          setup(function () {
            this.results = this.idx.search('title:plant')
          })

          test('one result returned', function () {
            assert.lengthOf(this.results, 1)
          })

          test('returns the correct document', function () {
            assert.equal('b', this.results[0].ref)
          })

          test('match data', function () {
            assert.sameMembers(['plant'], Object.keys(this.results[0].matchData.metadata))
          })
        })

        suite('no matching terms', function () {
          setup(function () {
            this.results = this.idx.search('title:candlestick')
          })

          test('no results returned', function () {
            assert.lengthOf(this.results, 0)
          })
        })
      })

      suite('wildcard matching', function () {
        suite('trailing wildcard', function () {
          suite('no matches', function () {
            setup(function () {
              this.results = this.idx.search('fo*')
            })

            test('no results returned', function () {
              assert.lengthOf(this.results, 0)
            })
          })

          suite('one match', function () {
            setup(function () {
              this.results = this.idx.search('candle*')
            })

            test('one result returned', function () {
              assert.lengthOf(this.results, 1)
            })

            test('correct document matched', function () {
              assert.equal('a', this.results[0].ref)
            })

            test('matching terms returned', function () {
              assert.sameMembers(['candlestick'], Object.keys(this.results[0].matchData.metadata))
            })
          })

          suite('multiple terms match', function () {
            setup(function () {
              this.results = this.idx.search('pl*')
            })

            test('two results returned', function () {
              assert.lengthOf(this.results, 2)
            })

            test('correct documents matched', function () {
              var matchingDocuments = this.results.map(function (r) {
                return r.ref
              })
              assert.sameMembers(['b', 'c'], matchingDocuments)
            })

            test('matching terms returned', function () {
              assert.sameMembers(['plumb', 'plant'], Object.keys(this.results[0].matchData.metadata))
              assert.sameMembers(['plumb', 'plant'], Object.keys(this.results[1].matchData.metadata))
            })
          })
        })
      })
    })

    suite('wildcard matching', function () {
      suite('trailing wildcard', function () {
        suite('no matches found', function () {
          setup(function () {
            this.results = this.idx.search('fo*')
          })

          test('no results returned', function () {
            assert.lengthOf(this.results, 0)
          })
        })

        suite('results found', function () {
          setup(function () {
            this.results = this.idx.search('pl*')
          })

          test('two results returned', function () {
            assert.lengthOf(this.results, 2)
          })

          test('matching documents returned', function () {
            assert.equal('b', this.results[0].ref)
            assert.equal('c', this.results[1].ref)
          })

          test('matching terms returned', function () {
            assert.sameMembers(['plant', 'plumb'], Object.keys(this.results[0].matchData.metadata))
            assert.sameMembers(['plant', 'plumb'], Object.keys(this.results[1].matchData.metadata))
          })
        })
      })

      suite('leading wildcard', function () {
        suite('no results found', function () {
          setup(function () {
            this.results = this.idx.search('*oo')
          })

          test('no results found', function () {
            assert.lengthOf(this.results, 0)
          })
        })

        suite('results found', function () {
          setup(function () {
            this.results = this.idx.search('*ant')
          })

          test('two results found', function () {
            assert.lengthOf(this.results, 2)
          })

          test('matching documents returned', function () {
            assert.equal('b', this.results[0].ref)
            assert.equal('c', this.results[1].ref)
          })

          test('matching terms returned', function () {
            assert.sameMembers(['plant'], Object.keys(this.results[0].matchData.metadata))
            assert.sameMembers(['plant'], Object.keys(this.results[1].matchData.metadata))
          })
        })
      })

      suite('contained wildcard', function () {
        suite('no results found', function () {
          setup(function () {
            this.results = this.idx.search('f*o')
          })

          test('no results found', function () {
            assert.lengthOf(this.results, 0)
          })
        })

        suite('results found', function () {
          setup(function () {
            this.results = this.idx.search('pl*nt')
          })

          test('two results found', function () {
            assert.lengthOf(this.results, 2)
          })

          test('matching documents returned', function () {
            assert.equal('b', this.results[0].ref)
            assert.equal('c', this.results[1].ref)
          })

          test('matching terms returned', function () {
            assert.sameMembers(['plant'], Object.keys(this.results[0].matchData.metadata))
            assert.sameMembers(['plant'], Object.keys(this.results[1].matchData.metadata))
          })
        })
      })
    })

    suite('edit distance', function () {
      suite('no results found', function () {
        setup(function () {
          this.results = this.idx.search('foo~1')
        })

        test('no results returned', function () {
          assert.lengthOf(this.results, 0)
        })
      })

      suite('results found', function () {
        setup(function () {
          this.results = this.idx.search('plont~1')
        })

        test('two results found', function () {
          assert.lengthOf(this.results, 2)
        })

        test('matching documents returned', function () {
          assert.equal('b', this.results[0].ref)
          assert.equal('c', this.results[1].ref)
        })

        test('matching terms returned', function () {
          assert.sameMembers(['plant'], Object.keys(this.results[0].matchData.metadata))
          assert.sameMembers(['plant'], Object.keys(this.results[1].matchData.metadata))
        })
      })
    })

    suite('searching by field', function () {
      suite('unknown field', function () {
        test('throws lunr.QueryParseError', function () {
          assert.throws(function () {
            this.idx.search('unknown-field:plant')
          }.bind(this), lunr.QueryParseError)
        })
      })

      suite('no results found', function () {
        setup(function () {
          this.results = this.idx.search('title:candlestick')
        })

        test('no results found', function () {
          assert.lengthOf(this.results, 0)
        })
      })

      suite('results found', function () {
        setup(function () {
          this.results = this.idx.search('title:plant')
        })

        test('one results found', function () {
          assert.lengthOf(this.results, 1)
        })

        test('matching documents returned', function () {
          assert.equal('b', this.results[0].ref)
        })

        test('matching terms returned', function () {
          assert.sameMembers(['plant'], Object.keys(this.results[0].matchData.metadata))
        })
      })
    })

    suite('term boosts', function () {
      suite('no results found', function () {
        setup(function () {
          this.results = this.idx.search('foo^10')
        })

        test('no results found', function () {
          assert.lengthOf(this.results, 0)
        })
      })

      suite('results found', function () {
        setup(function () {
          this.results = this.idx.search('scarlett candlestick^5')
        })

        test('two results found', function () {
          assert.lengthOf(this.results, 2)
        })

        test('matching documents returned', function () {
          assert.equal('a', this.results[0].ref)
          assert.equal('c', this.results[1].ref)
        })

        test('matching terms returned', function () {
          assert.sameMembers(['candlestick'], Object.keys(this.results[0].matchData.metadata))
          assert.sameMembers(['scarlett'], Object.keys(this.results[1].matchData.metadata))
        })
      })
    })

    suite('typeahead style search', function () {
      suite('no results found', function () {
        setup(function () {
          this.results = this.idx.query(function (q) {
            q.term("xyz", { boost: 100, usePipeline: true })
            q.term("xyz", { boost: 10, usePipeline: false, wildcard: lunr.Query.wildcard.TRAILING })
            q.term("xyz", { boost: 1, editDistance: 1 })
          })
        })

        test('no results found', function () {
          assert.lengthOf(this.results, 0)
        })
      })

      suite('results found', function () {
        setup(function () {
          this.results = this.idx.query(function (q) {
            q.term("pl", { boost: 100, usePipeline: true })
            q.term("pl", { boost: 10, usePipeline: false, wildcard: lunr.Query.wildcard.TRAILING })
            q.term("pl", { boost: 1, editDistance: 1 })
          })
        })

        test('two results found', function () {
          assert.lengthOf(this.results, 2)
        })

        test('matching documents returned', function () {
          assert.equal('b', this.results[0].ref)
          assert.equal('c', this.results[1].ref)
        })

        test('matching terms returned', function () {
          assert.sameMembers(['plumb', 'plant'], Object.keys(this.results[0].matchData.metadata))
          assert.sameMembers(['plumb', 'plant'], Object.keys(this.results[1].matchData.metadata))
        })
      })
    })

    suite('term presence', function () {
      suite('prohibited', function () {
        suite('match', function () {
          var assertions = function () {
            test('two results found', function () {
              assert.lengthOf(this.results, 2)
            })

            test('matching documents returned', function () {
              assert.equal('b', this.results[0].ref)
              assert.equal('c', this.results[1].ref)
            })

            test('matching terms returned', function () {
              assert.sameMembers(['green'], Object.keys(this.results[0].matchData.metadata))
              assert.sameMembers(['green'], Object.keys(this.results[1].matchData.metadata))
            })
          }

          suite('#query', function () {
            setup(function () {
              this.results = this.idx.query(function (q) {
                q.term('candlestick', { presence: lunr.Query.presence.PROHIBITED })
                q.term('green', { presence: lunr.Query.presence.OPTIONAL })
              })
            })

            assertions()
          })

          suite('#search', function () {
            setup(function () {
              this.results = this.idx.search('-candlestick green')
            })

            assertions()
          })
        })

        suite('no match', function () {
          var assertions = function () {
            test('no matches', function () {
              assert.lengthOf(this.results, 0)
            })
          }

          suite('#query', function () {
            setup(function () {
              this.results = this.idx.query(function (q) {
                q.term('green', { presence: lunr.Query.presence.PROHIBITED })
              })
            })

            assertions()
          })

          suite('#search', function () {
            setup(function () {
              this.results = this.idx.search('-green')
            })

            assertions()
          })
        })

        suite('negated query no match', function () {
          var assertions = function () {
            test('all documents returned', function () {
              assert.lengthOf(this.results, 3)
            })

            test('all results have same score', function () {
              assert.isTrue(this.results.every(function (r) { return r.score === 0 }))
            })
          }

          suite('#query', function () {
            setup(function () {
              this.results = this.idx.query(function (q) {
                q.term('qwertyuiop', { presence: lunr.Query.presence.PROHIBITED })
              })
            })

            assertions()
          })

          suite('#search', function () {
            setup(function () {
              this.results = this.idx.search("-qwertyuiop")
            })

            assertions()
          })
        })

        suite('negated query some match', function () {
          var assertions = function () {
            test('all documents returned', function () {
              assert.lengthOf(this.results, 1)
            })

            test('all results have same score', function () {
              assert.isTrue(this.results.every(function (r) { return r.score === 0 }))
            })

            test('matching documents returned', function () {
              assert.equal('a', this.results[0].ref)
            })
          }

          suite('#query', function () {
            setup(function () {
              this.results = this.idx.query(function (q) {
                q.term('plant', { presence: lunr.Query.presence.PROHIBITED })
              })
            })

            assertions()
          })

          suite('#search', function () {
            setup(function () {
              this.results = this.idx.search("-plant")
            })

            assertions()
          })
        })

        suite('field match', function () {
          var assertions = function () {
            test('one result found', function () {
              assert.lengthOf(this.results, 1)
            })

            test('matching documents returned', function () {
              assert.equal('c', this.results[0].ref)
            })

            test('matching terms returned', function () {
              assert.sameMembers(['plumb'], Object.keys(this.results[0].matchData.metadata))
            })
          }

          suite('#query', function () {
            setup(function () {
              this.results = this.idx.query(function (q) {
                q.term('plant', { presence: lunr.Query.presence.PROHIBITED, fields: ['title'] })
                q.term('plumb', { presence: lunr.Query.presence.OPTIONAL })
              })
            })

            assertions()
          })

          suite('#search', function () {
            setup(function () {
              this.results = this.idx.search('-title:plant plumb')
            })

            assertions()
          })
        })
      })

      suite('required', function () {
        suite('match', function () {
          var assertions = function () {
            test('one result found', function () {
              assert.lengthOf(this.results, 1)
            })

            test('matching documents returned', function () {
              assert.equal('a', this.results[0].ref)
            })

            test('matching terms returned', function () {
              assert.sameMembers(['candlestick', 'green'], Object.keys(this.results[0].matchData.metadata))
            })
          }

          suite('#search', function () {
            setup(function () {
              this.results = this.idx.search("+candlestick green")
            })

            assertions()
          })

          suite('#query', function () {
            setup(function () {
              this.results = this.idx.query(function (q) {
                q.term('candlestick', { presence: lunr.Query.presence.REQUIRED })
                q.term('green', { presence: lunr.Query.presence.OPTIONAL })
              })
            })

            assertions()
          })
        })

        suite('no match', function () {
          var assertions = function () {
            test('no matches', function () {
              assert.lengthOf(this.results, 0)
            })
          }

          suite('#query', function () {
            setup(function () {
              this.results = this.idx.query(function (q) {
                q.term('mustard', { presence: lunr.Query.presence.REQUIRED })
                q.term('plant', { presence: lunr.Query.presence.REQUIRED })
              })
            })

            assertions()
          })

          suite('#search', function () {
            setup(function () {
              this.results = this.idx.search('+mustard +plant')
            })

            assertions()
          })
        })

        suite('no matching term', function () {
          var assertions = function () {
            test('no matches', function () {
              assert.lengthOf(this.results, 0)
            })
          }

          suite('#query', function () {
            setup(function () {
              this.results = this.idx.query(function (q) {
                q.term('qwertyuiop', { presence: lunr.Query.presence.REQUIRED })
                q.term('green', { presence: lunr.Query.presence.OPTIONAL })
              })
            })

            assertions()
          })

          suite('#search', function () {
            setup(function () {
              this.results = this.idx.search('+qwertyuiop green')
            })

            assertions()
          })
        })

        suite('field match', function () {
          var assertions = function () {
            test('one result found', function () {
              assert.lengthOf(this.results, 1)
            })

            test('matching documents returned', function () {
              assert.equal('b', this.results[0].ref)
            })

            test('matching terms returned', function () {
              assert.sameMembers(['plant', 'green'], Object.keys(this.results[0].matchData.metadata))
            })
          }

          suite('#query', function () {
            setup(function () {
              this.results = this.idx.query(function (q) {
                q.term('plant', { presence: lunr.Query.presence.REQUIRED, fields: ['title'] })
                q.term('green', { presence: lunr.Query.presence.OPTIONAL })
              })
            })

            assertions()
          })

          suite('#search', function () {
            setup(function () {
              this.results = this.idx.search('+title:plant green')
            })

            assertions()
          })
        })

        suite('field and non field match', function () {
          var assertions = function () {
            test('one result found', function () {
              assert.lengthOf(this.results, 1)
            })

            test('matching documents returned', function () {
              assert.equal('b', this.results[0].ref)
            })

            test('matching terms returned', function () {
              assert.sameMembers(['plant', 'green'], Object.keys(this.results[0].matchData.metadata))
            })
          }

          suite('#search', function () {
            setup(function () {
              this.results = this.idx.search('+title:plant +green')
            })

            assertions()
          })

          suite('#query', function () {
            setup(function () {
              this.results = this.idx.query(function (q) {
                q.term('plant', { fields: ['title'], presence: lunr.Query.presence.REQUIRED })
                q.term('green', { presence: lunr.Query.presence.REQUIRED })
              })
            })

            assertions()
          })
        })

        suite('different fields', function () {
          var assertions = function () {
            test('one result found', function () {
              assert.lengthOf(this.results, 1)
            })

            test('matching documents returned', function () {
              assert.equal('b', this.results[0].ref)
            })

            test('matching terms returned', function () {
              assert.sameMembers(['studi', 'plant'], Object.keys(this.results[0].matchData.metadata))
            })
          }

          suite('#search', function () {
            setup(function () {
              this.results = this.idx.search('+title:plant +body:study')
            })

            assertions()
          })

          suite('#query', function () {
            setup(function () {
              this.results = this.idx.query(function (q) {
                q.term('plant', { fields: ['title'], presence: lunr.Query.presence.REQUIRED })
                q.term('study', { fields: ['body'], presence: lunr.Query.presence.REQUIRED })
              })
            })

            assertions()
          })
        })

        suite('different fields one without match', function () {
          var assertions = function () {
            test('no matches', function () {
              assert.lengthOf(this.results, 0)
            })
          }

          suite('#search', function () {
            setup(function () {
              this.results = this.idx.search('+title:plant +body:qwertyuiop')
            })

            assertions()
          })

          suite('#query', function () {
            setup(function () {
              this.results = this.idx.query(function (q) {
                q.term('plant', { fields: ['title'], presence: lunr.Query.presence.REQUIRED })
                q.term('qwertyuiop', { fields: ['body'], presence: lunr.Query.presence.REQUIRED })
              })
            })

            assertions()
          })
        })
      })

      suite('combined', function () {
        var assertions = function () {
          test('one result found', function () {
            assert.lengthOf(this.results, 1)
          })

          test('matching documents returned', function () {
            assert.equal('b', this.results[0].ref)
          })

          test('matching terms returned', function () {
            assert.sameMembers(['plant', 'green'], Object.keys(this.results[0].matchData.metadata))
          })
        }

        suite('#query', function () {
          setup(function () {
            this.results = this.idx.query(function (q) {
              q.term('plant', { presence: lunr.Query.presence.REQUIRED })
              q.term('green', { presence: lunr.Query.presence.OPTIONAL })
              q.term('office', { presence: lunr.Query.presence.PROHIBITED })
            })
          })

          assertions()
        })

        suite('#search', function () {
          setup(function () {
            this.results = this.idx.search('+plant green -office')
          })

          assertions()
        })

      })
    })
  })
})


<|start_filename|>perf/stemmer_perf.js<|end_filename|>
suite('lunr.stemmer', function () {
  this.add('#call', function () {
    for (var i = 0; i < words.length; i++) {
      lunr.stemmer(new lunr.Token (words[i]))
    }
  })
})


<|start_filename|>perf/query_parser_perf.js<|end_filename|>
suite('lunr.QueryParser', function () {
  var parse = function (q) {
    var query = new lunr.Query (['title', 'body']),
        parser = new lunr.QueryParser(q, query)

    parser.parse()
  }

  this.add('simple', function () {
    parse('foo bar')
  })

  this.add('field', function () {
    parse('title:foo bar')
  })

  this.add('modifier', function () {
    parse('foo~2 bar')
  })

  this.add('complex', function () {
    parse('title:foo~2^6 bar')
  })
})


<|start_filename|>test/utils_test.js<|end_filename|>
suite('lunr.utils', function () {
  suite('#clone', function () {
    var subject = function (obj) {
      setup(function () {
        this.obj = obj
        this.clone = lunr.utils.clone(obj)
      })
    }

    suite('handles null', function () {
      subject(null)

      test('returns null', function () {
        assert.equal(null, this.clone)
        assert.equal(this.obj, this.clone)
      })
    })

    suite('handles undefined', function () {
      subject(undefined)

      test('returns null', function () {
        assert.equal(undefined, this.clone)
        assert.equal(this.obj, this.clone)
      })
    })

    suite('object with primatives', function () {
      subject({
        number: 1,
        string: 'foo',
        bool: true
      })

      test('clones number correctly', function () {
        assert.equal(this.obj.number, this.clone.number)
      })

      test('clones string correctly', function () {
        assert.equal(this.obj.string, this.clone.string)
      })

      test('clones bool correctly', function () {
        assert.equal(this.obj.bool, this.clone.bool)
      })
    })

    suite('object with array property', function () {
      subject({
        array: [1, 2, 3]
      })

      test('clones array correctly', function () {
        assert.deepEqual(this.obj.array, this.clone.array)
      })

      test('mutations on clone do not affect orginial', function () {
        this.clone.array.push(4)
        assert.notDeepEqual(this.obj.array, this.clone.array)
        assert.equal(this.obj.array.length, 3)
        assert.equal(this.clone.array.length, 4)
      })
    })

    suite('nested object', function () {
      test('throws type error', function () {
        assert.throws(function () {
          lunr.utils.clone({
            'foo': {
              'bar': 1
            }
          })
        }, TypeError)
      })
    })
  })
})


<|start_filename|>lib/stop_word_filter.js<|end_filename|>
/*!
 * lunr.stopWordFilter
 * Copyright (C) @YEAR <NAME>
 */

/**
 * lunr.generateStopWordFilter builds a stopWordFilter function from the provided
 * list of stop words.
 *
 * The built in lunr.stopWordFilter is built using this generator and can be used
 * to generate custom stopWordFilters for applications or non English languages.
 *
 * @function
 * @param {Array} token The token to pass through the filter
 * @returns {lunr.PipelineFunction}
 * @see lunr.Pipeline
 * @see lunr.stopWordFilter
 */
lunr.generateStopWordFilter = function (stopWords) {
  var words = stopWords.reduce(function (memo, stopWord) {
    memo[stopWord] = stopWord
    return memo
  }, {})

  return function (token) {
    if (token && words[token.toString()] !== token.toString()) return token
  }
}

/**
 * lunr.stopWordFilter is an English language stop word list filter, any words
 * contained in the list will not be passed through the filter.
 *
 * This is intended to be used in the Pipeline. If the token does not pass the
 * filter then undefined will be returned.
 *
 * @function
 * @implements {lunr.PipelineFunction}
 * @params {lunr.Token} token - A token to check for being a stop word.
 * @returns {lunr.Token}
 * @see {@link lunr.Pipeline}
 */
lunr.stopWordFilter = lunr.generateStopWordFilter([
  'a',
  'able',
  'about',
  'across',
  'after',
  'all',
  'almost',
  'also',
  'am',
  'among',
  'an',
  'and',
  'any',
  'are',
  'as',
  'at',
  'be',
  'because',
  'been',
  'but',
  'by',
  'can',
  'cannot',
  'could',
  'dear',
  'did',
  'do',
  'does',
  'either',
  'else',
  'ever',
  'every',
  'for',
  'from',
  'get',
  'got',
  'had',
  'has',
  'have',
  'he',
  'her',
  'hers',
  'him',
  'his',
  'how',
  'however',
  'i',
  'if',
  'in',
  'into',
  'is',
  'it',
  'its',
  'just',
  'least',
  'let',
  'like',
  'likely',
  'may',
  'me',
  'might',
  'most',
  'must',
  'my',
  'neither',
  'no',
  'nor',
  'not',
  'of',
  'off',
  'often',
  'on',
  'only',
  'or',
  'other',
  'our',
  'own',
  'rather',
  'said',
  'say',
  'says',
  'she',
  'should',
  'since',
  'so',
  'some',
  'than',
  'that',
  'the',
  'their',
  'them',
  'then',
  'there',
  'these',
  'they',
  'this',
  'tis',
  'to',
  'too',
  'twas',
  'us',
  'wants',
  'was',
  'we',
  'were',
  'what',
  'when',
  'where',
  'which',
  'while',
  'who',
  'whom',
  'why',
  'will',
  'with',
  'would',
  'yet',
  'you',
  'your'
])

lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')


<|start_filename|>lib/idf.js<|end_filename|>
/**
 * A function to calculate the inverse document frequency for
 * a posting. This is shared between the builder and the index
 *
 * @private
 * @param {object} posting - The posting for a given term
 * @param {number} documentCount - The total number of documents.
 */
lunr.idf = function (posting, documentCount) {
  var documentsWithTerm = 0

  for (var fieldName in posting) {
    if (fieldName == '_index') continue // Ignore the term index, its not a field
    documentsWithTerm += Object.keys(posting[fieldName]).length
  }

  var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5)

  return Math.log(1 + Math.abs(x))
}



<|start_filename|>lib/vector.js<|end_filename|>
/*!
 * lunr.Vector
 * Copyright (C) @YEAR <NAME>
 */

/**
 * A vector is used to construct the vector space of documents and queries. These
 * vectors support operations to determine the similarity between two documents or
 * a document and a query.
 *
 * Normally no parameters are required for initializing a vector, but in the case of
 * loading a previously dumped vector the raw elements can be provided to the constructor.
 *
 * For performance reasons vectors are implemented with a flat array, where an elements
 * index is immediately followed by its value. E.g. [index, value, index, value]. This
 * allows the underlying array to be as sparse as possible and still offer decent
 * performance when being used for vector calculations.
 *
 * @constructor
 * @param {Number[]} [elements] - The flat list of element index and element value pairs.
 */
lunr.Vector = function (elements) {
  this._magnitude = 0
  this.elements = elements || []
}


/**
 * Calculates the position within the vector to insert a given index.
 *
 * This is used internally by insert and upsert. If there are duplicate indexes then
 * the position is returned as if the value for that index were to be updated, but it
 * is the callers responsibility to check whether there is a duplicate at that index
 *
 * @param {Number} insertIdx - The index at which the element should be inserted.
 * @returns {Number}
 */
lunr.Vector.prototype.positionForIndex = function (index) {
  // For an empty vector the tuple can be inserted at the beginning
  if (this.elements.length == 0) {
    return 0
  }

  var start = 0,
      end = this.elements.length / 2,
      sliceLength = end - start,
      pivotPoint = Math.floor(sliceLength / 2),
      pivotIndex = this.elements[pivotPoint * 2]

  while (sliceLength > 1) {
    if (pivotIndex < index) {
      start = pivotPoint
    }

    if (pivotIndex > index) {
      end = pivotPoint
    }

    if (pivotIndex == index) {
      break
    }

    sliceLength = end - start
    pivotPoint = start + Math.floor(sliceLength / 2)
    pivotIndex = this.elements[pivotPoint * 2]
  }

  if (pivotIndex == index) {
    return pivotPoint * 2
  }

  if (pivotIndex > index) {
    return pivotPoint * 2
  }

  if (pivotIndex < index) {
    return (pivotPoint + 1) * 2
  }
}

/**
 * Inserts an element at an index within the vector.
 *
 * Does not allow duplicates, will throw an error if there is already an entry
 * for this index.
 *
 * @param {Number} insertIdx - The index at which the element should be inserted.
 * @param {Number} val - The value to be inserted into the vector.
 */
lunr.Vector.prototype.insert = function (insertIdx, val) {
  this.upsert(insertIdx, val, function () {
    throw "duplicate index"
  })
}

/**
 * Inserts or updates an existing index within the vector.
 *
 * @param {Number} insertIdx - The index at which the element should be inserted.
 * @param {Number} val - The value to be inserted into the vector.
 * @param {function} fn - A function that is called for updates, the existing value and the
 * requested value are passed as arguments
 */
lunr.Vector.prototype.upsert = function (insertIdx, val, fn) {
  this._magnitude = 0
  var position = this.positionForIndex(insertIdx)

  if (this.elements[position] == insertIdx) {
    this.elements[position + 1] = fn(this.elements[position + 1], val)
  } else {
    this.elements.splice(position, 0, insertIdx, val)
  }
}

/**
 * Calculates the magnitude of this vector.
 *
 * @returns {Number}
 */
lunr.Vector.prototype.magnitude = function () {
  if (this._magnitude) return this._magnitude

  var sumOfSquares = 0,
      elementsLength = this.elements.length

  for (var i = 1; i < elementsLength; i += 2) {
    var val = this.elements[i]
    sumOfSquares += val * val
  }

  return this._magnitude = Math.sqrt(sumOfSquares)
}

/**
 * Calculates the dot product of this vector and another vector.
 *
 * @param {lunr.Vector} otherVector - The vector to compute the dot product with.
 * @returns {Number}
 */
lunr.Vector.prototype.dot = function (otherVector) {
  var dotProduct = 0,
      a = this.elements, b = otherVector.elements,
      aLen = a.length, bLen = b.length,
      aVal = 0, bVal = 0,
      i = 0, j = 0

  while (i < aLen && j < bLen) {
    aVal = a[i], bVal = b[j]
    if (aVal < bVal) {
      i += 2
    } else if (aVal > bVal) {
      j += 2
    } else if (aVal == bVal) {
      dotProduct += a[i + 1] * b[j + 1]
      i += 2
      j += 2
    }
  }

  return dotProduct
}

/**
 * Calculates the similarity between this vector and another vector.
 *
 * @param {lunr.Vector} otherVector - The other vector to calculate the
 * similarity with.
 * @returns {Number}
 */
lunr.Vector.prototype.similarity = function (otherVector) {
  return this.dot(otherVector) / this.magnitude() || 0
}

/**
 * Converts the vector to an array of the elements within the vector.
 *
 * @returns {Number[]}
 */
lunr.Vector.prototype.toArray = function () {
  var output = new Array (this.elements.length / 2)

  for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) {
    output[j] = this.elements[i]
  }

  return output
}

/**
 * A JSON serializable representation of the vector.
 *
 * @returns {Number[]}
 */
lunr.Vector.prototype.toJSON = function () {
  return this.elements
}


<|start_filename|>lib/query_parse_error.js<|end_filename|>
lunr.QueryParseError = function (message, start, end) {
  this.name = "QueryParseError"
  this.message = message
  this.start = start
  this.end = end
}

lunr.QueryParseError.prototype = new Error


<|start_filename|>test/tokenizer_test.js<|end_filename|>
suite('lunr.tokenizer', function () {
  var toString = function (o) { return o.toString() }

  test('splitting into tokens', function () {
    var tokens = lunr.tokenizer('foo bar baz')
      .map(toString)

    assert.sameMembers(['foo', 'bar', 'baz'], tokens)
  })

  test('downcases tokens', function () {
    var tokens = lunr.tokenizer('Foo BAR BAZ')
      .map(toString)

    assert.sameMembers(['foo', 'bar', 'baz'], tokens)
  })

  test('array of strings', function () {
    var tokens = lunr.tokenizer(['foo', 'bar', 'baz'])
      .map(toString)

    assert.sameMembers(['foo', 'bar', 'baz'], tokens)
  })

  test('undefined is converted to empty string', function () {
    var tokens = lunr.tokenizer(['foo', undefined, 'baz'])
      .map(toString)

    assert.sameMembers(['foo', '', 'baz'], tokens)
  })

  test('null is converted to empty string', function () {
    var tokens = lunr.tokenizer(['foo', null, 'baz'])
      .map(toString)

    assert.sameMembers(['foo', '', 'baz'], tokens)
  })

  test('multiple white space is stripped', function () {
    var tokens = lunr.tokenizer('   foo    bar   baz  ')
      .map(toString)

    assert.sameMembers(['foo', 'bar', 'baz'], tokens)
  })

  test('handling null-like arguments', function () {
    assert.lengthOf(lunr.tokenizer(), 0)
    assert.lengthOf(lunr.tokenizer(undefined), 0)
    assert.lengthOf(lunr.tokenizer(null), 0)
  })

  test('converting a date to tokens', function () {
    var date = new Date(Date.UTC(2013, 0, 1, 12))

    // NOTE: slicing here to prevent asserting on parts
    // of the date that might be affected by the timezone
    // the test is running in.
    assert.sameMembers(['tue', 'jan', '01', '2013'], lunr.tokenizer(date).slice(0, 4).map(toString))
  })

  test('converting a number to tokens', function () {
    assert.equal('41', lunr.tokenizer(41).map(toString))
  })

  test('converting a boolean to tokens', function () {
    assert.equal('false', lunr.tokenizer(false).map(toString))
  })

  test('converting an object to tokens', function () {
    var obj = {
      toString: function () { return 'custom object' }
    }

    assert.sameMembers(lunr.tokenizer(obj).map(toString), ['custom', 'object'])
  })

  test('splits strings with hyphens', function () {
    assert.sameMembers(lunr.tokenizer('foo-bar').map(toString), ['foo', 'bar'])
  })

  test('splits strings with hyphens and spaces', function () {
    assert.sameMembers(lunr.tokenizer('foo - bar').map(toString), ['foo', 'bar'])
  })

  test('tracking the token index', function () {
    var tokens = lunr.tokenizer('foo bar')
    assert.equal(tokens[0].metadata.index, 0)
    assert.equal(tokens[1].metadata.index, 1)
  })

  test('tracking the token position', function () {
    var tokens = lunr.tokenizer('foo bar')
    assert.deepEqual(tokens[0].metadata.position, [0, 3])
    assert.deepEqual(tokens[1].metadata.position, [4, 3])
  })

  test('tracking the token position with additional left-hand whitespace', function () {
    var tokens = lunr.tokenizer(' foo bar')
    assert.deepEqual(tokens[0].metadata.position, [1, 3])
    assert.deepEqual(tokens[1].metadata.position, [5, 3])
  })

  test('tracking the token position with additional right-hand whitespace', function () {
    var tokens = lunr.tokenizer('foo bar ')
    assert.deepEqual(tokens[0].metadata.position, [0, 3])
    assert.deepEqual(tokens[1].metadata.position, [4, 3])
  })

  test('providing additional metadata', function () {
    var tokens = lunr.tokenizer('foo bar', { 'hurp': 'durp' })
    assert.deepEqual(tokens[0].metadata.hurp, 'durp')
    assert.deepEqual(tokens[1].metadata.hurp, 'durp')
  })
})


<|start_filename|>lib/token.js<|end_filename|>
/**
 * A token wraps a string representation of a token
 * as it is passed through the text processing pipeline.
 *
 * @constructor
 * @param {string} [str=''] - The string token being wrapped.
 * @param {object} [metadata={}] - Metadata associated with this token.
 */
lunr.Token = function (str, metadata) {
  this.str = str || ""
  this.metadata = metadata || {}
}

/**
 * Returns the token string that is being wrapped by this object.
 *
 * @returns {string}
 */
lunr.Token.prototype.toString = function () {
  return this.str
}

/**
 * A token update function is used when updating or optionally
 * when cloning a token.
 *
 * @callback lunr.Token~updateFunction
 * @param {string} str - The string representation of the token.
 * @param {Object} metadata - All metadata associated with this token.
 */

/**
 * Applies the given function to the wrapped string token.
 *
 * @example
 * token.update(function (str, metadata) {
 *   return str.toUpperCase()
 * })
 *
 * @param {lunr.Token~updateFunction} fn - A function to apply to the token string.
 * @returns {lunr.Token}
 */
lunr.Token.prototype.update = function (fn) {
  this.str = fn(this.str, this.metadata)
  return this
}

/**
 * Creates a clone of this token. Optionally a function can be
 * applied to the cloned token.
 *
 * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token.
 * @returns {lunr.Token}
 */
lunr.Token.prototype.clone = function (fn) {
  fn = fn || function (s) { return s }
  return new lunr.Token (fn(this.str, this.metadata), this.metadata)
}


<|start_filename|>build/jsdoc.conf.json<|end_filename|>
{
  "plugins": ["plugins/markdown"],
  "destination": "docs",
  "readme": "README.md",
  "templates": {
    "default": {
      "useLongnameInNav": true
    }
  }
}



<|start_filename|>test/stop_word_filter_test.js<|end_filename|>
suite('lunr.stopWordFilter', function () {
  test('filters stop words', function () {
    var stopWords = ['the', 'and', 'but', 'than', 'when']

    stopWords.forEach(function (word) {
      assert.isUndefined(lunr.stopWordFilter(word))
    })
  })

  test('ignores non stop words', function () {
    var nonStopWords = ['interesting', 'words', 'pass', 'through']

    nonStopWords.forEach(function (word) {
      assert.equal(word, lunr.stopWordFilter(word))
    })
  })

  test('ignores properties of Object.prototype', function () {
    var nonStopWords = ['constructor', 'hasOwnProperty', 'toString', 'valueOf']

    nonStopWords.forEach(function (word) {
      assert.equal(word, lunr.stopWordFilter(word))
    })
  })

  test('is a registered pipeline function', function () {
    assert.equal('stopWordFilter', lunr.stopWordFilter.label)
    assert.equal(lunr.stopWordFilter, lunr.Pipeline.registeredFunctions['stopWordFilter'])
  })
})


<|start_filename|>perf/builder_perf.js<|end_filename|>
suite('lunr.Builder', function () {
  var documents = [{
    id: 'a',
    title: '<NAME> kills <NAME>',
    body: 'Mr. Green killed <NAME> in the study with the candlestick. Mr. Green is not a very nice fellow.',
    wordCount: 19
  },{
    id: 'b',
    title: 'Plumb waters plant',
    body: 'Professor Plumb has a green plant in his study',
    wordCount: 9
  },{
    id: 'c',
    title: 'Scarlett helps Professor',
    body: '<NAME> watered <NAME>s green plant while he was away from his office last week.',
    wordCount: 16
  }]

  this.add('build', function () {
    lunr(function () {
      this.ref('id')
      this.field('title')
      this.field('body')

      documents.forEach(function (doc) {
        this.add(doc)
      }, this)
    })
  })
})


<|start_filename|>test/query_test.js<|end_filename|>
suite('lunr.Query', function () {
  var allFields = ['title', 'body']

  suite('#term', function () {
    setup(function () {
      this.query = new lunr.Query (allFields)
    })

    suite('single string term', function () {
      setup(function () {
        this.query.term('foo')
      })

      test('adds a single clause', function () {
        assert.equal(this.query.clauses.length, 1)
      })

      test('clause has the correct term', function () {
        assert.equal(this.query.clauses[0].term, 'foo')
      })
    })

    suite('single token term', function () {
      setup(function () {
        this.query.term(new lunr.Token('foo'))
      })

      test('adds a single clause', function () {
        assert.equal(this.query.clauses.length, 1)
      })

      test('clause has the correct term', function () {
        assert.equal(this.query.clauses[0].term, 'foo')
      })
    })

    suite('multiple string terms', function () {
      setup(function () {
        this.query.term(['foo', 'bar'])
      })

      test('adds a single clause', function () {
        assert.equal(this.query.clauses.length, 2)
      })

      test('clause has the correct term', function () {
        var terms = this.query.clauses.map(function (c) { return c.term })
        assert.sameMembers(terms, ['foo', 'bar'])
      })
    })

    suite('multiple string terms with options', function () {
      setup(function () {
        this.query.term(['foo', 'bar'], { usePipeline: false })
      })

      test('clause has the correct term', function () {
        var terms = this.query.clauses.map(function (c) { return c.term })
        assert.sameMembers(terms, ['foo', 'bar'])
      })
    })

    suite('multiple token terms', function () {
      setup(function () {
        this.query.term(lunr.tokenizer('foo bar'))
      })

      test('adds a single clause', function () {
        assert.equal(this.query.clauses.length, 2)
      })

      test('clause has the correct term', function () {
        var terms = this.query.clauses.map(function (c) { return c.term })
        assert.sameMembers(terms, ['foo', 'bar'])
      })
    })
  })

  suite('#clause', function () {
    setup(function () {
      this.query = new lunr.Query (allFields)
    })

    suite('defaults', function () {
      setup(function () {
        this.query.clause({term: 'foo'})
        this.clause = this.query.clauses[0]
      })

      test('fields', function () {
        assert.sameMembers(this.clause.fields, allFields)
      })

      test('boost', function () {
        assert.equal(this.clause.boost, 1)
      })

      test('usePipeline', function () {
        assert.isTrue(this.clause.usePipeline)
      })
    })

    suite('specified', function () {
      setup(function () {
        this.query.clause({
          term: 'foo',
          boost: 10,
          fields: ['title'],
          usePipeline: false
        })

        this.clause = this.query.clauses[0]
      })

      test('fields', function () {
        assert.sameMembers(this.clause.fields, ['title'])
      })

      test('boost', function () {
        assert.equal(this.clause.boost, 10)
      })

      test('usePipeline', function () {
        assert.isFalse(this.clause.usePipeline)
      })
    })

    suite('wildcards', function () {
      suite('none', function () {
        setup(function () {
          this.query.clause({
            term: 'foo',
            wildcard: lunr.Query.wildcard.NONE
          })

          this.clause = this.query.clauses[0]
        })

        test('no wildcard', function () {
          assert.equal(this.clause.term, 'foo')
        })
      })

      suite('leading', function () {
        setup(function () {
          this.query.clause({
            term: 'foo',
            wildcard: lunr.Query.wildcard.LEADING
          })

          this.clause = this.query.clauses[0]
        })

        test('adds wildcard', function () {
          assert.equal(this.clause.term, '*foo')
        })
      })

      suite('trailing', function () {
        setup(function () {
          this.query.clause({
            term: 'foo',
            wildcard: lunr.Query.wildcard.TRAILING
          })

          this.clause = this.query.clauses[0]
        })

        test('adds wildcard', function () {
          assert.equal(this.clause.term, 'foo*')
        })
      })

      suite('leading and trailing', function () {
        setup(function () {
          this.query.clause({
            term: 'foo',
            wildcard: lunr.Query.wildcard.TRAILING | lunr.Query.wildcard.LEADING
          })

          this.clause = this.query.clauses[0]
        })

        test('adds wildcards', function () {
          assert.equal(this.clause.term, '*foo*')
        })
      })

      suite('existing', function () {
        setup(function () {
          this.query.clause({
            term: '*foo*',
            wildcard: lunr.Query.wildcard.TRAILING | lunr.Query.wildcard.LEADING
          })

          this.clause = this.query.clauses[0]
        })

        test('no additional wildcards', function () {
          assert.equal(this.clause.term, '*foo*')
        })
      })
    })
  })

  suite('#isNegated', function () {
    setup(function () {
      this.query = new lunr.Query (allFields)
    })

    suite('all prohibited', function () {
      setup(function () {
        this.query.term('foo', { presence: lunr.Query.presence.PROHIBITED })
        this.query.term('bar', { presence: lunr.Query.presence.PROHIBITED })
      })

      test('is negated', function () {
        assert.isTrue(this.query.isNegated())
      })
    })

    suite('some prohibited', function () {
      setup(function () {
        this.query.term('foo', { presence: lunr.Query.presence.PROHIBITED })
        this.query.term('bar', { presence: lunr.Query.presence.REQUIRED })
      })

      test('is negated', function () {
        assert.isFalse(this.query.isNegated())
      })
    })

    suite('none prohibited', function () {
      setup(function () {
        this.query.term('foo', { presence: lunr.Query.presence.OPTIONAL })
        this.query.term('bar', { presence: lunr.Query.presence.REQUIRED })
      })

      test('is negated', function () {
        assert.isFalse(this.query.isNegated())
      })
    })
  })
})


<|start_filename|>perf/token_set_perf.js<|end_filename|>
suite('lunr.TokenSet', function () {
  var tokenSet = lunr.TokenSet.fromArray([
    'january', 'february', 'march', 'april',
    'may', 'june', 'july', 'august',
    'september', 'october', 'november', 'december'
  ].sort())

  var noWildcard = lunr.TokenSet.fromString('september')
  var withWildcard = lunr.TokenSet.fromString('*ber')

  this.add('.fromArray', function () {
    lunr.TokenSet.fromArray(words)
  })

  this.add('.fromString (no wildcard)', function () {
    lunr.TokenSet.fromString('javascript')
  })

  this.add('.fromString (with wildcard)', function () {
    lunr.TokenSet.fromString('java*cript')
  })

  this.add('.fromFuzzyString', function () {
    lunr.TokenSet.fromFuzzyString('javascript', 2)
  })

  this.add('#toArray', function () {
    tokenSet.toArray()
  })

  this.add('#toString', function () {
    tokenSet.toString()
  })

  this.add('#intersect (no wildcard)', function () {
    tokenSet.intersect(noWildcard)
  })

  this.add('#intersect (with wildcard)', function () {
    tokenSet.intersect(withWildcard)
  })
})


<|start_filename|>test/field_ref_test.js<|end_filename|>
suite('lunr.FieldRef', function () {
  suite('#toString', function () {
    test('combines document ref and field name', function () {
      var fieldName = "title",
          documentRef = "123",
          fieldRef = new lunr.FieldRef (documentRef, fieldName)

      assert.equal(fieldRef.toString(), "title/123")
    })
  })

  suite('.fromString', function () {
    test('splits string into parts', function () {
      var fieldRef = lunr.FieldRef.fromString("title/123")

      assert.equal(fieldRef.fieldName, "title")
      assert.equal(fieldRef.docRef, "123")
    })

    test('docRef contains join character', function () {
      var fieldRef = lunr.FieldRef.fromString("title/http://example.com/123")

      assert.equal(fieldRef.fieldName, "title")
      assert.equal(fieldRef.docRef, "http://example.com/123")
    })

    test('string does not contain join character', function () {
      var s = "docRefOnly"

      assert.throws(function () {
        lunr.FieldRef.fromString(s)
      })
    })
  })
})


<|start_filename|>perf/perf_helper.js<|end_filename|>
var lunr = require('../lunr.js'),
    Benchmark = require('benchmark'),
    wordList = require('word-list'),
    fs = require('fs')

var suite = function (name, fn) {
  var s = new Benchmark.Suite(name, {
    onStart: function (e) { console.log(e.currentTarget.name) },
    onCycle: function (e) { console.log("  " + String(e.target)) },
    onError: function (e) { console.error(e.target.error) }
  })

  fn.call(s, s)

  s.run()
}

var words = fs.readFileSync(wordList, 'utf-8')
  .split('\n')
  .slice(0, 1000)
  .sort()

global.lunr = lunr
global.Benchmark = Benchmark
global.suite = suite
global.words = words


<|start_filename|>lib/utils.js<|end_filename|>
/*!
 * lunr.utils
 * Copyright (C) @YEAR <NAME>
 */

/**
 * A namespace containing utils for the rest of the lunr library
 * @namespace lunr.utils
 */
lunr.utils = {}

/**
 * Print a warning message to the console.
 *
 * @param {String} message The message to be printed.
 * @memberOf lunr.utils
 * @function
 */
lunr.utils.warn = (function (global) {
  /* eslint-disable no-console */
  return function (message) {
    if (global.console && console.warn) {
      console.warn(message)
    }
  }
  /* eslint-enable no-console */
})(this)

/**
 * Convert an object to a string.
 *
 * In the case of `null` and `undefined` the function returns
 * the empty string, in all other cases the result of calling
 * `toString` on the passed object is returned.
 *
 * @param {Any} obj The object to convert to a string.
 * @return {String} string representation of the passed object.
 * @memberOf lunr.utils
 */
lunr.utils.asString = function (obj) {
  if (obj === void 0 || obj === null) {
    return ""
  } else {
    return obj.toString()
  }
}

/**
 * Clones an object.
 *
 * Will create a copy of an existing object such that any mutations
 * on the copy cannot affect the original.
 *
 * Only shallow objects are supported, passing a nested object to this
 * function will cause a TypeError.
 *
 * Objects with primitives, and arrays of primitives are supported.
 *
 * @param {Object} obj The object to clone.
 * @return {Object} a clone of the passed object.
 * @throws {TypeError} when a nested object is passed.
 * @memberOf Utils
 */
lunr.utils.clone = function (obj) {
  if (obj === null || obj === undefined) {
    return obj
  }

  var clone = Object.create(null),
      keys = Object.keys(obj)

  for (var i = 0; i < keys.length; i++) {
    var key = keys[i],
        val = obj[key]

    if (Array.isArray(val)) {
      clone[key] = val.slice()
      continue
    }

    if (typeof val === 'string' ||
        typeof val === 'number' ||
        typeof val === 'boolean') {
      clone[key] = val
      continue
    }

    throw new TypeError("clone is not deep and does not support nested objects")
  }

  return clone
}


<|start_filename|>test/builder_test.js<|end_filename|>
suite('lunr.Builder', function () {
  suite('#add', function () {
    setup(function () {
      this.builder = new lunr.Builder
    })

    test('field contains terms that clash with object prototype', function () {
      this.builder.field('title')
      this.builder.add({ id: 'id', title: 'constructor'})

      assert.deepProperty(this.builder.invertedIndex, 'constructor.title.id')
      assert.deepEqual(this.builder.invertedIndex.constructor.title.id, {})

      assert.equal(this.builder.fieldTermFrequencies['title/id'].constructor, 1)
    })

    test('field name clashes with object prototype', function () {
      this.builder.field('constructor')
      this.builder.add({ id: 'id', constructor: 'constructor'})

      assert.deepProperty(this.builder.invertedIndex, 'constructor.constructor.id')
      assert.deepEqual(this.builder.invertedIndex.constructor.constructor.id, {})
    })

    test('document ref clashes with object prototype', function () {
      this.builder.field('title')
      this.builder.add({ id: 'constructor', title: 'word'})

      assert.deepProperty(this.builder.invertedIndex, 'word.title.constructor')
      assert.deepEqual(this.builder.invertedIndex.word.title.constructor, {})
    })

    test('token metadata clashes with object prototype', function () {
      var pipelineFunction = function (t) {
        t.metadata['constructor'] = 'foo'
        return t
      }

      lunr.Pipeline.registerFunction(pipelineFunction, 'test')
      this.builder.pipeline.add(pipelineFunction)

      // the registeredFunctions object is global, this is to prevent
      // polluting any other tests.
      delete lunr.Pipeline.registeredFunctions.test

      this.builder.metadataWhitelist.push('constructor')

      this.builder.field('title')
      this.builder.add({ id: 'id', title: 'word'})
      assert.deepProperty(this.builder.invertedIndex, 'word.title.id.constructor')
      assert.deepEqual(this.builder.invertedIndex.word.title.id.constructor, ['foo'])
    })

    test('extracting nested properties from a document', function () {
      var extractor = function (d) { return d.person.name }

      this.builder.field('name', {
        extractor: extractor
      })

      this.builder.add({
        id: 'id',
        person: {
          name: 'bob'
        }
      })

      assert.deepProperty(this.builder.invertedIndex, 'bob.name.id')
    })
  })

  suite('#field', function () {
    test('defining fields to index', function () {
      var builder = new lunr.Builder
      builder.field('foo')
      assert.property(builder._fields, 'foo')
    })

    test('field with illegal characters', function () {
      var builder = new lunr.Builder
      assert.throws(function () {
        builder.field('foo/bar')
      })
    })
  })

  suite('#ref', function () {
    test('default reference', function () {
      var builder = new lunr.Builder
      assert.equal('id', builder._ref)
    })

    test('defining a reference field', function () {
      var builder = new lunr.Builder
      builder.ref('foo')
      assert.equal('foo', builder._ref)
    })
  })

  suite('#b', function () {
    test('default value', function () {
      var builder = new lunr.Builder
      assert.equal(0.75, builder._b)
    })

    test('values less than zero', function () {
      var builder = new lunr.Builder
      builder.b(-1)
      assert.equal(0, builder._b)
    })

    test('values higher than one', function () {
      var builder = new lunr.Builder
      builder.b(1.5)
      assert.equal(1, builder._b)
    })

    test('value within range', function () {
      var builder = new lunr.Builder
      builder.b(0.5)
      assert.equal(0.5, builder._b)
    })
  })

  suite('#k1', function () {
    test('default value', function () {
      var builder = new lunr.Builder
      assert.equal(1.2, builder._k1)
    })

    test('values less than zero', function () {
      var builder = new lunr.Builder
      builder.k1(1.6)
      assert.equal(1.6, builder._k1)
    })
  })

  suite('#use', function () {
    setup(function () {
      this.builder = new lunr.Builder
    })

    test('calls plugin function', function () {
      var wasCalled = false,
          plugin = function () { wasCalled = true }

      this.builder.use(plugin)
      assert.isTrue(wasCalled)
    })

    test('sets context to the builder instance', function () {
      var context = null,
          plugin = function () { context = this }

      this.builder.use(plugin)
      assert.equal(context, this.builder)
    })

    test('passes builder as first argument', function () {
      var arg = null,
          plugin = function (a) { arg = a }

      this.builder.use(plugin)
      assert.equal(arg, this.builder)
    })

    test('forwards arguments to the plugin', function () {
      var args = null,
          plugin = function () { args = [].slice.call(arguments) }

      this.builder.use(plugin, 1, 2, 3)
      assert.deepEqual(args, [this.builder, 1, 2, 3])
    })
  })

  suite('#build', function () {
    setup(function () {
      var builder = new lunr.Builder,
          doc = { id: 'id', title: 'test', body: 'missing' }

      builder.ref('id')
      builder.field('title')
      builder.add(doc)
      builder.build()

      this.builder = builder
    })

    test('adds tokens to invertedIndex', function () {
      assert.deepProperty(this.builder.invertedIndex, 'test.title.id')
    })

    test('builds a vector space of the document fields', function () {
      assert.property(this.builder.fieldVectors, 'title/id')
      assert.instanceOf(this.builder.fieldVectors['title/id'], lunr.Vector)
    })

    test('skips fields not defined for indexing', function () {
      assert.notProperty(this.builder.invertedIndex, 'missing')
    })

    test('builds a token set for the corpus', function () {
      var needle = lunr.TokenSet.fromString('test')
      assert.include(this.builder.tokenSet.intersect(needle).toArray(), 'test')
    })

    test('calculates document count', function () {
      assert.equal(1, this.builder.documentCount)
    })

    test('calculates average field length', function () {
      assert.equal(1, this.builder.averageFieldLength['title'])
    })

    test('index returned', function () {
      var builder = new lunr.Builder,
          doc = { id: 'id', title: 'test', body: 'missing' }

      builder.ref('id')
      builder.field('title')
      builder.add(doc)
      assert.instanceOf(builder.build(), lunr.Index)
    })
  })
})


<|start_filename|>perf/vector_perf.js<|end_filename|>
suite('lunr.Vector', function () {
  var index, val

  var v1 = new lunr.Vector,
      v2 = new lunr.Vector

  for (var i = 0; i < 1000; i++) {
    index = Math.floor(i + Math.random() * 100)
    val = Math.random() * 100
    v1.insert(i, val)
  }

  for (var i = 0; i < 1000; i++) {
    index = Math.floor(i + Math.random() * 100)
    val = Math.random() * 100
    v2.insert(i, val)
  }

  this.add('magnitude', function () {
    v1.magnitude()
  })

  this.add('dot', function () {
    v1.dot(v2)
  })

  this.add('similarity', function () {
    v1.similarity(v2)
  })

})


<|start_filename|>test/test_helper.js<|end_filename|>
var lunr = require('../lunr.js'),
    assert = require('chai').assert,
    fs = require('fs'),
    path = require('path')

var withFixture = function (name, fn) {
  var fixturePath = path.join('test', 'fixtures', name)
  fs.readFile(fixturePath, fn)
}

global.lunr = lunr
global.assert = assert
global.withFixture = withFixture


<|start_filename|>test/token_test.js<|end_filename|>
suite('lunr.Token', function () {
  suite('#toString', function () {
    test('converts the token to a string', function () {
      var token = new lunr.Token('foo')
      assert.equal('foo', token.toString())
    })
  })

  suite('#metadata', function () {
    test('can attach arbitrary metadata', function () {
      var token = new lunr.Token('foo', { length: 3 })
      assert.equal(3, token.metadata.length)
    })
  })

  suite('#update', function () {
    test('can update the token value', function () {
      var token = new lunr.Token('foo')

      token.update(function (s) {
        return s.toUpperCase()
      })

      assert.equal('FOO', token.toString())
    })

    test('metadata is yielded when updating', function () {
      var metadata = { bar: true },
          token = new lunr.Token('foo', metadata),
          yieldedMetadata

      token.update(function (_, md) {
        yieldedMetadata = md
      })

      assert.equal(metadata, yieldedMetadata)
    })
  })

  suite('#clone', function () {
    var token = new lunr.Token('foo', { bar: true })

    test('clones value', function () {
      assert.equal(token.toString(), token.clone().toString())
    })

    test('clones metadata', function () {
      assert.equal(token.metadata, token.clone().metadata)
    })

    test('clone and modify', function () {
      var clone = token.clone(function (s) {
        return s.toUpperCase()
      })

      assert.equal('FOO', clone.toString())
      assert.equal(token.metadata, clone.metadata)
    })
  })
})
