blob: 8ef903701a94819b2f0e58e943c88162bdd29b6a [file]
// Copyright 2026 The GN Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef TOOLS_GN_BUILD_FILE_EDITOR_H_
#define TOOLS_GN_BUILD_FILE_EDITOR_H_
#include <functional>
#include <memory>
#include <optional>
#include <unordered_map>
#include <vector>
#include "gn/err.h"
#include "gn/input_file.h"
#include "gn/label.h"
#include "gn/label_pattern.h"
#include "gn/parse_tree.h"
#include "gn/source_file.h"
class BuildSettings;
class Loader;
struct EditState;
struct EditTarget;
// A TreeNode represents a node in a tree.
// It fundamentally represents a ParseNode, but differs from one as it
// understands where it exists in the tree.
class TreeNode {
public:
explicit TreeNode(std::vector<ParseNode*> stack) : stack_(std::move(stack)) {
DCHECK(!stack_.empty());
}
ParseNode* node() const { return stack_.back(); }
ParseNode* parent() const {
return stack_.size() > 1 ? stack_[stack_.size() - 2] : nullptr;
}
// Returns the BinaryOpNode if the node is an assignment ("=" or "+=").
BinaryOpNode* AsAssignment() const;
// Returns whether the node is conditional in a target.
// Note that if the target itself is conditional, this will return false.
bool is_conditional() const;
// Returns whether the node is a "+=" or "-=" operation.
bool is_modification() const;
// Adds a todo comment to the build file to show the user where manual
// intervention is required.
void add_todo(EditState& state, const EditTarget& target) const;
// Removes self from the tree, or adds a TODO suggesting that it should
// probably be removed.
void RemoveSelf(EditState& state, const EditTarget& target) const;
// Removes self from the tree unconditionally without adding TODO comments.
void RemoveSelfUnconditionally() const;
ParseNode* operator->() const { return stack_.back(); }
const std::vector<ParseNode*>& stack() const { return stack_; }
TreeNode Descend(ParseNode* child) const;
private:
std::vector<ParseNode*> stack_;
};
template <typename T>
void FindStatementRecursive(
ParseNode* node,
std::vector<ParseNode*>& stack,
const std::function<std::optional<T>(TreeNode&)>& transform,
std::vector<T>* results) {
if (!node)
return;
stack.push_back(node);
TreeNode node_ref(stack);
if (auto mapped = transform(node_ref)) {
results->push_back(std::move(*mapped));
}
if (auto* block = node->AsBlockMut()) {
for (const auto& stmt : block->statements()) {
FindStatementRecursive(stmt.get(), stack, transform, results);
}
} else if (auto* condition = node->AsConditionMut()) {
FindStatementRecursive(condition->if_true(), stack, transform, results);
if (condition->if_false()) {
FindStatementRecursive(condition->if_false(), stack, transform, results);
}
} else if (auto* func = node->AsFunctionCallMut(); func && func->block()) {
FindStatementRecursive(func->block(), stack, transform, results);
}
stack.pop_back();
}
// Returns a vector of nodes matching a condition.
// May also apply a transformation to add useful metadata to them.
template <typename T>
std::vector<T> FindStatement(
ParseNode* root,
const std::function<std::optional<T>(TreeNode&)>& transform) {
std::vector<T> results;
std::vector<ParseNode*> stack;
FindStatementRecursive<T>(root, stack, transform, &results);
return results;
}
// Finds an element in an assignment expression ("=" or "+=") whose right-hand
// side likely evaluates to a list.
std::vector<TreeNode> FindListElementInAssignment(const EditTarget& target,
const TreeNode& root,
const Value& value);
// Finds the first list node within an assignment expression.
std::optional<ListNode*> FindListInAssignment(const TreeNode& assignment);
// Represents a set of patterns within a build file.
class LabelMatcher {
public:
LabelMatcher(SourceDir source_dir, const std::vector<LabelPattern>& patterns);
enum MatchType {
// Target does not match any pattern in this build file.
NONE,
// Target was explicitly named (e.g. "//foo:bar"). Unmatched explicit
// targets will trigger an error when done() is called.
EXACT,
// Target matched a wildcard pattern (e.g. "//foo:*" or "//foo/*").
GLOB,
};
// Checks whether a label was a match for a given pattern.
MatchType matches(const std::string& name);
// Call this when done editing a build file.
// Any explicitly requested targets that were unused will trigger an error.
Err done() const;
private:
SourceDir source_dir_;
bool globbed_ = false;
std::unordered_map<std::string, bool> used_;
};
// Represents a build target to be edited.
struct EditTarget {
// Calculates all =, +=, and -= of a given attribute.
std::vector<TreeNode> assignments(std::string_view attr) const;
// Emits a warning to the user.
void add_warning(EditState& state, std::string_view message) const;
// True if the target was explicitly requested to be edited.
// This is relevant, because if the user requests something like
// "remove deps //dep" //:*, then we should not print warnings
// if not all targets depend on //dep.
// On the other hand, if the user says "remove deps //dep" //:foo,
// and we can't find a dep on //dep, we should warn them about it.
bool is_explicit;
Label label;
TreeNode node;
BlockNode* block;
};
// Represents a build file to be edited.
class BuildFile {
public:
static Result<BuildFile> Create(const BuildSettings* build_settings,
const SourceFile& source_file,
const std::vector<LabelPattern>& patterns);
const SourceFile& source_file() const { return source_file_; }
ParseNode* root() const { return tree_root_.get(); }
LabelMatcher& label_matcher() { return label_matcher_; }
// Returns a generic location at the start of the file.
// This is relevant because generated nodes won't have location information.
Location location() const;
// Returns all targets matching the patterns.
std::vector<EditTarget> targets();
// Creates a node to insert into the graph.
std::unique_ptr<ParseNode> to_node(const Value& value);
// Creates a node for an identifier.
std::unique_ptr<IdentifierNode> create_identifier(std::string_view value);
// Creates a node for `a = b`
std::unique_ptr<BinaryOpNode> create_assignment(
std::string_view name,
std::unique_ptr<ParseNode> value);
// Serializes the AST to the build file if it has changed.
// Returns Ok(true) if the file was written, Ok(false) if it was unchanged.
Result<bool> Write();
private:
BuildFile(const BuildSettings* build_settings,
SourceFile source_file,
std::unique_ptr<InputFile> input_file,
std::unique_ptr<ParseNode> tree_root,
LabelMatcher label_matcher);
const BuildSettings* build_settings_;
SourceFile source_file_;
std::unique_ptr<InputFile> input_file_;
std::unique_ptr<ParseNode> tree_root_;
// Our custom ParseNodes generated by to_node contain string views.
// We store objects containing the underlying strings here to ensure they
// live long enough.
std::vector<std::unique_ptr<InputFile>> extra_files_;
LabelMatcher label_matcher_;
};
// Resolves a list of LabelPatterns into the union of the build files they
// cover.
Result<std::vector<BuildFile>> ResolvePatternsToBuildFiles(
const BuildSettings* build_settings,
const Loader* loader,
const std::vector<LabelPattern>& patterns);
#endif // TOOLS_GN_BUILD_FILE_EDITOR_H_