sanitizeCrashingLayers function
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,
);
}