forked from qt-creator/qt-creator
Clang: Properly handle Q_PROPERTY in TokenInfo
Transform Q_PROPERTY into unique AST node. Mark different parts with types and search for parent in FullTokenInfos. Change-Id: Iaa1ec0c73d34773edf5605d3682bd6a290d195de Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
@@ -23,11 +23,17 @@
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef WRAPPED_QOBJECT_DEFS_H
|
||||
#define WRAPPED_QOBJECT_DEFS_H
|
||||
|
||||
// Include qobjectdefs.h from Qt ...
|
||||
#include_next <qobjectdefs.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wmacro-redefined"
|
||||
#pragma clang diagnostic ignored "-Wgnu-string-literal-operator-template"
|
||||
|
||||
// ...and redefine macros for tagging signals/slots
|
||||
#ifdef signals
|
||||
@@ -54,4 +60,15 @@
|
||||
# define Q_SLOT __attribute__((annotate("qt_slot")))
|
||||
#endif
|
||||
|
||||
template <char... chars>
|
||||
using QPropertyMagicString = std::integer_sequence<char, chars...>;
|
||||
|
||||
template <class T, T... chars>
|
||||
constexpr QPropertyMagicString<chars...> operator""_qpropstr() { return { }; }
|
||||
|
||||
// Create unique AST node for the property.
|
||||
#define Q_PROPERTY(arg) void QPropertyMagicFunction(decltype(#arg ## _qpropstr));
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#endif // WRAPPED_QOBJECT_DEFS_H
|
||||
|
@@ -95,7 +95,8 @@ enum class HighlightingType : quint8
|
||||
Enum,
|
||||
Union,
|
||||
TypeAlias,
|
||||
Typedef
|
||||
Typedef,
|
||||
QtProperty
|
||||
};
|
||||
|
||||
enum class StorageClass : quint8
|
||||
|
@@ -59,6 +59,7 @@ static const char *highlightingTypeToCStringLiteral(HighlightingType type)
|
||||
RETURN_TEXT_FOR_CASE(Union);
|
||||
RETURN_TEXT_FOR_CASE(TypeAlias);
|
||||
RETURN_TEXT_FOR_CASE(Typedef);
|
||||
RETURN_TEXT_FOR_CASE(QtProperty);
|
||||
default: return "UnhandledHighlightingType";
|
||||
}
|
||||
}
|
||||
@@ -79,7 +80,6 @@ QDebug operator<<(QDebug debug, const ExtraInfo &extraInfo)
|
||||
<< extraInfo.definition << ", "
|
||||
<< extraInfo.signal << ", "
|
||||
<< extraInfo.slot << ", "
|
||||
<< extraInfo.property
|
||||
<< ")";
|
||||
return debug;
|
||||
}
|
||||
|
@@ -56,13 +56,12 @@ struct ExtraInfo
|
||||
, definition(false)
|
||||
, signal(false)
|
||||
, slot(false)
|
||||
, property(false)
|
||||
{
|
||||
}
|
||||
ExtraInfo(Utf8String token, Utf8String typeSpelling, Utf8String resultTypeSpelling,
|
||||
Utf8String semanticParentTypeSpelling, AccessSpecifier accessSpecifier,
|
||||
StorageClass storageClass, bool isIdentifier, bool isInclusion,
|
||||
bool isDeclaration, bool isDefinition, bool isSignal, bool isSlot, bool isProperty)
|
||||
bool isDeclaration, bool isDefinition, bool isSignal, bool isSlot)
|
||||
: token(token)
|
||||
, typeSpelling(typeSpelling)
|
||||
, resultTypeSpelling(resultTypeSpelling)
|
||||
@@ -75,7 +74,6 @@ struct ExtraInfo
|
||||
, definition(isDefinition)
|
||||
, signal(isSignal)
|
||||
, slot(isSlot)
|
||||
, property(isProperty)
|
||||
{
|
||||
}
|
||||
Utf8String token;
|
||||
@@ -90,7 +88,6 @@ struct ExtraInfo
|
||||
bool definition : 1;
|
||||
bool signal : 1;
|
||||
bool slot : 1;
|
||||
bool property : 1;
|
||||
};
|
||||
|
||||
inline QDataStream &operator<<(QDataStream &out, const ExtraInfo &extraInfo);
|
||||
@@ -215,7 +212,6 @@ inline QDataStream &operator<<(QDataStream &out, const ExtraInfo &extraInfo)
|
||||
out << extraInfo.definition;
|
||||
out << extraInfo.signal;
|
||||
out << extraInfo.slot;
|
||||
out << extraInfo.property;
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -234,7 +230,6 @@ inline QDataStream &operator>>(QDataStream &in, ExtraInfo &extraInfo)
|
||||
bool isDefinition;
|
||||
bool isSignal;
|
||||
bool isSlot;
|
||||
bool isProperty;
|
||||
|
||||
in >> accessSpecifier;
|
||||
in >> storageClass;
|
||||
@@ -244,7 +239,6 @@ inline QDataStream &operator>>(QDataStream &in, ExtraInfo &extraInfo)
|
||||
in >> isDefinition;
|
||||
in >> isSignal;
|
||||
in >> isSlot;
|
||||
in >> isProperty;
|
||||
|
||||
extraInfo.accessSpecifier = static_cast<AccessSpecifier>(accessSpecifier);
|
||||
extraInfo.storageClass = static_cast<StorageClass>(storageClass);
|
||||
@@ -254,7 +248,6 @@ inline QDataStream &operator>>(QDataStream &in, ExtraInfo &extraInfo)
|
||||
extraInfo.definition = isDefinition;
|
||||
extraInfo.signal = isSignal;
|
||||
extraInfo.slot = isSlot;
|
||||
extraInfo.property = isProperty;
|
||||
return in;
|
||||
}
|
||||
|
||||
@@ -271,8 +264,7 @@ inline bool operator==(const ExtraInfo &first, const ExtraInfo &second)
|
||||
&& first.declaration == second.declaration
|
||||
&& first.definition == second.definition
|
||||
&& first.signal == second.signal
|
||||
&& first.slot == second.slot
|
||||
&& first.property == second.property;
|
||||
&& first.slot == second.slot;
|
||||
}
|
||||
|
||||
inline QDataStream &operator<<(QDataStream &out, HighlightingType highlightingType)
|
||||
|
@@ -50,6 +50,7 @@ TextEditor::TextStyle toTextStyle(ClangBackEnd::HighlightingType type)
|
||||
case HighlightingType::LocalVariable:
|
||||
return TextEditor::C_LOCAL;
|
||||
case HighlightingType::Field:
|
||||
case HighlightingType::QtProperty:
|
||||
return TextEditor::C_FIELD;
|
||||
case HighlightingType::GlobalVariable:
|
||||
return TextEditor::C_GLOBAL;
|
||||
|
@@ -26,6 +26,9 @@
|
||||
#include "clangstring.h"
|
||||
#include "cursor.h"
|
||||
#include "fulltokeninfo.h"
|
||||
#include "sourcerange.h"
|
||||
|
||||
#include <utils/predicates.h>
|
||||
|
||||
namespace ClangBackEnd {
|
||||
|
||||
@@ -72,6 +75,73 @@ void FullTokenInfo::updateTypeSpelling(const Cursor &cursor, bool functionLike)
|
||||
+ (hasSpaceAfterReturnType ? 1 : 0));
|
||||
}
|
||||
|
||||
static Utf8String propertyParentSpelling(CXTranslationUnit cxTranslationUnit,
|
||||
const Utf8String &filePath,
|
||||
uint line, uint column)
|
||||
{
|
||||
// Q_PROPERTY expands into QPropertyMagicFunction which can be found as a child of
|
||||
// the containing class.
|
||||
Cursor tuCursor = clang_getTranslationUnitCursor(cxTranslationUnit);
|
||||
Utf8String parentSpelling;
|
||||
tuCursor.visit([&filePath, line, column, &parentSpelling](CXCursor cxCursor, CXCursor parent) {
|
||||
const CXCursorKind kind = clang_getCursorKind(cxCursor);
|
||||
if (kind == CXCursor_Namespace || kind == CXCursor_StructDecl
|
||||
|| kind == CXCursor_ClassDecl || kind == CXCursor_CXXMethod) {
|
||||
Cursor cursor(cxCursor);
|
||||
const SourceRange range = cursor.sourceRange();
|
||||
if (range.start().filePath() != filePath)
|
||||
return CXChildVisit_Continue;
|
||||
if (range.contains(line, column)) {
|
||||
if (kind == CXCursor_Namespace || kind == CXCursor_StructDecl
|
||||
|| kind == CXCursor_ClassDecl) {
|
||||
return CXChildVisit_Recurse;
|
||||
}
|
||||
// CXCursor_CXXMethod case. This is Q_PROPERTY_MAGIC_FUNCTION
|
||||
parentSpelling = Cursor(parent).type().spelling();
|
||||
return CXChildVisit_Break;
|
||||
}
|
||||
}
|
||||
return CXChildVisit_Continue;
|
||||
});
|
||||
return parentSpelling;
|
||||
}
|
||||
|
||||
static Utf8String getPropertyType(const char *const lineContents, uint propertyPosition)
|
||||
{
|
||||
const char *typeStart = std::strstr(lineContents, "Q_PROPERTY") + 10;
|
||||
typeStart += std::strspn(typeStart, "( \t\n\r");
|
||||
if (typeStart - lineContents >= propertyPosition)
|
||||
return Utf8String();
|
||||
auto typeEnd = std::find_if(std::reverse_iterator<const char*>(lineContents + propertyPosition),
|
||||
std::reverse_iterator<const char*>(typeStart),
|
||||
Utils::unequalTo(' '));
|
||||
|
||||
return Utf8String(typeStart, static_cast<int>(&(*typeEnd) + 1 - typeStart));
|
||||
}
|
||||
|
||||
void FullTokenInfo::updatePropertyData()
|
||||
{
|
||||
CXSourceLocation cxLocation(clang_getTokenLocation(m_cxTranslationUnit, *m_cxToken));
|
||||
const SourceLocation location(m_cxTranslationUnit, cxLocation);
|
||||
m_extraInfo.semanticParentTypeSpelling = propertyParentSpelling(m_cxTranslationUnit,
|
||||
location.filePath(),
|
||||
line(),
|
||||
column());
|
||||
m_extraInfo.declaration = true;
|
||||
m_extraInfo.definition = true;
|
||||
#if defined(CINDEX_VERSION_HAS_GETFILECONTENTS_BACKPORTED) || CINDEX_VERSION_MINOR >= 47
|
||||
// Extract property type from the source code
|
||||
CXFile cxFile;
|
||||
uint offset;
|
||||
clang_getFileLocation(cxLocation, &cxFile, nullptr, nullptr, &offset);
|
||||
const uint propertyPosition = column() - 1;
|
||||
const char *const contents = clang_getFileContents(m_cxTranslationUnit, cxFile, nullptr);
|
||||
const char *const lineContents = &contents[offset - propertyPosition];
|
||||
|
||||
m_extraInfo.typeSpelling = getPropertyType(lineContents, propertyPosition);
|
||||
#endif
|
||||
}
|
||||
|
||||
void FullTokenInfo::identifierKind(const Cursor &cursor, Recursion recursion)
|
||||
{
|
||||
updateTypeSpelling(cursor);
|
||||
@@ -79,6 +149,8 @@ void FullTokenInfo::identifierKind(const Cursor &cursor, Recursion recursion)
|
||||
TokenInfo::identifierKind(cursor, recursion);
|
||||
|
||||
m_extraInfo.identifier = (cursor.kind() != CXCursor_PreprocessingDirective);
|
||||
if (types().mainHighlightingType == HighlightingType::QtProperty)
|
||||
updatePropertyData();
|
||||
}
|
||||
|
||||
void FullTokenInfo::referencedTypeKind(const Cursor &cursor)
|
||||
@@ -138,8 +210,6 @@ void FullTokenInfo::memberReferenceKind(const Cursor &cursor)
|
||||
|
||||
void FullTokenInfo::evaluate()
|
||||
{
|
||||
TokenInfo::evaluate();
|
||||
|
||||
m_extraInfo.token = ClangString(clang_getTokenSpelling(m_cxTranslationUnit, *m_cxToken));
|
||||
|
||||
auto cxTokenKind = clang_getTokenKind(*m_cxToken);
|
||||
@@ -148,6 +218,8 @@ void FullTokenInfo::evaluate()
|
||||
m_extraInfo.definition = m_originalCursor.isDefinition();
|
||||
}
|
||||
m_extraInfo.includeDirectivePath = (m_originalCursor.kind() == CXCursor_InclusionDirective);
|
||||
|
||||
TokenInfo::evaluate();
|
||||
}
|
||||
|
||||
} // namespace ClangBackEnd
|
||||
|
@@ -48,6 +48,7 @@ protected:
|
||||
void memberReferenceKind(const Cursor &cursor) override;
|
||||
private:
|
||||
void updateTypeSpelling(const Cursor &cursor, bool functionLike = false);
|
||||
void updatePropertyData();
|
||||
|
||||
ExtraInfo m_extraInfo;
|
||||
};
|
||||
|
@@ -40,6 +40,7 @@ class SourceLocation
|
||||
friend class SourceRange;
|
||||
friend class TranslationUnit;
|
||||
friend class Cursor;
|
||||
friend class FullTokenInfo;
|
||||
friend bool operator==(const SourceLocation &first, const SourceLocation &second);
|
||||
|
||||
public:
|
||||
|
@@ -69,10 +69,13 @@ bool SourceRange::contains(unsigned line, unsigned column) const
|
||||
const SourceLocation start_ = start();
|
||||
const SourceLocation end_ = end();
|
||||
|
||||
return start_.line() <= line
|
||||
&& start_.column() <= column
|
||||
&& line <= end_.line()
|
||||
&& column <= end_.column();
|
||||
if (line < start_.line() || line > end_.line())
|
||||
return false;
|
||||
if (line == start_.line() && column < start_.column())
|
||||
return false;
|
||||
if (line == end_.line() && column > end_.column())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
SourceRangeContainer SourceRange::toSourceRangeContainer() const
|
||||
|
@@ -32,7 +32,7 @@
|
||||
|
||||
#include <utils/qtcfallthrough.h>
|
||||
|
||||
#include <QDebug>
|
||||
#include <array>
|
||||
|
||||
namespace ClangBackEnd {
|
||||
|
||||
@@ -421,6 +421,9 @@ void TokenInfo::identifierKind(const Cursor &cursor, Recursion recursion)
|
||||
case CXCursor_LabelStmt:
|
||||
m_types.mainHighlightingType = HighlightingType::Label;
|
||||
break;
|
||||
case CXCursor_InvalidFile:
|
||||
invalidFileKind();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -466,10 +469,15 @@ HighlightingType TokenInfo::punctuationKind(const Cursor &cursor)
|
||||
HighlightingType highlightingType = HighlightingType::Invalid;
|
||||
|
||||
switch (cursor.kind()) {
|
||||
case CXCursor_DeclRefExpr: highlightingType = operatorKind(cursor); break;
|
||||
case CXCursor_DeclRefExpr:
|
||||
highlightingType = operatorKind(cursor);
|
||||
break;
|
||||
case CXCursor_Constructor:
|
||||
case CXCursor_CallExpr: collectOutputArguments(cursor); break;
|
||||
default: break;
|
||||
case CXCursor_CallExpr:
|
||||
collectOutputArguments(cursor);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (isOutputArgument())
|
||||
@@ -478,6 +486,75 @@ HighlightingType TokenInfo::punctuationKind(const Cursor &cursor)
|
||||
return highlightingType;
|
||||
}
|
||||
|
||||
enum class PropertyPart
|
||||
{
|
||||
None,
|
||||
Type,
|
||||
Property,
|
||||
Keyword,
|
||||
FunctionOrPrimitiveType
|
||||
};
|
||||
|
||||
static PropertyPart propertyPart(CXTranslationUnit tu, CXToken *token)
|
||||
{
|
||||
static constexpr const char *propertyKeywords[]
|
||||
= {"READ", "WRITE", "MEMBER", "RESET", "NOTIFY", "REVISION", "DESIGNABLE",
|
||||
"SCRIPTABLE", "STORED", "USER", "CONSTANT", "FINAL"
|
||||
};
|
||||
CXSourceLocation location = clang_getTokenLocation(tu, *token);
|
||||
|
||||
// If current token is inside Q_PROPERTY then the cursor from token's position will be
|
||||
// the whole Q_PROPERTY macro cursor.
|
||||
Cursor possibleQPropertyCursor = clang_getCursor(tu, location);
|
||||
if (!(possibleQPropertyCursor.spelling() == "Q_PROPERTY"))
|
||||
return PropertyPart::None;
|
||||
|
||||
const ClangString currentToken = clang_getTokenSpelling(tu, *token);
|
||||
if (std::find(std::begin(propertyKeywords), std::end(propertyKeywords), currentToken)
|
||||
!= std::end(propertyKeywords)) {
|
||||
return PropertyPart::Keyword;
|
||||
}
|
||||
|
||||
const ClangString nextToken = clang_getTokenSpelling(tu, *(token + 1));
|
||||
const ClangString previousToken = clang_getTokenSpelling(tu, *(token - 1));
|
||||
if (std::find(std::begin(propertyKeywords), std::end(propertyKeywords), nextToken)
|
||||
!= std::end(propertyKeywords)) {
|
||||
if (std::find(std::begin(propertyKeywords), std::end(propertyKeywords), previousToken)
|
||||
== std::end(propertyKeywords)) {
|
||||
return PropertyPart::Property;
|
||||
}
|
||||
|
||||
return PropertyPart::FunctionOrPrimitiveType;
|
||||
}
|
||||
|
||||
if (std::find(std::begin(propertyKeywords), std::end(propertyKeywords), previousToken)
|
||||
!= std::end(propertyKeywords)) {
|
||||
return PropertyPart::FunctionOrPrimitiveType;
|
||||
}
|
||||
return PropertyPart::Type;
|
||||
}
|
||||
|
||||
void TokenInfo::invalidFileKind()
|
||||
{
|
||||
const PropertyPart propPart = propertyPart(m_cxTranslationUnit, m_cxToken);
|
||||
|
||||
switch (propPart) {
|
||||
case PropertyPart::None:
|
||||
case PropertyPart::Keyword:
|
||||
m_types.mainHighlightingType = HighlightingType::Invalid;
|
||||
return;
|
||||
case PropertyPart::Property:
|
||||
m_types.mainHighlightingType = HighlightingType::QtProperty;
|
||||
return;
|
||||
case PropertyPart::Type:
|
||||
m_types.mainHighlightingType = HighlightingType::Type;
|
||||
return;
|
||||
case PropertyPart::FunctionOrPrimitiveType:
|
||||
m_types.mainHighlightingType = HighlightingType::Function;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static HighlightingType highlightingTypeForKeyword(CXTranslationUnit cxTranslationUnit,
|
||||
CXToken *cxToken,
|
||||
const Cursor &cursor)
|
||||
|
@@ -94,6 +94,7 @@ protected:
|
||||
virtual void memberReferenceKind(const Cursor &cursor);
|
||||
virtual HighlightingType punctuationKind(const Cursor &cursor);
|
||||
virtual void typeKind(const Cursor &cursor);
|
||||
virtual void invalidFileKind();
|
||||
|
||||
Cursor m_originalCursor;
|
||||
CXToken *m_cxToken;
|
||||
|
@@ -680,7 +680,7 @@ void ClangCodeModelServer::expectDocumentAnnotationsChangedForFileBWithSpecificH
|
||||
AccessSpecifier::Invalid,
|
||||
StorageClass::None,
|
||||
true, false, true, true,
|
||||
false, false, false});
|
||||
false, false});
|
||||
|
||||
EXPECT_CALL(mockClangCodeModelClient,
|
||||
documentAnnotationsChanged(
|
||||
|
@@ -592,3 +592,10 @@ class WithVirtualFunctionDefined {
|
||||
namespace NFoo { namespace NBar { namespace NTest { class NamespaceTypeSpelling; } } }
|
||||
|
||||
Undeclared u;
|
||||
|
||||
#include "../../../../share/qtcreator/cplusplus/wrappedQtHeaders/QtCore/qobjectdefs.h"
|
||||
|
||||
class Property {
|
||||
Q_PROPERTY(const volatile unsigned long long * prop READ getProp WRITE setProp NOTIFY propChanged)
|
||||
Q_PROPERTY(const QString str READ getStr)
|
||||
};
|
||||
|
@@ -500,6 +500,7 @@ static const char *highlightingTypeToCStringLiteral(HighlightingType type)
|
||||
RETURN_TEXT_FOR_CASE(Union);
|
||||
RETURN_TEXT_FOR_CASE(TypeAlias);
|
||||
RETURN_TEXT_FOR_CASE(Typedef);
|
||||
RETURN_TEXT_FOR_CASE(QtProperty);
|
||||
}
|
||||
|
||||
return "";
|
||||
@@ -538,8 +539,7 @@ std::ostream &operator<<(std::ostream &os, const ExtraInfo &extraInfo)
|
||||
<< extraInfo.declaration << ", "
|
||||
<< extraInfo.definition << ", "
|
||||
<< extraInfo.signal << ", "
|
||||
<< extraInfo.slot << ", "
|
||||
<< extraInfo.property
|
||||
<< extraInfo.slot
|
||||
<< ")";
|
||||
return os;
|
||||
}
|
||||
|
@@ -1257,18 +1257,53 @@ TEST_F(TokenProcessor, NamespaceTypeSpelling)
|
||||
|
||||
TEST_F(TokenProcessor, DISABLED_WITHOUT_INVALIDDECL_PATCH(TypeNameOfInvalidDeclarationIsInvalid))
|
||||
{
|
||||
const auto infos = translationUnit.tokenInfosInRange(sourceRange(592, 14));
|
||||
const auto infos = translationUnit.tokenInfosInRange(sourceRange(594, 14));
|
||||
|
||||
ASSERT_THAT(infos[0], HasOnlyType(HighlightingType::Invalid));
|
||||
}
|
||||
|
||||
TEST_F(TokenProcessor, DISABLED_WITHOUT_INVALIDDECL_PATCH(VariableNameOfInvalidDeclarationIsInvalid))
|
||||
{
|
||||
const auto infos = translationUnit.tokenInfosInRange(sourceRange(592, 14));
|
||||
const auto infos = translationUnit.tokenInfosInRange(sourceRange(594, 14));
|
||||
|
||||
ASSERT_THAT(infos[1], HasOnlyType(HighlightingType::Invalid));
|
||||
}
|
||||
|
||||
TEST_F(TokenProcessor, QtPropertyName)
|
||||
{
|
||||
const auto infos = translationUnit.fullTokenInfosInRange(sourceRange(599, 103));
|
||||
|
||||
ASSERT_THAT(infos[8], HasOnlyType(HighlightingType::QtProperty));
|
||||
}
|
||||
|
||||
TEST_F(TokenProcessor, QtPropertyFunction)
|
||||
{
|
||||
const auto infos = translationUnit.fullTokenInfosInRange(sourceRange(599, 103));
|
||||
|
||||
ASSERT_THAT(infos[10], HasOnlyType(HighlightingType::Function));
|
||||
}
|
||||
|
||||
TEST_F(TokenProcessor, QtPropertyInternalKeyword)
|
||||
{
|
||||
const auto infos = translationUnit.fullTokenInfosInRange(sourceRange(599, 103));
|
||||
|
||||
ASSERT_THAT(infos[9], HasOnlyType(HighlightingType::Invalid));
|
||||
}
|
||||
|
||||
TEST_F(TokenProcessor, QtPropertyLastToken)
|
||||
{
|
||||
const auto infos = translationUnit.fullTokenInfosInRange(sourceRange(599, 103));
|
||||
|
||||
ASSERT_THAT(infos[14], HasOnlyType(HighlightingType::Function));
|
||||
}
|
||||
|
||||
TEST_F(TokenProcessor, QtPropertyType)
|
||||
{
|
||||
const auto infos = translationUnit.fullTokenInfosInRange(sourceRange(600, 46));
|
||||
|
||||
ASSERT_THAT(infos[3], HasOnlyType(HighlightingType::Type));
|
||||
}
|
||||
|
||||
Data *TokenProcessor::d;
|
||||
|
||||
void TokenProcessor::SetUpTestCase()
|
||||
|
Reference in New Issue
Block a user