sanitizeCrashingLayers function

SanitizeResult sanitizeCrashingLayers(
  1. Map<String, dynamic> doc
)

Removes crashing/dead layers from doc in place and prunes assets left unreferenced by that removal.

Implementation

SanitizeResult sanitizeCrashingLayers(Map<String, dynamic> doc) {
  var audioRemoved = 0;
  void stripAudioLayers(List<dynamic> layers) {
    final before = layers.length;
    layers.removeWhere((l) => l is Map && l['ty'] == audioLayerType);
    audioRemoved += before - layers.length;
  }

  stripAudioLayers(_layersOf(doc));
  // Mutated in place (removeWhere on the very list `doc['assets']` already
  // holds) rather than rebuilt and reassigned: callers may pass a document
  // with a more narrowly-typed nested list than plain `List<dynamic>` (any
  // `jsonDecode` output is fine, but a hand-built Map literal, e.g. in
  // tests, can infer a stricter generic type), and assigning a freshly
  // built `List<...>` back into `doc['assets']` would then fail Dart's
  // runtime covariant-generic check.
  final assets = (doc['assets'] as List?) ?? [];
  for (final asset in assets) {
    if (asset is Map && asset['layers'] is List) {
      stripAudioLayers(asset['layers'] as List);
    }
  }

  // Only remove an asset once no layer refId's it anymore — even if it's
  // empty: a preComp layer may intentionally point to an empty precomp (an
  // AE-exported placeholder). Wrongly removing a still-referenced asset
  // leaves a dangling `refId`, causing `composition.getPrecomps(refId)!` to
  // crash while building the render tree — later, and quite different from
  // the audio layer's null-check error, so it doesn't surface at parse
  // time but only when actually rendering.
  final reachable = _reachableAssetIds(_layersOf(doc), assets);
  var emptyPrecompsRemoved = 0;
  var unreferencedRemoved = 0;
  assets.removeWhere((a) {
    if (a is! Map || a['id'] is! String || reachable.contains(a['id'])) {
      return false;
    }
    if (a['layers'] is List && (a['layers'] as List).isEmpty) {
      emptyPrecompsRemoved++;
    } else {
      unreferencedRemoved++;
    }
    return true;
  });

  final missingTransform = <String>[];
  void scanMissingTransform(List<dynamic> layers, String where) {
    for (final l in layers) {
      if (l is Map && !l.containsKey('ks')) {
        missingTransform.add('$where: ty=${l['ty']} nm=${l['nm']}');
      }
    }
  }

  scanMissingTransform(_layersOf(doc), 'root');
  for (final asset in assets) {
    if (asset is Map && asset['layers'] is List) {
      scanMissingTransform(asset['layers'] as List, 'asset ${asset['id']}');
    }
  }

  return SanitizeResult(
    audioLayersRemoved: audioRemoved,
    emptyPrecompsRemoved: emptyPrecompsRemoved,
    unreferencedAssetsRemoved: unreferencedRemoved,
    layersMissingTransform: missingTransform,
  );
}