testSearchCode

tcmts.cli.pages.testSearchCode · #ofe5nx3a · term

Code search finds text in definitions as they read now (current names), and in pages, by line; a regular expression (written /…/, or all the text with regex), whole words and case work as an editor's do; under keeps to a namespace or a definition, and it says it stopped early only when there was more to search.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { searchCode, searchTexts, searchTextsKey } from "#codebase/tcmts/cli/pages";
import type { SearchOptions } from "#codebase/tcmts/cli/pages";
import assert from "node:assert/strict";

export const testSearchCode = () => {
  const texts = [
    {
      path: "lib.square",
      hash: "h1",
      kind: "term",
      text: "// lib.square #h1\nexport const square = (x: number): number => x * x;\n",
      first: 1,
    },
    {
      path: "docs.guide",
      hash: "h2",
      kind: "file",
      text: "# Guide\n\nSquare things with `square`.\n",
      first: 0,
    },
  ];
  const codebase = (db: string, branch: string, head: number, sessionDir?: string) =>
    ({
      lay: { dbFile: db },
      sessionDir,
      store: { branch: () => ({ name: branch }), logHead: () => head },
    }) as unknown as Codebase;
  const lines = (q: string, opts: SearchOptions = {}, these = texts) => {
    const cb = codebase("db", "b", 0);
    searchTexts.clear();
    searchTexts.set(searchTextsKey(cb), these);
    return searchCode(cb, q, opts, (h) => `file:///${h}`);
  };
  // the texts are of one codebase's branch, as of its last history entry (a text version shown is one), and whether
  // documents are read (not in a session)
  const keys = [
    codebase("db", "b", 0),
    codebase("other", "b", 0),
    codebase("db", "main", 0),
    codebase("db", "b", 1),
    codebase("db", "b", 0, "session"),
  ].map(searchTextsKey);
  assert.equal(new Set(keys).size, keys.length, keys.join(", "));
  const r = lines("square");
  assert.deepEqual(
    r.hits.map((h) => [h.path, h.lines.map((l) => l.line)]),
    [
      ["lib.square", [1]],
      ["docs.guide", [2]],
    ],
  );
  assert.equal(r.matches, 3);
  assert.equal(r.hits[0]!.uri, "file:///h1");
  assert.equal(r.hits[1]!.uri, undefined);
  assert.deepEqual(r.hits[0]!.lines[0]!.ranges, [[13, 19]]);
  // the header line, with the name in it, isn't searched
  assert.equal(lines("lib.square").matches, 0);
  assert.equal(lines("Square", { caseSensitive: true }).matches, 1);
  assert.equal(lines("squar", { wholeWord: true }).matches, 0);
  assert.equal(lines("x \\* x", { regex: true }).matches, 1);
  assert.match(lines("(", { regex: true }).error ?? "", /Invalid regular expression/);
  // a regular expression written /…/, its case as its flags say; `(` is text
  assert.equal(lines("/x \\* x/").matches, 1);
  assert.equal(lines("/SQUARE/").matches, 0);
  assert.equal(lines("/SQUARE|guide/i").matches, 4);
  assert.equal(lines("(").error, undefined);
  // under a namespace, or a definition; not a prefix of a name
  assert.deepEqual(
    lines("square", { under: "docs" }).hits.map((h) => h.path),
    ["docs.guide"],
  );
  assert.deepEqual(
    lines("square", { under: "lib.square" }).hits.map((h) => h.path),
    ["lib.square"],
  );
  assert.deepEqual(lines("square", { under: "lib.sq" }).hits, []);
  // it stops at 500 definitions: truncated only when there was more to search
  const many = (n: number) =>
    Array.from({ length: n }, (_, i) => ({
      path: `ns.d${i}`,
      hash: `h${i}`,
      kind: "term",
      text: "// ns\nconst a = 1;\n",
      first: 1,
    }));
  assert.equal(lines("a", {}, many(500)).truncated, false);
  const over = lines("a", {}, many(501));
  assert.deepEqual([over.truncated, over.hits.length], [true, 500]);
  assert.deepEqual(
    [
      lines("a", { under: "ns.d1" }, many(501)).truncated,
      lines("a", { under: "ns.d1" }, many(501)).hits.length,
    ],
    [false, 1],
  );
}

uses