File indexing completed on 2025-01-05 05:20:14

0001 /*  This file is part of the Kate project.
0002  *
0003  *  SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 #include "clazy.h"
0008 #include "kateproject.h"
0009 
0010 #include <KLocalizedString>
0011 
0012 #include <QDir>
0013 #include <QRegularExpression>
0014 
0015 KateProjectCodeAnalysisToolClazy::KateProjectCodeAnalysisToolClazy(QObject *parent)
0016     : KateProjectCodeAnalysisTool(parent)
0017 {
0018 }
0019 
0020 QString KateProjectCodeAnalysisToolClazy::name() const
0021 {
0022     return i18n("Clazy (Qt/C++)");
0023 }
0024 
0025 QString KateProjectCodeAnalysisToolClazy::description() const
0026 {
0027     return i18n("Clazy is a static analysis tool for Qt/C++ code");
0028 }
0029 
0030 QString KateProjectCodeAnalysisToolClazy::fileExtensions() const
0031 {
0032     return QStringLiteral("cpp|cxx|cc|c++|tpp|txx");
0033 }
0034 
0035 QStringList KateProjectCodeAnalysisToolClazy::filter(const QStringList &files) const
0036 {
0037     // c++ files
0038     return files.filter(
0039         QRegularExpression(QStringLiteral("\\.(") + fileExtensions().replace(QStringLiteral("+"), QStringLiteral("\\+")) + QStringLiteral(")$")));
0040 }
0041 
0042 QString KateProjectCodeAnalysisToolClazy::path() const
0043 {
0044     return QStringLiteral("clazy-standalone");
0045 }
0046 
0047 static QString buildDirectory(const QVariantMap &projectMap)
0048 {
0049     const QVariantMap buildMap = projectMap[QStringLiteral("build")].toMap();
0050     const QString buildDir = buildMap[QStringLiteral("directory")].toString();
0051     return buildDir;
0052 }
0053 
0054 QStringList KateProjectCodeAnalysisToolClazy::arguments()
0055 {
0056     if (!m_project) {
0057         return {};
0058     }
0059 
0060     QString compileCommandsDir = compileCommandsDirectory();
0061 
0062     QStringList args;
0063     if (!compileCommandsDir.isEmpty()) {
0064         args = QStringList{QStringLiteral("-p"), compileCommandsDir};
0065     }
0066 
0067     const QStringList fileList = filter(m_project->files());
0068     setActualFilesCount(fileList.size());
0069 
0070     return args << fileList;
0071 }
0072 
0073 QString KateProjectCodeAnalysisToolClazy::notInstalledMessage() const
0074 {
0075     return i18n("Please install 'clazy'.");
0076 }
0077 
0078 FileDiagnostics KateProjectCodeAnalysisToolClazy::parseLine(const QString &line) const
0079 {
0080     //"/path/kate/kate/kateapp.cpp:529:10: warning: Missing reference in range-for with non trivial type (QJsonValue) [-Wclazy-range-loop]"
0081     int idxColon = line.indexOf(QLatin1Char(':'));
0082     if (idxColon < 0) {
0083         return {};
0084     }
0085     // File
0086     const QString file = line.mid(0, idxColon);
0087     idxColon++;
0088 
0089     // Line
0090     int nextColon = line.indexOf(QLatin1Char(':'), idxColon);
0091     if (nextColon < 0) {
0092         return {};
0093     }
0094     const QString lineNo = line.mid(idxColon, nextColon - idxColon);
0095     bool ok = true;
0096     lineNo.toInt(&ok);
0097     if (!ok) {
0098         return {};
0099     }
0100     idxColon = nextColon + 1;
0101 
0102     // Column
0103     nextColon = line.indexOf(QLatin1Char(':'), idxColon);
0104     const QString columnNo = line.mid(idxColon, nextColon - idxColon);
0105     idxColon = nextColon + 1;
0106 
0107     int spaceIdx = line.indexOf(QLatin1Char(' '), nextColon);
0108     if (spaceIdx < 0) {
0109         return {};
0110     }
0111 
0112     idxColon = line.indexOf(QLatin1Char(':'), spaceIdx);
0113     if (idxColon < 0) {
0114         return {};
0115     }
0116 
0117     const QString severity = line.mid(spaceIdx + 1, idxColon - (spaceIdx + 1));
0118 
0119     idxColon++;
0120     QString msg = line.mid(idxColon);
0121 
0122     // Code e.g [-Wclazy-range-loop]
0123     QString code;
0124     {
0125         int bracketOpen = msg.lastIndexOf(QLatin1Char('['));
0126         int bracketClose = msg.lastIndexOf(QLatin1Char(']'));
0127         if (bracketOpen > 0 && bracketClose > 0) {
0128             code = msg.mid(bracketOpen + 1, bracketClose - bracketOpen);
0129             // remove code from msg
0130             msg.remove(bracketOpen, (bracketClose - bracketOpen) + 1);
0131         }
0132     }
0133 
0134     const auto url = QUrl::fromLocalFile(file);
0135     Diagnostic d;
0136     d.message = msg;
0137     d.severity = DiagnosticSeverity::Warning;
0138     d.code = code;
0139     int ln = lineNo.toInt() - 1;
0140     int col = columnNo.toInt() - 1;
0141     col = col < 0 ? 0 : col;
0142     d.range = KTextEditor::Range(ln, col, ln, col);
0143     return {url, {d}};
0144 }
0145 
0146 QString KateProjectCodeAnalysisToolClazy::stdinMessages()
0147 {
0148     return QString();
0149 }
0150 
0151 QString KateProjectCodeAnalysisToolClazy::compileCommandsDirectory() const
0152 {
0153     QString buildDir = buildDirectory(m_project->projectMap());
0154     const QString compCommandsFile = QStringLiteral("compile_commands.json");
0155 
0156     if (buildDir.startsWith(QLatin1String("./"))) {
0157         buildDir = buildDir.mid(2);
0158     }
0159 
0160     /**
0161      * list of absoloute paths to check compile commands
0162      */
0163     const QString possiblePaths[4] = {
0164         /** Absoloute build path in .kateproject e.g from cmake */
0165         buildDir,
0166         /** Relative path in .kateproject e.g */
0167         m_project->baseDir() + (buildDir.startsWith(QLatin1Char('/')) ? buildDir : QLatin1Char('/') + buildDir),
0168         /** Check for the commonly existing "build/" directory */
0169         m_project->baseDir() + QStringLiteral("/build"),
0170         /** Project base, maybe it has a symlink to compile_commands.json file */
0171         m_project->baseDir(),
0172     };
0173 
0174     /**
0175      * Check all paths one by one for compile_commands.json and exit when found
0176      */
0177     QString compileCommandsDir;
0178     for (const QString &path : possiblePaths) {
0179         if (path.isEmpty()) {
0180             continue;
0181         }
0182         const QString guessedPath = QDir(path).filePath(compCommandsFile);
0183         const bool dirHasCompileComds = QFile::exists(guessedPath);
0184         if (dirHasCompileComds) {
0185             compileCommandsDir = guessedPath;
0186             break;
0187         }
0188     }
0189 
0190     return compileCommandsDir;
0191 }