build/lib/treeshaking.ts/loop
function markNodes(ts: typeof import('typescript'), languageService: ts.LanguageService, options: ITreeShakingOptions) {
	const program = languageService.getProgram();
	if (!program) {
		throw new Error('Could not get program from language service');
	}

	if (options.shakeLevel === ShakeLevel.Files) {
		// Mark all source files Black
		program.getSourceFiles().forEach((sourceFile) => {
			setColor(sourceFile, NodeColor.Black);
		});
		return;
	}

	const black_queue: ts.Node[] = [];
	const gray_queue: ts.Node[] = [];
	const export_import_queue: ts.Node[] = [];
	const sourceFilesLoaded: { [fileName: string]: boolean } = {};

	function enqueueTopLevelModuleStatements(sourceFile: ts.SourceFile): void {

		sourceFile.forEachChild((node: ts.Node) => {

			if (ts.isImportDeclaration(node)) {
				if (!node.importClause && ts.isStringLiteral(node.moduleSpecifier)) {
					setColor(node, NodeColor.Black);
					enqueueImport(node, node.moduleSpecifier.text);
				}
				return;
			}

			if (ts.isExportDeclaration(node)) {
				if (!node.exportClause && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
					// export * from "foo";
					setColor(node, NodeColor.Black);
					enqueueImport(node, node.moduleSpecifier.text);
				}
				if (node.exportClause && ts.isNamedExports(node.exportClause)) {
					for (const exportSpecifier of node.exportClause.elements) {
						export_import_queue.push(exportSpecifier);
					}
				}
				return;
			}

			if (isVariableStatementWithSideEffects(ts, node)) {
				enqueue_black(node);
			}

			if (
				ts.isExpressionStatement(node)
				|| ts.isIfStatement(node)
				|| ts.isIterationStatement(node, true)
				|| ts.isExportAssignment(node)
			) {
				enqueue_black(node);
			}

			if (ts.isImportEqualsDeclaration(node)) {
				if (/export/.test(node.getFullText(sourceFile))) {
					// e.g. "export import Severity = BaseSeverity;"
					enqueue_black(node);
				}
			}

		});
	}

	/**
	 * Return the parent of `node` which is an ImportDeclaration
	 */
	function findParentImportDeclaration(node: ts.Declaration): ts.ImportDeclaration | null {
		let _node: ts.Node = node;
		do {
			if (ts.isImportDeclaration(_node)) {
				return _node;
			}
			_node = _node.parent;
		} while (_node);
		return null;
	}

	function enqueue_gray(node: ts.Node): void {
		if (nodeOrParentIsBlack(node) || getColor(node) === NodeColor.Gray) {
			return;
		}
		setColor(node, NodeColor.Gray);
		gray_queue.push(node);
	}

	function enqueue_black(node: ts.Node): void {
		const previousColor = getColor(node);

		if (previousColor === NodeColor.Black) {
			return;
		}

		if (previousColor === NodeColor.Gray) {
			// remove from gray queue
			gray_queue.splice(gray_queue.indexOf(node), 1);
			setColor(node, NodeColor.White);

			// add to black queue
			enqueue_black(node);

			// move from one queue to the other
			// black_queue.push(node);
			// setColor(node, NodeColor.Black);
			return;
		}

		if (nodeOrParentIsBlack(node)) {
			return;
		}

		const fileName = node.getSourceFile().fileName;
		if (/^defaultLib:/.test(fileName) || /\.d\.ts$/.test(fileName)) {
			setColor(node, NodeColor.Black);
			return;
		}

		const sourceFile = node.getSourceFile();
		if (!sourceFilesLoaded[sourceFile.fileName]) {
			sourceFilesLoaded[sourceFile.fileName] = true;
			enqueueTopLevelModuleStatements(sourceFile);
		}

		if (ts.isSourceFile(node)) {
			return;
		}

		setColor(node, NodeColor.Black);
		black_queue.push(node);

		if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) {
			const references = languageService.getReferencesAtPosition(node.getSourceFile().fileName, node.name.pos + node.name.getLeadingTriviaWidth());
			if (references) {
				for (let i = 0, len = references.length; i < len; i++) {
					const reference = references[i];
					const referenceSourceFile = program!.getSourceFile(reference.fileName);
					if (!referenceSourceFile) {
						continue;
					}

					const referenceNode = getTokenAtPosition(ts, referenceSourceFile, reference.textSpan.start, false, false);
					if (
						ts.isMethodDeclaration(referenceNode.parent)
						|| ts.isPropertyDeclaration(referenceNode.parent)
						|| ts.isGetAccessor(referenceNode.parent)
						|| ts.isSetAccessor(referenceNode.parent)
					) {
						enqueue_gray(referenceNode.parent);
					}
				}
			}
		}
	}

	function enqueueFile(filename: string): void {
		const sourceFile = program!.getSourceFile(filename);
		if (!sourceFile) {
			console.warn(`Cannot find source file ${filename}`);
			return;
		}
		// This source file should survive even if it is empty
		markNeededSourceFile(sourceFile);
		enqueue_black(sourceFile);
	}

	function enqueueImport(node: ts.Node, importText: string): void {
		if (options.importIgnorePattern.test(importText)) {
			// this import should be ignored
			return;
		}

		const nodeSourceFile = node.getSourceFile();
		let fullPath: string;
		if (/(^\.\/)|(^\.\.\/)/.test(importText)) {
			fullPath = path.join(path.dirname(nodeSourceFile.fileName), importText) + '.ts';
		} else {
			fullPath = importText + '.ts';
		}
		enqueueFile(fullPath);
	}

	options.entryPoints.forEach(moduleId => enqueueFile(moduleId + '.ts'));
	// Add fake usage files
	options.inlineEntryPoints.forEach((_, index) => enqueueFile(`inlineEntryPoint.${index}.ts`));

	let step = 0;

	const checker = program.getTypeChecker();
	while (black_queue.length > 0 || gray_queue.length > 0) {
		++step;
		let node: ts.Node;

		if (step % 100 === 0) {
			console.log(`Treeshaking - ${Math.floor(100 * step / (step + black_queue.length + gray_queue.length))}% - ${step}/${step + black_queue.length + gray_queue.length} (${black_queue.length}, ${gray_queue.length})`);
		}

		if (black_queue.length === 0) {
			for (let i = 0; i < gray_queue.length; i++) {
				const node = gray_queue[i];
				const nodeParent = node.parent;
				if ((ts.isClassDeclaration(nodeParent) || ts.isInterfaceDeclaration(nodeParent)) && nodeOrChildIsBlack(nodeParent)) {
					gray_queue.splice(i, 1);
					black_queue.push(node);
					setColor(node, NodeColor.Black);
					i--;
				}
			}
		}

		if (black_queue.length > 0) {
			node = black_queue.shift()!;
		} else {
			// only gray nodes remaining...
			break;
		}
		const nodeSourceFile = node.getSourceFile();

		const loop = (node: ts.Node) => {
			const symbols = getRealNodeSymbol(ts, checker, node);
			for (const { symbol, symbolImportNode } of symbols) {
				if (symbolImportNode) {
					setColor(symbolImportNode, NodeColor.Black);
					const importDeclarationNode = findParentImportDeclaration(symbolImportNode);
					if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) {
						enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text);
					}
				}

				if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) {
					for (let i = 0, len = symbol.declarations.length; i < len; i++) {
						const declaration = symbol.declarations[i];
						if (ts.isSourceFile(declaration)) {
							// Do not enqueue full source files
							// (they can be the declaration of a module import)
							continue;
						}

						if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) {
							enqueue_black(declaration.name!);

							for (let j = 0; j < declaration.members.length; j++) {
								const member = declaration.members[j];
								const memberName = member.name ? member.name.getText() : null;
								if (
									ts.isConstructorDeclaration(member)
									|| ts.isConstructSignatureDeclaration(member)
									|| ts.isIndexSignatureDeclaration(member)
									|| ts.isCallSignatureDeclaration(member)
									|| memberName === '[Symbol.iterator]'
									|| memberName === '[Symbol.toStringTag]'
									|| memberName === 'toJSON'
									|| memberName === 'toString'
									|| memberName === 'dispose'// TODO: keeping all `dispose` methods
									|| /^_(.*)Brand$/.test(memberName || '') // TODO: keeping all members ending with `Brand`...
								) {
									enqueue_black(member);
								}

								if (isStaticMemberWithSideEffects(ts, member)) {
									enqueue_black(member);
								}
							}

							// queue the heritage clauses
							if (declaration.heritageClauses) {
								for (const heritageClause of declaration.heritageClauses) {
									enqueue_black(heritageClause);
								}
							}
						} else {
							enqueue_black(declaration);
						}
					}
				}
			}
			node.forEachChild(loop);
		};
		node.forEachChild(loop);
	}

	while (export_import_queue.length > 0) {
		const node = export_import_queue.shift()!;
		if (nodeOrParentIsBlack(node)) {
			continue;
		}
		const symbol: ts.Symbol | undefined = (<any>node).symbol;
		if (!symbol) {
			continue;
		}
		const aliased = checker.getAliasedSymbol(symbol);
		if (aliased.declarations && aliased.declarations.length > 0) {
			if (nodeOrParentIsBlack(aliased.declarations[0]) || nodeOrChildIsBlack(aliased.declarations[0])) {
				setColor(node, NodeColor.Black);
			}
		}
	}
}