traverse() {
		// Because the AST doesn't mutate, we can cache the steps
		if (this.#steps) {
			return this.#steps.values();
		}

		/** @type {Array<VisitNodeStep>} */
		const steps = (this.#steps = []);

		const visit = (node, parent) => {
			// first set the parent
			this.#parents.set(node, parent);

			// then add the step
			steps.push(
				new VisitNodeStep({
					target: node,
					phase: 1,
					args: [node, parent],
				}),
			);

			// save HTML nodes
			if (node.type === "html") {
				this.#htmlNodes.push(node);
			}

			// then visit the children
			if (node.children) {
				node.children.forEach(child => {
					visit(child, node);
				});
			}

			// then add the exit step
			steps.push(
				new VisitNodeStep({
					target: node,
					phase: 2,
					args: [node, parent],
				}),
			);
		};

		visit(this.ast);

		return steps.values();
	}
Traverse the source code and return the steps that were taken.
@returns {Iterable<TraversalStep>} The steps that were taken while traversing the source code.