properties

tcmts.cli.semver_checks.properties · #h64fz6zr · term · runs anywhere

Properties gained, lost and changed, for an object type (or a value of one).

import { change, FLAGS } from "#codebase/tcmts/cli/semver_checks";
import type { LintId } from "#codebase/tcmts/cli/semver_checks";
import type { TypeChecker } from "#codebase/tcmts/core/ts";
import { SymbolFlags as ts_SymbolFlags } from "typescript/sync";
import type { Symbol as ts_Symbol, Type as ts_Type } from "typescript/sync";

export function properties(checker: TypeChecker, ot: ts_Type, nt: ts_Type, add: (l: LintId, d?: string) => void) {
  const str = (t: ts_Type) => checker.typeToString(t, undefined, FLAGS);
  const optional = (s: ts_Symbol) => !!(s.flags & ts_SymbolFlags.Optional);
  const readonly = (s: ts_Symbol) => checker.isReadonlySymbol(s);
  const before = new Map(checker.getPropertiesOfType(ot).map((s) => [s.name, s]));
  const after = new Map(checker.getPropertiesOfType(nt).map((s) => [s.name, s]));
  for (const [name, s] of before) {
    const t = after.get(name);
    if (!t) {
      add("type_property_removed", name);
      continue;
    }
    if (!optional(s) && optional(t)) add("type_property_made_optional", name);
    if (optional(s) && !optional(t)) add("type_property_made_required", name);
    if (!readonly(s) && readonly(t)) add("type_property_made_readonly", name);
    if (readonly(s) && !readonly(t)) add("type_property_made_writable", name);
    const [x, y] = [checker.getTypeOfSymbol(s), checker.getTypeOfSymbol(t)];
    if (!checker.isTypeAssignableTo(x, y) || !checker.isTypeAssignableTo(y, x)) add("type_property_type_changed", change(`${name}: `, str(x), str(y)));
  }
  for (const [name, t] of after) if (!before.has(name)) add(optional(t) ? "type_optional_property_added" : "type_required_property_added", name);
}

uses

used by