You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1224 lines
34 KiB

/**
* @file tokenize_cleanup.cpp
* Looks at simple sequences to refine the chunk types.
* Examples:
* - change '[' + ']' into '[]'/
* - detect "version = 10;" vs "version (xxx) {"
*
* @author Ben Gardner
* @author Guy Maurel 2015, 2022
* @license GPL v2+
*/
#include "tokenize_cleanup.h"
#include "check_template.h"
#include "combine.h"
#include "combine_skip.h"
#include "flag_braced_init_list.h"
#include "flag_decltype.h"
#include "keywords.h"
#include "prototypes.h"
#include "punctuators.h"
#include "space.h"
#include "unc_ctype.h"
using namespace uncrustify;
/**
* Marks ObjC specific chunks in property declaration, by setting
* parent types and chunk types.
*/
static void cleanup_objc_property(Chunk *start);
/**
* Marks ObjC specific chunks in property declaration (getter/setter attribute)
* Will mark 'test4Setter'and ':' in '@property (setter=test4Setter:, strong) int test4;' as CT_OC_SEL_NAME
*/
static void mark_selectors_in_property_with_open_paren(Chunk *open_paren);
/**
* Marks ObjC specific chunks in property declaration ( attributes)
* Changes all the CT_WORD and CT_TYPE to CT_OC_PROPERTY_ATTR
*/
static void mark_attributes_in_property_with_open_paren(Chunk *open_paren);
void split_off_angle_close(Chunk *pc)
{
const chunk_tag_t *ct = find_punctuator(pc->Text() + 1, cpd.lang_flags);
if (ct == nullptr)
{
return;
}
Chunk nc = *pc;
pc->Str().resize(1);
pc->SetOrigColEnd(pc->GetOrigCol() + 1);
pc->SetType(CT_ANGLE_CLOSE);
nc.SetType(ct->type);
nc.Str().pop_front();
nc.SetOrigCol(nc.GetOrigCol() + 1);
nc.SetColumn(nc.GetColumn() + 1);
nc.CopyAndAddAfter(pc);
}
void tokenize_trailing_return_types()
{
// Issue #2330
// auto max(int a, int b) -> int;
// Issue #2460
// auto f01() -> bool;
// auto f02() noexcept -> bool;
// auto f03() noexcept(true) -> bool;
// auto f04() noexcept(false) -> bool;
// auto f05() noexcept -> bool = delete;
// auto f06() noexcept(true) -> bool = delete;
// auto f07() noexcept(false) -> bool = delete;
// auto f11() const -> bool;
// auto f12() const noexcept -> bool;
// auto f13() const noexcept(true) -> bool;
// auto f14() const noexcept(false) -> bool;
// auto f15() const noexcept -> bool = delete;
// auto f16() const noexcept(true) -> bool = delete;
// auto f17() const noexcept(false) -> bool = delete;
// auto f21() throw() -> bool;
// auto f22() throw() -> bool = delete;
// auto f23() const throw() -> bool;
// auto f24() const throw() -> bool = delete;
for (Chunk *pc = Chunk::GetHead(); pc->IsNotNullChunk(); pc = pc->GetNextNcNnl())
{
char copy[1000];
LOG_FMT(LNOTE, "%s(%d): orig line is %zu, orig col is %zu, Text() is '%s'\n",
__func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), pc->ElidedText(copy));
if ( pc->Is(CT_MEMBER)
&& (strcmp(pc->Text(), "->") == 0))
{
Chunk *tmp = pc->GetPrevNcNnl();
Chunk *tmp_2;
Chunk *open_paren;
if (tmp->Is(CT_QUALIFIER))
{
// auto max(int a, int b) const -> int;
// auto f11() const -> bool;
tmp = tmp->GetPrevNcNnl();
}
else if (tmp->Is(CT_NOEXCEPT))
{
// noexcept is present
tmp_2 = tmp->GetPrevNcNnl();
if (tmp_2->Is(CT_QUALIFIER))
{
// auto f12() const noexcept -> bool;
// auto f15() const noexcept -> bool = delete;
tmp = tmp_2->GetPrevNcNnl();
}
else
{
// auto f02() noexcept -> bool;
// auto f05() noexcept -> bool = delete;
tmp = tmp_2;
}
}
else if (tmp->Is(CT_PAREN_CLOSE))
{
open_paren = tmp->GetPrevType(CT_PAREN_OPEN, tmp->GetLevel());
tmp = open_paren->GetPrevNcNnl();
if (tmp->Is(CT_NOEXCEPT))
{
// noexcept is present
tmp_2 = tmp->GetPrevNcNnl();
if (tmp_2->Is(CT_QUALIFIER))
{
// auto f13() const noexcept(true) -> bool;
// auto f14() const noexcept(false) -> bool;
// auto f16() const noexcept(true) -> bool = delete;
// auto f17() const noexcept(false) -> bool = delete;
tmp = tmp_2->GetPrevNcNnl();
}
else
{
// auto f03() noexcept(true) -> bool;
// auto f04() noexcept(false) -> bool;
// auto f06() noexcept(true) -> bool = delete;
// auto f07() noexcept(false) -> bool = delete;
tmp = tmp_2;
}
}
else if (tmp->Is(CT_THROW))
{
// throw is present
tmp_2 = tmp->GetPrevNcNnl();
if (tmp_2->Is(CT_QUALIFIER))
{
// auto f23() const throw() -> bool;
// auto f24() const throw() -> bool = delete;
tmp = tmp_2->GetPrevNcNnl();
}
else
{
// auto f21() throw() -> bool;
// auto f22() throw() -> bool = delete;
tmp = tmp_2;
}
}
else
{
LOG_FMT(LNOTE, "%s(%d): NOT COVERED\n", __func__, __LINE__);
}
}
else
{
LOG_FMT(LNOTE, "%s(%d): NOT COVERED\n", __func__, __LINE__);
}
if ( tmp->Is(CT_FPAREN_CLOSE)
&& ( tmp->GetParentType() == CT_FUNC_PROTO
|| tmp->GetParentType() == CT_FUNC_DEF))
{
pc->SetType(CT_TRAILING_RET);
LOG_FMT(LNOTE, "%s(%d): set trailing return type for Text() is '%s'\n",
__func__, __LINE__, pc->Text()); // Issue #3222
// TODO
// https://en.cppreference.com/w/cpp/language/function
// noptr-declarator ( parameter-list ) cv(optional) ref(optional) except(optional) attr(optional) -> trailing
Chunk *next = pc->GetNextNcNnl();
if (next->Is(CT_DECLTYPE))
{
// TODO
}
else if (next->Is(CT_WORD))
{
next->SetType(CT_TYPE); // Issue #3222
next = next->GetNextNcNnl();
if (next->Is(CT_ARITH))
{
if (next->GetStr()[0] == '*')
{
next->SetType(CT_PTR_TYPE);
}
else if (next->GetStr()[0] == '&') // Issue #3407
{
next->SetType(CT_BYREF);
}
}
}
else
{
// TODO
}
}
}
}
} // tokenize_trailing_return_types
void tokenize_cleanup()
{
LOG_FUNC_ENTRY();
Chunk *prev = Chunk::NullChunkPtr;
Chunk *next;
bool in_type_cast = false;
cpd.unc_stage = unc_stage_e::TOKENIZE_CLEANUP;
/*
* Since [] is expected to be TSQUARE for the 'operator', we need to make
* this change in the first pass.
*/
Chunk *pc;
for (pc = Chunk::GetHead(); pc->IsNotNullChunk(); pc = pc->GetNextNcNnl())
{
if (pc->Is(CT_SQUARE_OPEN))
{
next = pc->GetNextNcNnl();
if (next->Is(CT_SQUARE_CLOSE))
{
// Change '[' + ']' into '[]'
pc->SetType(CT_TSQUARE);
pc->Str() = "[]";
/*
* bug #664: The original m_origColEnd of CT_SQUARE_CLOSE is
* stored at m_origColEnd of CT_TSQUARE.
* pc->SetOrigColEnd(pc->GetOrigColEnd() + 1);
*/
pc->SetOrigColEnd(next->GetOrigColEnd());
Chunk::Delete(next);
}
}
if ( pc->Is(CT_SEMICOLON)
&& pc->TestFlags(PCF_IN_PREPROC)
&& !pc->GetNextNcNnl(E_Scope::PREPROC))
{
LOG_FMT(LNOTE, "%s(%d): %s:%zu Detected a macro that ends with a semicolon. Possible failures if used.\n",
__func__, __LINE__, cpd.filename.c_str(), pc->GetOrigLine());
}
}
// change := to CT_SQL_ASSIGN Issue #527
for (pc = Chunk::GetHead(); pc->IsNotNullChunk(); pc = pc->GetNextNcNnl())
{
if (pc->Is(CT_COLON))
{
next = pc->GetNextNcNnl();
if (next->Is(CT_ASSIGN))
{
// Change ':' + '=' into ':='
pc->SetType(CT_SQL_ASSIGN);
pc->Str() = ":=";
pc->SetOrigColEnd(next->GetOrigColEnd());
Chunk::Delete(next);
}
}
}
// We can handle everything else in the second pass
pc = Chunk::GetHead();
next = pc->GetNextNcNnl();
while ( pc->IsNotNullChunk()
&& next->IsNotNullChunk())
{
if ( pc->Is(CT_DOT)
&& language_is_set(LANG_ALLC))
{
pc->SetType(CT_MEMBER);
}
if ( pc->Is(CT_NULLCOND)
&& language_is_set(LANG_CS))
{
pc->SetType(CT_MEMBER);
}
// Determine the version stuff (D only)
if (pc->Is(CT_D_VERSION))
{
if (next->Is(CT_PAREN_OPEN))
{
pc->SetType(CT_D_VERSION_IF);
}
else
{
if (next->IsNot(CT_ASSIGN))
{
LOG_FMT(LERR, "%s(%d): %s:%zu: version: Unexpected token %s\n",
__func__, __LINE__, cpd.filename.c_str(), pc->GetOrigLine(), get_token_name(next->GetType()));
exit(EX_SOFTWARE);
}
pc->SetType(CT_WORD);
}
}
// Determine the scope stuff (D only)
if (pc->Is(CT_D_SCOPE))
{
if (next->Is(CT_PAREN_OPEN))
{
pc->SetType(CT_D_SCOPE_IF);
}
else
{
pc->SetType(CT_TYPE);
}
}
/*
* Change CT_BASE before CT_PAREN_OPEN to CT_WORD.
* public myclass() : base() {}
* -or-
* var x = (T)base.y;
*/
if ( pc->Is(CT_BASE)
&& ( next->Is(CT_PAREN_OPEN)
|| next->Is(CT_DOT)))
{
pc->SetType(CT_WORD);
}
if ( pc->Is(CT_ENUM)
&& ( next->Is(CT_STRUCT)
|| next->Is(CT_CLASS)))
{
next->SetType(CT_ENUM_CLASS);
}
Chunk *next_non_attr = language_is_set(LANG_CPP) ? skip_attribute_next(next) : next;
/*
* Change CT_WORD after CT_ENUM, CT_UNION, CT_STRUCT, or CT_CLASS to CT_TYPE
* Change CT_WORD before CT_WORD to CT_TYPE
*/
if (next_non_attr->Is(CT_WORD))
{
if (pc->IsClassEnumStructOrUnion())
{
next_non_attr->SetType(CT_TYPE);
}
if (pc->Is(CT_WORD))
{
pc->SetType(CT_TYPE);
}
}
/*
* change extern to qualifier if extern isn't followed by a string or
* an open parenthesis
*/
if (pc->Is(CT_EXTERN))
{
if (next->Is(CT_STRING))
{
// Probably 'extern "C"'
}
else if (next->Is(CT_PAREN_OPEN))
{
// Probably 'extern (C)'
}
else
{
// Something else followed by a open brace
Chunk *tmp = next->GetNextNcNnl();
if ( tmp->IsNullChunk()
|| tmp->IsNot(CT_BRACE_OPEN))
{
pc->SetType(CT_QUALIFIER);
}
}
}
/*
* Change CT_STAR to CT_PTR_TYPE if preceded by
* CT_TYPE, CT_QUALIFIER, or CT_PTR_TYPE
* or by a
* CT_WORD which is preceded by CT_DC_MEMBER: '::aaa *b'
*/
if ( (next->Is(CT_STAR))
|| ( language_is_set(LANG_CPP)
&& (next->Is(CT_CARET)))
|| ( language_is_set(LANG_CS | LANG_VALA)
&& (next->Is(CT_QUESTION))
&& (strcmp(pc->Text(), "null") != 0)))
{
if ( pc->Is(CT_TYPE)
|| pc->Is(CT_QUALIFIER)
|| pc->Is(CT_PTR_TYPE))
{
next->SetType(CT_PTR_TYPE);
}
}
if ( pc->Is(CT_TYPE_CAST)
&& next->Is(CT_ANGLE_OPEN))
{
next->SetParentType(CT_TYPE_CAST);
in_type_cast = true;
}
if (pc->Is(CT_DECLTYPE))
{
flag_cpp_decltype(pc);
}
// Change angle open/close to CT_COMPARE, if not a template thingy
if ( pc->Is(CT_ANGLE_OPEN)
&& pc->GetParentType() != CT_TYPE_CAST)
{
/*
* pretty much all languages except C use <> for something other than
* comparisons. "#include<xxx>" is handled elsewhere.
*/
if (language_is_set(LANG_OC | LANG_CPP | LANG_CS | LANG_JAVA | LANG_VALA))
{
// bug #663
check_template(pc, in_type_cast);
}
else
{
// convert CT_ANGLE_OPEN to CT_COMPARE
pc->SetType(CT_COMPARE);
}
}
if ( pc->Is(CT_ANGLE_CLOSE)
&& pc->GetParentType() != CT_TEMPLATE)
{
if (in_type_cast)
{
in_type_cast = false;
pc->SetParentType(CT_TYPE_CAST);
}
else
{
next = handle_double_angle_close(pc);
}
}
if (language_is_set(LANG_D))
{
// Check for the D string concat symbol '~'
if ( pc->Is(CT_INV)
&& ( prev->Is(CT_STRING)
|| prev->Is(CT_WORD)
|| next->Is(CT_STRING)))
{
pc->SetType(CT_CONCAT);
}
// Check for the D template symbol '!' (word + '!' + word or '(')
if ( pc->Is(CT_NOT)
&& prev->Is(CT_WORD)
&& ( next->Is(CT_PAREN_OPEN)
|| next->Is(CT_WORD)
|| next->Is(CT_TYPE)
|| next->Is(CT_NUMBER)
|| next->Is(CT_NUMBER_FP)
|| next->Is(CT_STRING)
|| next->Is(CT_STRING_MULTI)))
{
pc->SetType(CT_D_TEMPLATE);
}
// handle "version(unittest) { }" vs "unittest { }"
if ( pc->Is(CT_UNITTEST)
&& prev->Is(CT_PAREN_OPEN))
{
pc->SetType(CT_WORD);
}
// handle 'static if' and merge the tokens
if ( pc->Is(CT_IF)
&& prev->IsString("static"))
{
// delete PREV and merge with IF
pc->Str().insert(0, ' ');
pc->Str().insert(0, prev->GetStr());
pc->SetOrigCol(prev->GetOrigCol());
pc->SetOrigLine(prev->GetOrigLine());
Chunk *to_be_deleted = prev;
prev = prev->GetPrevNcNnl();
if (prev->IsNotNullChunk())
{
Chunk::Delete(to_be_deleted);
}
}
}
if (language_is_set(LANG_CPP))
{
// Change Word before '::' into a type
if ( pc->Is(CT_WORD)
&& next->Is(CT_DC_MEMBER))
{
prev = pc->GetPrev();
if (prev->IsNullChunk()) // Issue #3010
{
pc->SetType(CT_TYPE);
}
else
{
if (prev->Is(CT_COLON))
{
// nothing to do
}
else
{
pc->SetType(CT_TYPE);
}
}
}
// Set parent type for 'if constexpr'
if ( prev->Is(CT_IF)
&& pc->Is(CT_QUALIFIER)
&& pc->IsString("constexpr"))
{
pc->SetType(CT_CONSTEXPR);
}
}
// Change get/set to CT_WORD if not followed by a brace open
if ( pc->Is(CT_GETSET)
&& next->IsNot(CT_BRACE_OPEN))
{
if ( next->Is(CT_SEMICOLON)
&& ( prev->Is(CT_BRACE_CLOSE)
|| prev->Is(CT_BRACE_OPEN)
|| prev->Is(CT_SEMICOLON)))
{
pc->SetType(CT_GETSET_EMPTY);
next->SetParentType(CT_GETSET);
}
else
{
pc->SetType(CT_WORD);
}
}
/*
* Interface is only a keyword in MS land if followed by 'class' or 'struct'
* likewise, 'class' may be a member name in Java.
*/
if ( pc->Is(CT_CLASS)
&& !CharTable::IsKw1(next->GetStr()[0]))
{
if ( next->IsNot(CT_DC_MEMBER)
&& next->IsNot(CT_ATTRIBUTE)) // Issue #2570
{
pc->SetType(CT_WORD);
}
else if ( prev->Is(CT_DC_MEMBER)
|| prev->Is(CT_TYPE))
{
pc->SetType(CT_TYPE);
}
else if (next->Is(CT_DC_MEMBER))
{
Chunk *next2 = next->GetNextNcNnlNet();
if ( next2->Is(CT_INV) // CT_INV hasn't turned into CT_DESTRUCTOR just yet
|| ( next2->Is(CT_CLASS) // constructor isn't turned into CT_FUNC* just yet
&& !strcmp(pc->Text(), next2->Text())))
{
pc->SetType(CT_TYPE);
}
}
}
/*
* Change item after operator (>=, ==, etc) to a CT_OPERATOR_VAL
* Usually the next item is part of the operator.
* In a few cases the next few tokens are part of it:
* operator + - common case
* operator >> - need to combine '>' and '>'
* operator ()
* operator [] - already converted to TSQUARE
* operator new []
* operator delete []
* operator const char *
* operator const B&
* operator std::allocator<U>
*
* In all cases except the last, this will put the entire operator value
* in one chunk.
*/
if (pc->Is(CT_OPERATOR))
{
Chunk *tmp2 = next->GetNext();
// Handle special case of () operator -- [] already handled
if (next->Is(CT_PAREN_OPEN))
{
Chunk *tmp = next->GetNext();
if (tmp->Is(CT_PAREN_CLOSE))
{
next->Str() = "()";
next->SetType(CT_OPERATOR_VAL);
Chunk::Delete(tmp);
next->SetOrigColEnd(next->GetOrigColEnd() + 1);
}
}
else if ( next->Is(CT_ANGLE_CLOSE)
&& tmp2->Is(CT_ANGLE_CLOSE)
&& tmp2->GetOrigCol() == next->GetOrigColEnd())
{
next->Str().append('>');
next->SetOrigColEnd(next->GetOrigColEnd() + 1);
next->SetType(CT_OPERATOR_VAL);
Chunk::Delete(tmp2);
}
else if (next->TestFlags(PCF_PUNCTUATOR))
{
next->SetType(CT_OPERATOR_VAL);
}
else
{
next->SetType(CT_TYPE);
/*
* Replace next with a collection of all tokens that are part of
* the type.
*/
tmp2 = next;
Chunk *tmp;
while ((tmp = tmp2->GetNext())->IsNotNullChunk())
{
if ( tmp->IsNot(CT_WORD)
&& tmp->IsNot(CT_TYPE)
&& tmp->IsNot(CT_QUALIFIER)
&& tmp->IsNot(CT_STAR)
&& tmp->IsNot(CT_CARET)
&& tmp->IsNot(CT_AMP)
&& tmp->IsNot(CT_TSQUARE))
{
break;
}
// Change tmp into a type so that space_needed() works right
make_type(tmp);
size_t num_sp = space_needed(tmp2, tmp);
while (num_sp-- > 0)
{
next->Str().append(" ");
}
next->Str().append(tmp->GetStr());
tmp2 = tmp;
}
while ((tmp2 = next->GetNext()) != tmp)
{
Chunk::Delete(tmp2);
}
next->SetType(CT_OPERATOR_VAL);
next->SetOrigColEnd(next->GetOrigCol() + next->Len());
}
next->SetParentType(CT_OPERATOR);
LOG_FMT(LOPERATOR, "%s(%d): %zu:%zu operator '%s'\n",
__func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), next->Text());
}
// Change private, public, protected into either a qualifier or label
if (pc->Is(CT_ACCESS))
{
// Handle Qt slots - maybe should just check for a CT_WORD?
if ( next->IsString("slots")
|| next->IsString("Q_SLOTS"))
{
Chunk *tmp = next->GetNext();
if (tmp->Is(CT_COLON))
{
next = tmp;
}
}
if (next->Is(CT_COLON))
{
next->SetType(CT_ACCESS_COLON);
Chunk *tmp;
if ((tmp = next->GetNextNcNnl())->IsNotNullChunk())
{
tmp->SetFlagBits(PCF_STMT_START | PCF_EXPR_START);
log_ruleStart("start statementi/ expression", tmp);
}
}
else
{
pc->SetType(( pc->IsString("signals")
|| pc->IsString("Q_SIGNALS"))
? CT_WORD : CT_QUALIFIER);
}
}
// Look for <newline> 'EXEC' 'SQL'
if ( ( pc->IsString("EXEC", false)
&& next->IsString("SQL", false))
|| ( (*pc->GetStr().c_str() == '$')
&& pc->IsNot(CT_SQL_WORD)
/* but avoid breaking tokenization for C# 6 interpolated strings. */
&& ( !language_is_set(LANG_CS)
|| ( pc->Is(CT_STRING)
&& (!pc->GetStr().startswith("$\""))
&& (!pc->GetStr().startswith("$@\""))))))
{
Chunk *tmp = pc->GetPrev();
if (tmp->IsNewline())
{
if (*pc->GetStr().c_str() == '$')
{
pc->SetType(CT_SQL_EXEC);
if (pc->Len() > 1)
{
// SPLIT OFF '$'
Chunk nc;
nc = *pc;
pc->Str().resize(1);
pc->SetOrigColEnd(pc->GetOrigCol() + 1);
nc.SetType(CT_SQL_WORD);
nc.Str().pop_front();
nc.SetOrigCol(nc.GetOrigCol() + 1);
nc.SetColumn(nc.GetColumn() + 1);
nc.CopyAndAddAfter(pc);
next = pc->GetNext();
}
}
tmp = next->GetNext();
if (tmp->IsString("BEGIN", false))
{
pc->SetType(CT_SQL_BEGIN);
}
else if (tmp->IsString("END", false))
{
pc->SetType(CT_SQL_END);
}
else
{
pc->SetType(CT_SQL_EXEC);
}
// Change words into CT_SQL_WORD until CT_SEMICOLON
while (tmp->IsNotNullChunk())
{
if (tmp->Is(CT_SEMICOLON))
{
break;
}
if ( (tmp->Len() > 0)
&& ( unc_isalpha(*tmp->GetStr().c_str())
|| (*tmp->GetStr().c_str() == '$')))
{
tmp->SetType(CT_SQL_WORD);
}
tmp = tmp->GetNextNcNnl();
}
}
}
// handle MS abomination 'for each'
if ( pc->Is(CT_FOR)
&& next->IsString("each")
&& (next == pc->GetNext()))
{
// merge the two with a space between
pc->Str().append(' ');
pc->Str() += next->GetStr();
pc->SetOrigColEnd(next->GetOrigColEnd());
Chunk::Delete(next);
next = pc->GetNextNcNnl();
// label the 'in'
if (next->Is(CT_PAREN_OPEN))
{
Chunk *tmp = next->GetNextNcNnl();
while ( tmp->IsNotNullChunk()
&& tmp->IsNot(CT_PAREN_CLOSE))
{
if (tmp->IsString("in"))
{
tmp->SetType(CT_IN);
break;
}
tmp = tmp->GetNextNcNnl();
}
}
}
/*
* ObjectiveC allows keywords to be used as identifiers in some situations
* This is a dirty hack to allow some of the more common situations.
*/
if (language_is_set(LANG_OC))
{
if ( ( pc->Is(CT_IF)
|| pc->Is(CT_FOR)
|| pc->Is(CT_WHILE))
&& !next->Is(CT_PAREN_OPEN))
{
pc->SetType(CT_WORD);
}
if ( pc->Is(CT_DO)
&& ( prev->Is(CT_MINUS)
|| next->Is(CT_SQUARE_CLOSE)))
{
pc->SetType(CT_WORD);
}
// Fix self keyword back to word when mixing c++/objective-c
if ( pc->Is(CT_THIS)
&& !strcmp(pc->Text(), "self")
&& ( next->Is(CT_COMMA)
|| next->Is(CT_PAREN_CLOSE)))
{
pc->SetType(CT_WORD);
}
// Fix self keyword back to word when mixing c++/objective-c
if ( pc->Is(CT_THIS)
&& !strcmp(pc->Text(), "self")
&& ( next->Is(CT_COMMA)
|| next->Is(CT_PAREN_CLOSE)))
{
pc->SetType(CT_WORD);
}
}
// Vala allows keywords to be used as identifiers
if (language_is_set(LANG_VALA))
{
if ( find_keyword_type(pc->Text(), pc->Len()) != CT_WORD
&& ( prev->Is(CT_DOT)
|| next->Is(CT_DOT)
|| prev->Is(CT_MEMBER)
|| next->Is(CT_MEMBER)
|| prev->Is(CT_TYPE)))
{
pc->SetType(CT_WORD);
}
}
// Another hack to clean up more keyword abuse
if ( pc->Is(CT_CLASS)
&& ( prev->Is(CT_DOT)
|| next->Is(CT_DOT)
|| prev->Is(CT_MEMBER) // Issue #3031
|| next->Is(CT_MEMBER)))
{
pc->SetType(CT_WORD);
}
// Detect Objective C class name
if ( pc->Is(CT_OC_IMPL)
|| pc->Is(CT_OC_INTF)
|| pc->Is(CT_OC_PROTOCOL))
{
if (next->IsNot(CT_PAREN_OPEN))
{
next->SetType(CT_OC_CLASS);
}
next->SetParentType(pc->GetType());
Chunk *tmp = next->GetNextNcNnl();
if (tmp->IsNotNullChunk())
{
tmp->SetFlagBits(PCF_STMT_START | PCF_EXPR_START);
log_ruleStart("start statementi/ expression", tmp);
}
tmp = pc->GetNextType(CT_OC_END, pc->GetLevel());
if (tmp->IsNotNullChunk())
{
tmp->SetParentType(pc->GetType());
}
}
if (pc->Is(CT_OC_INTF))
{
Chunk *tmp = pc->GetNextNcNnl(E_Scope::PREPROC);
while ( tmp->IsNotNullChunk()
&& tmp->IsNot(CT_OC_END))
{
if (get_token_pattern_class(tmp->GetType()) != pattern_class_e::NONE)
{
LOG_FMT(LOBJCWORD, "%s(%d): @interface %zu:%zu change '%s' (%s) to CT_WORD\n",
__func__, __LINE__, pc->GetOrigLine(), pc->GetOrigCol(), tmp->Text(),
get_token_name(tmp->GetType()));
tmp->SetType(CT_WORD);
}
tmp = tmp->GetNextNcNnl(E_Scope::PREPROC);
}
}
/*
* Detect Objective-C categories and class extensions:
* @interface ClassName (CategoryName)
* @implementation ClassName (CategoryName)
* @interface ClassName ()
* @implementation ClassName ()
*/
if ( ( pc->GetParentType() == CT_OC_IMPL
|| pc->GetParentType() == CT_OC_INTF
|| pc->Is(CT_OC_CLASS))
&& next->Is(CT_PAREN_OPEN))
{
next->SetParentType(pc->GetParentType());
Chunk *tmp = next->GetNext();
if ( tmp->IsNotNullChunk()
&& tmp->GetNext()->IsNotNullChunk())
{
if (tmp->Is(CT_PAREN_CLOSE))
{
//tmp->SetType(CT_OC_CLASS_EXT);
tmp->SetParentType(pc->GetParentType());
}
else
{
tmp->SetType(CT_OC_CATEGORY);
tmp->SetParentType(pc->GetParentType());
}
}
tmp = pc->GetNextType(CT_PAREN_CLOSE, pc->GetLevel());
if (tmp->IsNotNullChunk())
{
tmp->SetParentType(pc->GetParentType());
}
}
/*
* Detect Objective C @property:
* @property NSString *stringProperty;
* @property(nonatomic, retain) NSMutableDictionary *shareWith;
*/
if (pc->Is(CT_OC_PROPERTY))
{
if (next->IsNot(CT_PAREN_OPEN))
{
next->SetFlagBits(PCF_STMT_START | PCF_EXPR_START);
log_ruleStart("start statement/ expression", next);
}
else
{
cleanup_objc_property(pc);
}
}
/*
* Detect Objective C @selector:
* @selector(msgNameWithNoArg)
* @selector(msgNameWith1Arg:)
* @selector(msgNameWith2Args:arg2Name:)
*/
if ( pc->Is(CT_OC_SEL)
&& next->Is(CT_PAREN_OPEN))
{
next->SetParentType(pc->GetType());
Chunk *tmp = next->GetNext();
if (tmp->IsNotNullChunk())
{
tmp->SetType(CT_OC_SEL_NAME);
tmp->SetParentType(pc->GetType());
while ((tmp = tmp->GetNextNcNnl())->IsNotNullChunk())
{
if (tmp->Is(CT_PAREN_CLOSE))
{
tmp->SetParentType(CT_OC_SEL);
break;
}
tmp->SetType(CT_OC_SEL_NAME);
tmp->SetParentType(pc->GetType());
}
}
}
// Handle special preprocessor junk
if (pc->Is(CT_PREPROC))
{
pc->SetParentType(next->GetType());
}
// Detect "pragma region" and "pragma endregion"
if ( pc->Is(CT_PP_PRAGMA)
&& next->Is(CT_PREPROC_BODY))
{
if ( (strncmp(next->GetStr().c_str(), "region", 6) == 0)
|| (strncmp(next->GetStr().c_str(), "endregion", 9) == 0))
// TODO: probably better use strncmp
{
pc->SetType((*next->GetStr().c_str() == 'r') ? CT_PP_REGION : CT_PP_ENDREGION);
prev->SetParentType(pc->GetType());
}
}
// Change 'default(' into a sizeof-like statement
if ( language_is_set(LANG_CS)
&& pc->Is(CT_DEFAULT)
&& next->Is(CT_PAREN_OPEN))
{
pc->SetType(CT_SIZEOF);
}
if ( pc->Is(CT_UNSAFE)
&& next->IsNot(CT_BRACE_OPEN))
{
pc->SetType(CT_QUALIFIER);
}
if ( ( pc->Is(CT_USING)
|| ( pc->Is(CT_TRY)
&& language_is_set(LANG_JAVA)))
&& next->Is(CT_PAREN_OPEN))
{
pc->SetType(CT_USING_STMT);
}
// Add minimal support for C++0x rvalue references
if ( pc->Is(CT_BOOL)
&& language_is_set(LANG_CPP)
&& pc->IsString("&&"))
{
if (prev->Is(CT_TYPE))
{
// Issue # 1002
if (!pc->TestFlags(PCF_IN_TEMPLATE))
{
pc->SetType(CT_BYREF);
}
}
}
/*
* HACK: treat try followed by a colon as a qualifier to handle this:
* A::A(int) try : B() { } catch (...) { }
*/
if ( pc->Is(CT_TRY)
&& pc->IsString("try")
&& next->Is(CT_COLON))
{
pc->SetType(CT_QUALIFIER);
}
/*
* If Java's 'synchronized' is in a method declaration, it should be
* a qualifier.
*/
if ( language_is_set(LANG_JAVA)
&& pc->Is(CT_SYNCHRONIZED)
&& next->IsNot(CT_PAREN_OPEN))
{
pc->SetType(CT_QUALIFIER);
}
// change CT_DC_MEMBER + CT_FOR into CT_DC_MEMBER + CT_FUNC_CALL
if ( pc->Is(CT_FOR)
&& pc->GetPrev()->Is(CT_DC_MEMBER))
{
pc->SetType(CT_FUNC_CALL);
}
// TODO: determine other stuff here
prev = pc;
pc = next;
next = pc->GetNextNcNnl();
}
} // tokenize_cleanup
static void cleanup_objc_property(Chunk *start)
{
assert(start->Is(CT_OC_PROPERTY));
Chunk *open_paren = start->GetNextType(CT_PAREN_OPEN, start->GetLevel());
if (open_paren->IsNullChunk())
{
LOG_FMT(LTEMPL, "%s(%d): Property is not followed by opening paren\n", __func__, __LINE__);
return;
}
open_paren->SetParentType(start->GetType());
Chunk *tmp = start->GetNextType(CT_PAREN_CLOSE, start->GetLevel());
if (tmp->IsNotNullChunk())
{
tmp->SetParentType(start->GetType());
tmp = tmp->GetNextNcNnl();
if (tmp->IsNotNullChunk())
{
tmp->SetFlagBits(PCF_STMT_START | PCF_EXPR_START);
log_ruleStart("start statement/ expression", tmp);
tmp = tmp->GetNextType(CT_SEMICOLON, start->GetLevel());
if (tmp->IsNotNullChunk())
{
tmp->SetParentType(start->GetType());
}
}
}
mark_selectors_in_property_with_open_paren(open_paren);
mark_attributes_in_property_with_open_paren(open_paren);
}
static void mark_selectors_in_property_with_open_paren(Chunk *open_paren)
{
assert(open_paren->Is(CT_PAREN_OPEN));
Chunk *tmp = open_paren;
while (tmp->IsNot(CT_PAREN_CLOSE))
{
if ( tmp->Is(CT_WORD)
&& ( tmp->IsString("setter")
|| tmp->IsString("getter")))
{
tmp = tmp->GetNext();
while ( tmp->IsNotNullChunk()
&& tmp->IsNot(CT_COMMA)
&& tmp->IsNot(CT_PAREN_CLOSE))
{
if ( tmp->Is(CT_WORD)
|| tmp->IsString(":"))
{
tmp->SetType(CT_OC_SEL_NAME);
}
tmp = tmp->GetNext();
}
}
else
{
tmp = tmp->GetNext();
}
}
}
static void mark_attributes_in_property_with_open_paren(Chunk *open_paren)
{
assert(open_paren->Is(CT_PAREN_OPEN));
Chunk *tmp = open_paren;
while (tmp->IsNot(CT_PAREN_CLOSE))
{
Chunk *next = tmp->GetNext();
if ( ( tmp->Is(CT_COMMA)
|| tmp->Is(CT_PAREN_OPEN))
&& ( next->Is(CT_WORD)
|| next->Is(CT_TYPE)))
{
next->SetType(CT_OC_PROPERTY_ATTR);
}
tmp = next;
}
}