measureCoverage function

CoverageReport measureCoverage({
  1. required String sampleYaml,
  2. required String metaJson,
  3. required String blueprintYaml,
  4. required String rowsYaml,
  5. required String reasonsYaml,
  6. String? oldBlueprintYaml,
})

Measures blueprintYaml against the model sample sampleYaml and the model metadata metaJson, with the row table rowsYaml and the reason rules reasonsYaml; oldBlueprintYaml, when given, fills the "old" columns.

Implementation

CoverageReport measureCoverage({
  required String sampleYaml,
  required String metaJson,
  required String blueprintYaml,
  required String rowsYaml,
  required String reasonsYaml,
  String? oldBlueprintYaml,
}) {
  // One-of case arms, from the model metadata.
  final meta =
      (jsonDecode(metaJson) as Map<String, dynamic>)['classes']
          as Map<String, dynamic>;
  final caseKeys = <String>{};
  meta.forEach((_, c) {
    final cls = c as Map<String, dynamic>;
    final annotations = (cls['annotations'] as List?) ?? const [];
    if (!annotations.any((a) => (a as Map)['name'] == 'OneOf')) return;
    for (final f in (cls['fields'] as List)) {
      final fm = f as Map<String, dynamic>;
      if (!((fm['annotations'] as List?) ?? const []).any(
        (a) => (a as Map)['name'] == 'Case',
      )) {
        continue;
      }
      final sid =
          fm['sectionId'] ??
          ((meta[fm['type']] as Map<String, dynamic>?)?['sectionId']);
      caseKeys.add('$sid\u0000${fm['name']}');
    }
  });

  // The model's sections, from the sample.
  final model = <String, _ModelSection>{};
  var idless = 0;
  void walkSample(
    Object? node,
    List<String> fpath,
    List<String> ipath,
    bool inList,
    List<String> variant,
    bool refexp,
  ) {
    if (node is! YamlMap) return;
    node.forEach((key, v) {
      final k = '$key';
      if (k == 'content' || k == 'headline') return;
      if (!k.contains(' ')) {
        if (inList) {
          // An item is named by its class id: the sample numbers its items
          // `<class>-<kind>-<n>`, and a reason rule names the class.
          final classId = k.split('-').first;
          walkSample(v, fpath, [...ipath, classId], false, variant, refexp);
        } else if (k[0].toLowerCase() == k[0] && k[0].toUpperCase() != k[0]) {
          idless++;
        }
        return;
      }
      final space = k.indexOf(' ');
      final sid = k.substring(0, space);
      final name = k.substring(space + 1);
      final fp = [...fpath, name];
      final ip = [...ipath, sid];
      final fpKey = fp.join('/');
      final variants = [
        ...variant,
        if (caseKeys.contains('$sid\u0000$name')) fpKey,
      ];
      if (_isBlock(v)) model[fpKey] = _ModelSection(ip, variants, refexp);
      walkSample(
        v,
        fp,
        ip,
        sid.endsWith('-LST'),
        variants,
        refexp || sid.endsWith('-REF'),
      );
    });
  }

  walkSample(_document(sampleYaml), const [], const [], false, const [], false);

  // The Blueprint's paths.
  ({Set<String> present, Set<String> filled, Set<String> block}) walkBlueprint(
    String text,
  ) {
    final present = <String>{}, filled = <String>{}, block = <String>{};
    void w(Object? node, List<String> fpath, bool inList) {
      if (node is! YamlMap) return;
      node.forEach((key, v) {
        final k = '$key';
        if (k == 'content' || k == 'headline') return;
        if (!k.contains(' ')) {
          if (inList) w(v, fpath, false);
          return;
        }
        final space = k.indexOf(' ');
        final sid = k.substring(0, space);
        final fp = [...fpath, k.substring(space + 1)];
        final fpKey = fp.join('/');
        present.add(fpKey);
        if (_isBlock(v)) block.add(fpKey);
        if (_nonEmpty(v)) filled.add(fpKey);
        w(v, fp, sid.endsWith('-LST'));
      });
    }

    w(_document(text), const [], false);
    return (present: present, filled: filled, block: block);
  }

  final bp = walkBlueprint(blueprintYaml);
  final old = oldBlueprintYaml == null ? null : walkBlueprint(oldBlueprintYaml);

  // Rows and rules.
  final report = CoverageReport();
  final prefixes = <String, List<String>>{};
  for (final row in (loadYaml(rowsYaml) as YamlMap)['rows'] as YamlList) {
    final id = '${row['id']}';
    report.rows.add(id);
    report.labels[id] = '${row['label']}';
    prefixes[id] = [for (final p in row['prefixes'] as YamlList) '$p'];
  }
  String owner(String path) {
    String? best;
    var bestLength = -1;
    prefixes.forEach((g, list) {
      for (final pre in list) {
        if ((path == pre || path.startsWith('$pre/')) &&
            pre.length > bestLength) {
          best = g;
          bestLength = pre.length;
        }
      }
    });
    return best!;
  }

  final rules = [
    for (final r in (loadYaml(reasonsYaml) as YamlMap)['rules'] as YamlList)
      (RegExp('${r['pattern']}'), '${r['class']}', '${r['reason']}'),
  ];

  void inc(Map<String, int> m, String g) => m[g] = (m[g] ?? 0) + 1;
  model.forEach((fp, m) {
    final g = owner(fp);
    inc(report.model, g);
    if (bp.filled.contains(fp)) inc(report.filled, g);
    if (bp.block.contains(fp)) inc(report.block, g);
    if (old != null) {
      if (old.block.contains(fp)) inc(report.oldPresent, g);
      if (old.filled.contains(fp)) inc(report.oldFilled, g);
    }
    if (bp.filled.contains(fp)) return;
    var kind = '';
    if (m.referenceExpansion) {
      inc(report.referenceExpansions, g);
      kind = 'R';
    } else if (m.variants.any((a) {
      final parent = a.contains('/') ? a.substring(0, a.lastIndexOf('/')) : '';
      return !bp.filled.contains(a) && bp.filled.contains(parent);
    })) {
      inc(report.variants, g);
      kind = 'V';
    }
    report.unfilled.add(
      UnfilledSection(
        g,
        m.ids.join(' > '),
        fp,
        kind,
        bp.present.contains(fp) ? 'present-empty' : 'absent',
      ),
    );
  });

  int cmp(UnfilledSection a, UnfilledSection b) {
    for (final pair in [
      (a.row, b.row),
      (a.idPath, b.idPath),
      (a.fieldPath, b.fieldPath),
      (a.kind, b.kind),
      (a.state, b.state),
    ]) {
      final c = pair.$1.compareTo(pair.$2);
      if (c != 0) return c;
    }
    return 0;
  }

  report.unfilled.sort(cmp);
  for (final u in report.unfilled) {
    for (final (pattern, cls, why) in rules) {
      if (pattern.hasMatch(u.idPath)) {
        u.cls = cls;
        u.reason = why;
        break;
      }
    }
    if (u.kind == 'R') {
      u.cls = 'R';
      u.reason = 'sample-only reference expansion';
    }
    if (u.kind == 'V') {
      u.cls = 'V';
      u.reason = 'variant arm (OneOf case) unused by any item; ${u.reason}';
    }
    final counts = report.classes.putIfAbsent(u.row, () => {});
    counts[u.cls] = (counts[u.cls] ?? 0) + 1;
  }
  report.idlessSampleSections = idless;
  return report;
}