| 523 | }); |
| 524 | |
| 525 | const scorePreparedField = ( |
| 526 | query: string, |
| 527 | queryTokens: readonly string[], |
| 528 | field: PreparedField, |
| 529 | weight: number, |
| 530 | ): { |
| 531 | readonly score: number; |
| 532 | readonly matchedTokens: ReadonlySet<string>; |
| 533 | readonly exactPhraseMatch: boolean; |
| 534 | } => { |
| 535 | if (field.raw.length === 0) { |
| 536 | return { |
| 537 | score: 0, |
| 538 | matchedTokens: new Set<string>(), |
| 539 | exactPhraseMatch: false, |
| 540 | }; |
| 541 | } |
| 542 | |
| 543 | let score = 0; |
| 544 | const matchedTokens = new Set<string>(); |
| 545 | const exactPhraseMatch = query.length > 0 && field.raw.includes(query); |
| 546 | |
| 547 | if (query.length > 0) { |
| 548 | if (field.raw === query) { |
| 549 | score += weight * 14; |
| 550 | } else if (field.raw.startsWith(query)) { |
| 551 | score += weight * 9; |
| 552 | } else if (exactPhraseMatch) { |
| 553 | score += weight * 6; |
| 554 | } |
| 555 | } |
| 556 | |
| 557 | for (const token of queryTokens) { |
| 558 | if (field.tokens.includes(token)) { |
| 559 | score += weight * 4; |
| 560 | matchedTokens.add(token); |
| 561 | continue; |
| 562 | } |
| 563 | |
| 564 | if ( |
| 565 | field.tokens.some((candidate) => candidate.startsWith(token) || token.startsWith(candidate)) |
| 566 | ) { |
| 567 | score += weight * 2; |
| 568 | matchedTokens.add(token); |
| 569 | continue; |
| 570 | } |
| 571 | |
| 572 | if (field.raw.includes(token)) { |
| 573 | score += weight; |
| 574 | matchedTokens.add(token); |
| 575 | } |
| 576 | } |
| 577 | |
| 578 | return { |
| 579 | score, |
| 580 | matchedTokens, |
| 581 | exactPhraseMatch, |
| 582 | }; |