lintDefinitions

tcmts.cli.lint.lintDefinitions · #o7qrwv4l · term · runs on the server

uses node:crypto

Lint stored definitions, as tcm view shows them (current names, imports, export), cached by hash and the linter's version and configuration. fresh lints again regardless.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { apartFromItsCycle, configFor, lintTexts } from "#codebase/tcmts/cli/lint";
import type { DefinitionLint, LintIssue, LintTraits } from "#codebase/tcmts/cli/lint";
import { isTestPath } from "#codebase/tcmts/cli/testing";
import { render } from "#codebase/tcmts/loader";
import { createHash } from "node:crypto";

export function lintDefinitions(
  cb: Codebase,
  targets: { hash: string; path: string }[],
  fresh = false,
): DefinitionLint[] {
  const src = cb.source();
  const out: DefinitionLint[] = [];
  const pending = new Map<
    string,
    { hash: string; path: string; text: string; ext: string; traits: LintTraits; key: string }[]
  >();
  for (const t of targets) {
    const rec = src.get(t.hash);
    if (!rec || rec.kind === "type") continue;
    const text = render([rec], src, cb.lay, { style: "scratch", exportDeclarations: true }).text;
    const traits: LintTraits = {
      jsx: !!rec.jsx,
      tests: isTestPath(t.path),
      node: rec.spans.some((s) => s.ext?.module.startsWith("node:")),
    };
    // findings depend on the text as well as the code: names (an unused `_x`) and comments (`eslint-disable`), which
    // a hash doesn't cover
    const key = `${configFor(cb, traits).key} ${createHash("sha256").update(text).digest("hex").slice(0, 16)}`;
    const hit = fresh ? undefined : cb.store.lintResult(t.hash, key);
    if (hit !== undefined) {
      out.push({ ...t, text, issues: JSON.parse(hit) as LintIssue[], cached: true });
      continue;
    }
    const group = JSON.stringify(traits);
    pending.set(group, [
      ...(pending.get(group) ?? []),
      { ...t, text, ext: rec.jsx ? ".tsx" : ".ts", traits, key },
    ]);
  }
  for (const items of pending.values()) {
    const results = lintTexts(cb, items, items[0]!.traits);
    items.forEach((item, i) => {
      // (what only comes of linting it apart from the definitions it's one module with isn't kept)
      const rec = src.get(item.hash)!;
      const mates = new Set(
        cb.store
          .componentMembers(rec.componentHash)
          .filter((m) => m.hash !== item.hash)
          .map((m) => (src.nameOf(m.hash) ?? m.nameHint).split(".").pop()!),
      );
      const issues = results[i]!.filter((issue) => !apartFromItsCycle(issue, item.text, mates));
      cb.store.putLintResult(item.hash, item.key, JSON.stringify(issues));
      out.push({ hash: item.hash, path: item.path, text: item.text, issues, cached: false });
    });
  }
  return out.sort((a, b) => (a.path < b.path ? -1 : 1));
}

uses

used by