Extended AST matchers
In addition to the AST matchers that Clang provides by default, clang-tidy for Unreal Engine adds additional matchers that are helpful when trying to match against Unreal Engine code. All of these extended AST matchers are detailed below.
Narrowing Matchers
Narrowing matchers match certain attributes on the current node, thus narrowing down the set of nodes of the current type to match on.
isUClass
Matcher<NamedDecl> | isUClass |
Matches if the declaration is a UCLASS()
.
For example, using this matcher would match UA
but not B
:
UCLASS()
class UA : public UObject
{
GENERATED_BODY();
};
class B
{
};
isUStruct
Matcher<NamedDecl> | isUStruct |
Matches if the declaration is a USTRUCT()
.
For example, using this matcher would match FA
but not B
:
USTRUCT()
struct FA
{
GENERATED_BODY();
};
struct B
{
};
isUInterface
Matcher<NamedDecl> | isUInterface |
Matches if the declaration is a UINTERFACE()
.
For example, using this matcher would match UI
but not B
(it also doesn't match II
):
UINTERFACE()
class UI : public UInterface
{
GENERATED_BODY();
};
class II
{
GENERATED_BODY();
}
class B
{
};
isIInterface
Matcher<NamedDecl> | isIInterface |
Matches if the declaration is the associated interface of a UINTERFACE()
.
For example, using this matcher would match II
but not UI
:
UINTERFACE()
class UI : public UInterface
{
GENERATED_BODY();
};
class II
{
GENERATED_BODY();
}
class B
{
};
isUFunction
Matcher<NamedDecl> | isUFunction |
Matches if the declaration is a UFUNCTION()
.
For example, using this matcher would match A
but not B
:
UCLASS()
class UObj : public UObject
{
GENERATED_BODY();
public:
UFUNCTION()
void A();
void B();
};
isUProperty
Matcher<NamedDecl> | isUProperty |
Matches if the declaration is a UPROPERTY()
.
For example, using this matcher would match A
but not B
:
UCLASS()
class UObj : public UObject
{
GENERATED_BODY();
public:
UPROPERTY()
int A;
int B;
};
hasUSpecifier
Matcher<NamedDecl> | hasUSpecifier | std::string Name |
Matches if the declaration has Name
as a specifier. Name
is a case insensitive match. For the following code:
UPROPERTY(Replicated)
int A;
the A
field declaration would be matched by hasUSpecifier("Replicated")
.
hasUSpecifierValue
Matcher<NamedDecl> | hasUSpecifierValue | std::string Name, std::string Value |
Matches if the declaration has Name
as a specifier with the value Value
. Name
is a case insensitive match. Value
is matched exactly. For the following code:
UPROPERTY(BlueprintGetter=Hello)
int A;
the A
field declaration would be matched by hasUSpecifierValue("blueprintgetter", "Hello")
.
hasUMetadata
Matcher<NamedDecl> | hasUMetadata | std::string Name |
Matches if the declaration has Name
as a specifier. Name
is a case insensitive match. For the following code:
UPROPERTY(meta = (Category = "Hello"))
int A;
the A
field declaration would be matched by hasUMetadata("Category")
.
hasUMetadataValue
Matcher<NamedDecl> | hasUMetadataValue | std::string Name, std::string Value |
Matches if the declaration has Name
as a specifier with the value Value
. Name
is a case insensitive match. Value
is matched exactly. For the following code:
UPROPERTY(meta = (Category = "Hello"))
int A;
the A
field declaration would be matched by hasUMetadataValue("category", "Hello")
.
isPODType
Matcher<QualType> | isPODType |
Matches if the type is a Plain Old Data (POD) type.
For example, using this matcher would match the type of A
but not the type of B
:
class FStruct {
int A;
FString B;
}
Traversal Matchers
Traversal matchers specify the relationship to other nodes that are reachable from the current node.
forNone
Matcher<*> | forNone | Matcher<*> |
The exclusion version of forEach
, this is intended to be used when you have at least one other forEach
matcher in the expression, and you want exclude a set of nodes that meet that other condition.
It's best explained with an example. Let us say you have a class whose method refers to the field declarations through member access, and you want to find the fields that are not accessed in Method
:
class Test {
int A;
int B;
int C;
int D;
void Method() {
this->A;
this->D;
}
}
If you were to use the matcher expression:
cxxMethodDecl(
hasName("Method"),
ofClass(
cxxRecordDecl(
forEach(
fieldDecl().bind("declared_field")
)
)
),
hasBody(
compoundStmt(
forEach(
memberExpr(
member(
fieldDecl().bind("referenced_field")
)
)
)
)
)
)
It would give you the following set of matches:
Match | declared_field | referenced_field |
---|---|---|
#1 | A | A |
#2 | B | A |
#3 | C | A |
#4 | D | A |
#5 | A | D |
#6 | B | D |
#7 | C | D |
#8 | D | D |
That is, it gives you every combination as a unique match result.
If you were to constrain the referenced_field
so that it had to match the declared_field
using equalsBoundNode
within forEach(memberExpr(member(fieldDecl(equalsBoundNode("declared_field")).bind("referenced_field"))))
, it would give you the subset where both are equal:
Match | declared_field | referenced_field |
---|---|---|
#1 | A | A |
#2 | D | D |
forNone
would give you the subset where there is no matching referenced_field
for declared_field
with forNone(memberExpr(member(fieldDecl(equalsBoundNode("declared_field")).bind("referenced_field"))))
:
Match | declared_field |
---|---|
#1 | B |
#2 | C |
Note that .bind()
within a forNone
has no effect, because any match that would generate bindings within a forNone
would preclude the result from being included anyway.
forNoDescendant
Matcher<*> | forNoDescendant | Matcher<*> |
forNoDescendant
is the forEachDescendant
equivalent of forNone
. Rather than just checking if there are no immediate children that match the inner matcher, it checks all descendants.
refersToPack
Matcher<TemplateArgument> | refersToPack | Matcher<TemplateArgument> InnerMatcher |
Where a given template argument matches a pack parameter (...
), this iterates over all of template arguments contained within a pack, and matches if any of them match InnerMatcher
.
withUInterface
Matcher<CXXRecordDecl> | withUInterface | Matcher<CXXRecordDecl> InnerMatcher |
Where a given matcher is an interface class of a UINTERFACE()
(that is, it is the ITheInterface
to an interface declared as UTheInterface
), this allows you to match on the the associated UInterface
declaration.
For example, in the following code:
UINTERFACE()
class UTheInterface : public UInterface
{
GENERATED_BODY()
};
class ITheInterface
{
GENERATED_BODY()
};
Then the following matcher expression would match:
cxxRecordDecl(hasName("ITheInterface"), withUInterface(cxxRecordDecl(hasName("UTheInterface"))))
withIInterface
Matcher<CXXRecordDecl> | withIInterface | Matcher<CXXRecordDecl> InnerMatcher |
Where a given matcher is a UINTERFACE()
(that is, it is the UTheInterface
which has an interface class ITheInterface
declared), this allows you to match on the the associated interface class declaration.
For example, in the following code:
UINTERFACE()
class UTheInterface : public UInterface
{
GENERATED_BODY()
};
class ITheInterface
{
GENERATED_BODY()
};
Then the following matcher expression would match:
cxxRecordDecl(hasName("UTheInterface"), withIInterface(cxxRecordDecl(hasName("ITheInterface"))))