@@ -500,6 +500,114 @@ bool CResourceChecker::CheckLuaDeobfuscateRequirements(const string& strFileCont
500500 return IsLuaObfuscatedScript (strFileContents.c_str (), strFileContents.length ());
501501}
502502
503+ // Helper struct to store token information
504+ struct LuaToken {
505+ enum Type {
506+ IDENTIFIER,
507+ OPERATOR,
508+ BRACKET,
509+ PARENTHESIS,
510+ OTHER
511+ };
512+
513+ Type type;
514+ std::string value;
515+ long position;
516+ long line;
517+ };
518+
519+ // Helper class to track parsing state
520+ class LuaParseState {
521+ public:
522+ bool isInComment = false ;
523+ bool isInString = false ;
524+ char stringDelimiter = 0 ;
525+ int bracketDepth = 0 ;
526+ int parenthesisDepth = 0 ;
527+ };
528+
529+ class CLuaSyntaxChecker {
530+ public:
531+ static bool IsFunctionCall (const std::string& source, long identifierPos, long identifierLength, long & outLine) {
532+ LuaParseState state;
533+ std::vector<LuaToken> tokens;
534+
535+ // First, tokenize everything after the identifier
536+ long pos = identifierPos + identifierLength;
537+ while (pos < (long )source.length ()) {
538+ // Skip whitespace
539+ while (pos < (long )source.length () && isspace (source[pos])) {
540+ if (source[pos] == ' \n ' ) outLine++;
541+ pos++;
542+ }
543+
544+ if (pos >= (long )source.length ()) break ;
545+
546+ char c = source[pos];
547+
548+ // Handle comments
549+ if (!state.isInString && c == ' -' && pos + 1 < (long )source.length () && source[pos + 1 ] == ' -' ) {
550+ // Skip until end of line
551+ while (pos < (long )source.length () && source[pos] != ' \n ' ) pos++;
552+ continue ;
553+ }
554+
555+ // Handle strings
556+ if (!state.isInString && (c == ' "' || c == ' \' ' )) {
557+ state.isInString = true ;
558+ state.stringDelimiter = c;
559+ pos++;
560+ continue ;
561+ }
562+ if (state.isInString && c == state.stringDelimiter ) {
563+ state.isInString = false ;
564+ pos++;
565+ continue ;
566+ }
567+ if (state.isInString ) {
568+ pos++;
569+ continue ;
570+ }
571+
572+ // Track brackets and parentheses
573+ if (c == ' (' ) {
574+ tokens.push_back ({LuaToken::PARENTHESIS, " (" , pos, outLine});
575+ state.parenthesisDepth ++;
576+ pos++;
577+ break ; // We found an opening parenthesis, no need to look further
578+ }
579+ else if (c == ' {' || c == ' [' ) {
580+ tokens.push_back ({LuaToken::BRACKET, string (1 , c), pos, outLine});
581+ state.bracketDepth ++;
582+ pos++;
583+ }
584+ else if (c == ' }' || c == ' ]' ) {
585+ tokens.push_back ({LuaToken::BRACKET, string (1 , c), pos, outLine});
586+ state.bracketDepth --;
587+ pos++;
588+ }
589+ else if (isalnum (c) || c == ' _' ) {
590+ // Skip identifiers
591+ while (pos < (long )source.length () && (isalnum (source[pos]) || source[pos] == ' _' )) pos++;
592+ }
593+ else {
594+ // Handle operators and other characters
595+ tokens.push_back ({LuaToken::OPERATOR, string (1 , c), pos, outLine});
596+ pos++;
597+ }
598+
599+ // If we find anything other than whitespace or comments before a parenthesis,
600+ // then this isn't a function call
601+ if (!tokens.empty () && tokens.back ().type != LuaToken::PARENTHESIS) {
602+ return false ;
603+ }
604+ }
605+
606+ // Check if we found an opening parenthesis
607+ return !tokens.empty () && tokens.back ().type == LuaToken::PARENTHESIS;
608+ }
609+ };
610+
503611// /////////////////////////////////////////////////////////////
504612//
505613// CResourceChecker::CheckLuaSourceForIssues
@@ -513,6 +621,7 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string
513621{
514622 CHashMap<SString, long > doneWarningMap;
515623 long lLineNumber = 1 ;
624+
516625 // Check if this is a UTF-8 script
517626 bool bUTF8 = IsUTF8BOM (strLuaSource.c_str (), strLuaSource.length ());
518627
@@ -542,8 +651,8 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string
542651 if (checkerMode == ECheckerMode::WARNINGS)
543652 {
544653 m_ulDeprecatedWarningCount++;
545- CLogger::LogPrintf (" WARNING: %s/%s [%s] is encoded in ANSI instead of UTF-8. Please convert your file to UTF-8.\n " , strResourceName. c_str (),
546- strFileName.c_str (), bClientScript ? " Client" : " Server" );
654+ CLogger::LogPrintf (" WARNING: %s/%s [%s] is encoded in ANSI instead of UTF-8. Please convert your file to UTF-8.\n " ,
655+ strResourceName. c_str (), strFileName.c_str (), bClientScript ? " Client" : " Server" );
547656 }
548657 }
549658 }
@@ -557,12 +666,12 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string
557666 if (lNameOffset == -1 )
558667 break ;
559668
560- lNameOffset += lPos; // Make offset absolute from the start of the file
561- lPos = lNameOffset + lNameLength; // Adjust so the next pass starts from just after this identifier
669+ lNameOffset += lPos;
670+ lPos = lNameOffset + lNameLength;
562671
563672 string strIdentifierName (strLuaSource.c_str () + lNameOffset, lNameLength);
564673
565- // In-place upgrade ...
674+ // Handle upgrades ...
566675 if (checkerMode == ECheckerMode::UPGRADE)
567676 {
568677 assert (!bCompiledScript);
@@ -585,12 +694,18 @@ void CResourceChecker::CheckLuaSourceForIssues(string strLuaSource, const string
585694 // Log warnings...
586695 if (checkerMode == ECheckerMode::WARNINGS)
587696 {
588- // Only do the identifier once per file
589- if (doneWarningMap.find (strIdentifierName ) == doneWarningMap.end ())
697+ std::string strContextKey = strIdentifierName + " : " + std::to_string (lLineNumber);
698+ if (doneWarningMap.find (strContextKey ) == doneWarningMap.end ())
590699 {
591- doneWarningMap[strIdentifierName] = 1 ;
592- if (!bCompiledScript) // Don't issue deprecated function warnings if the script is compiled, because we can't upgrade it
593- IssueLuaFunctionNameWarnings (strIdentifierName, strFileName, strResourceName, bClientScript, lLineNumber);
700+ doneWarningMap[strContextKey] = 1 ;
701+ if (!bCompiledScript)
702+ {
703+ long currentLine = lLineNumber;
704+ if (CLuaSyntaxChecker::IsFunctionCall (strLuaSource, lNameOffset, lNameLength, currentLine))
705+ {
706+ IssueLuaFunctionNameWarnings (strIdentifierName, strFileName, strResourceName, bClientScript, lLineNumber);
707+ }
708+ }
594709 CheckVersionRequirements (strIdentifierName, bClientScript);
595710 }
596711 }
@@ -720,7 +835,6 @@ void CResourceChecker::IssueLuaFunctionNameWarnings(const string& strFunctionNam
720835 string strHow;
721836 CMtaVersion strVersion;
722837 ECheckerWhatType what = GetLuaFunctionNameUpgradeInfo (strFunctionName, bClientScript, strHow, strVersion);
723-
724838 if (what == ECheckerWhat::NONE)
725839 return ;
726840
@@ -740,7 +854,6 @@ void CResourceChecker::IssueLuaFunctionNameWarnings(const string& strFunctionNam
740854 strTemp.Format (" %s %s because <min_mta_version> %s setting in meta.xml is below %s" , strFunctionName.c_str (), strHow.c_str (),
741855 bClientScript ? " Client" : " Server" , strVersion.c_str ());
742856 }
743-
744857 CLogger::LogPrint (SString (" WARNING: %s/%s(Line %lu) [%s] %s\n " , strResourceName.c_str (), strFileName.c_str (), ulLineNumber,
745858 bClientScript ? " Client" : " Server" , *strTemp));
746859}
@@ -755,21 +868,33 @@ void CResourceChecker::IssueLuaFunctionNameWarnings(const string& strFunctionNam
755868ECheckerWhatType CResourceChecker::GetLuaFunctionNameUpgradeInfo (const string& strFunctionName, bool bClientScript, string& strOutHow,
756869 CMtaVersion& strOutVersion)
757870{
871+ // Early exit if this is likely a variable assignment
872+ if (strFunctionName.find (' =' ) != std::string::npos)
873+ return ECheckerWhat::NONE;
874+
758875 static CHashMap<SString, SDeprecatedItem*> clientUpgradeInfoMap;
759876 static CHashMap<SString, SDeprecatedItem*> serverUpgradeInfoMap;
760-
761877 if (clientUpgradeInfoMap.size () == 0 )
762878 {
763879 // Make maps to speed things up
764880 for (uint i = 0 ; i < NUMELMS (clientDeprecatedList); i++)
765881 clientUpgradeInfoMap[clientDeprecatedList[i].strOldName ] = &clientDeprecatedList[i];
766-
767882 for (uint i = 0 ; i < NUMELMS (serverDeprecatedList); i++)
768883 serverUpgradeInfoMap[serverDeprecatedList[i].strOldName ] = &serverDeprecatedList[i];
769884 }
770885
771- // Query the correct map
772- SDeprecatedItem* pItem = MapFindRef (bClientScript ? clientUpgradeInfoMap : serverUpgradeInfoMap, strFunctionName);
886+ // Extract just the function name if it's being called
887+ std::string strCleanFunctionName = strFunctionName;
888+ size_t parenPos = strCleanFunctionName.find (' (' );
889+ if (parenPos != std::string::npos)
890+ strCleanFunctionName = strCleanFunctionName.substr (0 , parenPos);
891+
892+ // Trim any whitespace
893+ strCleanFunctionName.erase (0 , strCleanFunctionName.find_first_not_of (" \t\n\r " ));
894+ strCleanFunctionName.erase (strCleanFunctionName.find_last_not_of (" \t\n\r " ) + 1 );
895+
896+ // Query the correct map with the cleaned function name
897+ SDeprecatedItem* pItem = MapFindRef (bClientScript ? clientUpgradeInfoMap : serverUpgradeInfoMap, strCleanFunctionName);
773898 if (!pItem)
774899 return ECheckerWhat::NONE; // Nothing found
775900
0 commit comments