| 456 | }); |
| 457 | |
| 458 | const scorePreparedField = ( |
| 459 | query: string, |
| 460 | queryTokens: readonly string[], |
| 461 | field: PreparedField, |
| 462 | weight: number, |
| 463 | ): { |
| 464 | readonly score: number; |
| 465 | readonly matchedTokens: ReadonlySet<string>; |
| 466 | readonly exactPhraseMatch: boolean; |
| 467 | } => { |
| 468 | if (field.raw.length === 0) { |
| 469 | return { |
| 470 | score: 0, |
| 471 | matchedTokens: new Set<string>(), |
| 472 | exactPhraseMatch: false, |
| 473 | }; |
| 474 | } |
| 475 | |
| 476 | let score = 0; |
| 477 | const matchedTokens = new Set<string>(); |
| 478 | const exactPhraseMatch = query.length > 0 && field.raw.includes(query); |
| 479 | |
| 480 | if (query.length > 0) { |
| 481 | if (field.raw === query) { |
| 482 | score += weight * 14; |
| 483 | } else if (field.raw.startsWith(query)) { |
| 484 | score += weight * 9; |
| 485 | } else if (exactPhraseMatch) { |
| 486 | score += weight * 6; |
| 487 | } |
| 488 | } |
| 489 | |
| 490 | for (const token of queryTokens) { |
| 491 | if (field.tokens.includes(token)) { |
| 492 | score += weight * 4; |
| 493 | matchedTokens.add(token); |
| 494 | continue; |
| 495 | } |
| 496 | |
| 497 | if ( |
| 498 | field.tokens.some((candidate) => candidate.startsWith(token) || token.startsWith(candidate)) |
| 499 | ) { |
| 500 | score += weight * 2; |
| 501 | matchedTokens.add(token); |
| 502 | continue; |
| 503 | } |
| 504 | |
| 505 | if (field.raw.includes(token)) { |
| 506 | score += weight; |
| 507 | matchedTokens.add(token); |
| 508 | } |
| 509 | } |
| 510 | |
| 511 | return { |
| 512 | score, |
| 513 | matchedTokens, |
| 514 | exactPhraseMatch, |
| 515 | }; |