build/lib/mangleTypeScript.ts/Mangler/computeNewFileContents
class Mangler:	computeNewFileContents(): Map<string, string> {

		// STEP: find all classes and their field info

		const visit = (node: ts.Node): void => {
			if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
				const anchor = node.name ?? node;
				const key = `${node.getSourceFile().fileName}|${anchor.getStart()}`;
				if (this.allClassDataByKey.has(key)) {
					throw new Error('DUPE?');
				}
				this.allClassDataByKey.set(key, new ClassData(node.getSourceFile().fileName, node));
			}
			ts.forEachChild(node, visit);
		};

		for (const file of this.service.getProgram()!.getSourceFiles()) {
			if (!file.isDeclarationFile) {
				ts.forEachChild(file, visit);
			}
		}
		this.log(`done COLLECTING ${this.allClassDataByKey.size} classes`);


		//  STEP: connect sub and super-types

		const setupParents = (data: ClassData) => {
			const extendsClause = data.node.heritageClauses?.find(h => h.token === ts.SyntaxKind.ExtendsKeyword);
			if (!extendsClause) {
				// no EXTENDS-clause
				return;
			}

			const info = this.service.getDefinitionAtPosition(data.fileName, extendsClause.types[0].expression.getEnd());
			if (!info || info.length === 0) {
				// throw new Error('SUPER type not found');
				return;
			}

			if (info.length !== 1) {
				// inherits from declared/library type
				return;
			}

			const [definition] = info;
			const key = `${definition.fileName}|${definition.textSpan.start}`;
			const parent = this.allClassDataByKey.get(key);
			if (!parent) {
				// throw new Error(`SUPER type not found: ${key}`);
				return;
			}
			parent.addChild(data);
		};
		for (const data of this.allClassDataByKey.values()) {
			setupParents(data);
		}

		//  STEP: make implicit public (actually protected) field really public
		const violations = new Map<string, string[]>();
		for (const data of this.allClassDataByKey.values()) {
			ClassData.makeImplicitPublicActuallyPublic(data, (what, why) => {
				const arr = violations.get(what);
				if (arr) {
					arr.push(why);
				} else {
					violations.set(what, [why]);
				}
			});
		}
		for (const [why, whys] of violations) {
			this.log(`WARN: ${why} became PUBLIC because of: ${whys.join(' , ')}`);
		}

		// STEP: compute replacement names for each class
		for (const data of this.allClassDataByKey.values()) {
			ClassData.fillInReplacement(data);
		}
		this.log(`done creating REPLACEMENTS`);

		// STEP: prepare rename edits
		type Edit = { newText: string; offset: number; length: number };
		const editsByFile = new Map<string, Edit[]>();

		const appendEdit = (fileName: string, edit: Edit) => {
			const edits = editsByFile.get(fileName);
			if (!edits) {
				editsByFile.set(fileName, [edit]);
			} else {
				edits.push(edit);
			}
		};

		for (const data of this.allClassDataByKey.values()) {

			if (hasModifier(data.node, ts.SyntaxKind.DeclareKeyword)) {
				continue;
			}

			fields: for (const [name, info] of data.fields) {
				if (!ClassData._shouldMangle(info.type)) {
					continue fields;
				}

				// TS-HACK: protected became public via 'some' child
				// and because of that we might need to ignore this now
				let parent = data.parent;
				while (parent) {
					if (parent.fields.get(name)?.type === FieldType.Public) {
						continue fields;
					}
					parent = parent.parent;
				}

				const newText = data.lookupShortName(name);
				const locations = this.service.findRenameLocations(data.fileName, info.pos, false, false, true) ?? [];
				for (const loc of locations) {
					appendEdit(loc.fileName, {
						newText: (loc.prefixText || '') + newText + (loc.suffixText || ''),
						offset: loc.textSpan.start,
						length: loc.textSpan.length
					});
				}
			}
		}

		this.log(`done preparing EDITS for ${editsByFile.size} files`);

		// STEP: apply all rename edits (per file)
		const result = new Map<string, string>();
		let savedBytes = 0;

		for (const item of this.service.getProgram()!.getSourceFiles()) {

			let newFullText: string;
			const edits = editsByFile.get(item.fileName);
			if (!edits) {
				// just copy
				newFullText = item.getFullText();

			} else {
				// apply renames
				edits.sort((a, b) => b.offset - a.offset);
				const characters = item.getFullText().split('');

				let lastEdit: Edit | undefined;

				for (const edit of edits) {
					if (lastEdit) {
						if (lastEdit.offset === edit.offset) {
							//
							if (lastEdit.length !== edit.length || lastEdit.newText !== edit.newText) {
								this.log('OVERLAPPING edit', item.fileName, edit.offset, edits);
								throw new Error('OVERLAPPING edit');
							} else {
								continue;
							}
						}
					}
					lastEdit = edit;
					const removed = characters.splice(edit.offset, edit.length, edit.newText);
					savedBytes += removed.length - edit.newText.length;
				}
				newFullText = characters.join('');
			}
			result.set(item.fileName, newFullText);
		}

		this.log(`DONE saved ${savedBytes / 1000}kb`);
		return result;
	}