Dateien, die als Parameter übergeben werden, werden nach dem Start direkt geladen
This commit is contained in:
181
barecode/src/editor/DeadCodeAnalyzer.cpp
Normal file
181
barecode/src/editor/DeadCodeAnalyzer.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
#include "DeadCodeAnalyzer.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
#include <QFileInfo>
|
||||
|
||||
DeadCodeAnalyzer::DeadCodeAnalyzer(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
m_scanner = new FunctionScanner(this);
|
||||
}
|
||||
|
||||
QList<DeadCodeAnalyzer::DeadFunction> DeadCodeAnalyzer::analyze(
|
||||
const QString &projectRoot,
|
||||
const QStringList &extensions,
|
||||
const QStringList &excludeDirs) const
|
||||
{
|
||||
// -----------------------------------------------------------------------
|
||||
// Schritt 1: Alle Dateien sammeln (mit Exclude-Filter)
|
||||
// -----------------------------------------------------------------------
|
||||
QStringList allFiles;
|
||||
QDirIterator it(projectRoot, extensions, QDir::Files,
|
||||
QDirIterator::Subdirectories);
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
const QString path = it.next();
|
||||
|
||||
// Exclude-Verzeichnisse prüfen
|
||||
bool excluded = false;
|
||||
if (!excludeDirs.isEmpty())
|
||||
{
|
||||
const QStringList parts = path.split('/');
|
||||
for (const QString &excl : excludeDirs)
|
||||
{
|
||||
if (parts.contains(excl.trimmed(), Qt::CaseInsensitive))
|
||||
{
|
||||
excluded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!excluded)
|
||||
{
|
||||
allFiles.append(path);
|
||||
}
|
||||
}
|
||||
|
||||
const int totalFiles = allFiles.size();
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Schritt 2: Alle Funktionsdefinitionen aus nicht-excluded Dateien
|
||||
// -----------------------------------------------------------------------
|
||||
QHash<QString, FunctionScanner::FunctionInfo> definitions;
|
||||
|
||||
for (const QString &path : allFiles)
|
||||
{
|
||||
const QList<FunctionScanner::FunctionInfo> funcs = m_scanner->scanFile(path);
|
||||
for (const FunctionScanner::FunctionInfo &func : funcs)
|
||||
{
|
||||
if (func.name.startsWith("__"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const QString key = func.name.toLower();
|
||||
if (!definitions.contains(key))
|
||||
{
|
||||
definitions.insert(key, func);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (definitions.isEmpty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Schritt 3: Alle Dateien EINMAL lesen, alle Aufrufe sammeln
|
||||
// -----------------------------------------------------------------------
|
||||
QSet<QString> calledFunctions;
|
||||
|
||||
static const QRegularExpression identifierRegex(
|
||||
R"((?:->|::|\b)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\s*\()"
|
||||
R"(|['"]([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)['"])"
|
||||
);
|
||||
|
||||
static const QRegularExpression defLineRegex(
|
||||
R"(\bfunction\s+([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*))"
|
||||
);
|
||||
|
||||
int filesScanned = 0;
|
||||
|
||||
for (const QString &filePath : allFiles)
|
||||
{
|
||||
++filesScanned;
|
||||
|
||||
// Fortschritt signalisieren
|
||||
emit progressUpdate(filesScanned, totalFiles,
|
||||
QFileInfo(filePath).fileName());
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QTextStream stream(&file);
|
||||
stream.setEncoding(QStringConverter::Utf8);
|
||||
|
||||
while (!stream.atEnd())
|
||||
{
|
||||
const QString line = stream.readLine();
|
||||
const QString trimmed = line.trimmed();
|
||||
|
||||
if (trimmed.startsWith("//") ||
|
||||
trimmed.startsWith("*") ||
|
||||
trimmed.startsWith("#") ||
|
||||
trimmed.startsWith("/*"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Definitionen auf dieser Zeile nicht als Aufruf werten
|
||||
QSet<QString> definedOnThisLine;
|
||||
{
|
||||
QRegularExpressionMatchIterator dit = defLineRegex.globalMatch(line);
|
||||
while (dit.hasNext())
|
||||
{
|
||||
definedOnThisLine.insert(dit.next().captured(1).toLower());
|
||||
}
|
||||
}
|
||||
|
||||
QRegularExpressionMatchIterator mit = identifierRegex.globalMatch(line);
|
||||
while (mit.hasNext())
|
||||
{
|
||||
const QRegularExpressionMatch match = mit.next();
|
||||
const QString name = match.captured(1).isEmpty()
|
||||
? match.captured(2).toLower()
|
||||
: match.captured(1).toLower();
|
||||
|
||||
if (name.isEmpty() || definedOnThisLine.contains(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (definitions.contains(name))
|
||||
{
|
||||
calledFunctions.insert(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Schritt 4: Nicht aufgerufene Funktionen zurückgeben
|
||||
// -----------------------------------------------------------------------
|
||||
QList<DeadFunction> dead;
|
||||
|
||||
for (auto it2 = definitions.begin(); it2 != definitions.end(); ++it2)
|
||||
{
|
||||
if (!calledFunctions.contains(it2.key()))
|
||||
{
|
||||
const FunctionScanner::FunctionInfo &func = it2.value();
|
||||
DeadFunction d;
|
||||
d.info = func;
|
||||
d.reason = func.className.isEmpty()
|
||||
? QObject::tr("Globale Funktion — kein Aufruf gefunden")
|
||||
: QObject::tr("Methode von %1 — kein Aufruf gefunden")
|
||||
.arg(func.className);
|
||||
dead.append(d);
|
||||
}
|
||||
}
|
||||
|
||||
return dead;
|
||||
}
|
||||
Reference in New Issue
Block a user